Compare commits
125 Commits
docker.nu
...
py-enable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b272be9c4c | ||
|
|
dfc7f123e0 | ||
|
|
0cb179dd29 | ||
|
|
adfc2ba69b | ||
|
|
e949227938 | ||
|
|
485f0842fb | ||
|
|
8904ce04f8 | ||
|
|
4dbeae6ec8 | ||
|
|
9f184ca4f2 | ||
|
|
b0e3fda414 | ||
|
|
611d5e8bf3 | ||
|
|
6d9edc8c2b | ||
|
|
0c4901f802 | ||
|
|
f9e4e0a8d2 | ||
|
|
2478d29e8c | ||
|
|
c397e99adc | ||
|
|
2638dfcf00 | ||
|
|
99b3308c56 | ||
|
|
0b0e5640d8 | ||
|
|
e29a54022c | ||
|
|
940fb03023 | ||
|
|
9b6e464c5a | ||
|
|
b74d3fd6e0 | ||
|
|
596969eac5 | ||
|
|
e41fe928d8 | ||
|
|
73dbb8fb80 | ||
|
|
dc00a13501 | ||
|
|
e726013369 | ||
|
|
7956d3a7ed | ||
|
|
8a93418c25 | ||
|
|
587f16dced | ||
|
|
b5703c8a37 | ||
|
|
8ba0f3addd | ||
|
|
60769e9ed3 | ||
|
|
997d3ffeb3 | ||
|
|
ff3339dc4e | ||
|
|
8b65f1bc38 | ||
|
|
6c5a9238f0 | ||
|
|
b1155a9012 | ||
|
|
4296315995 | ||
|
|
4812c0cc9e | ||
|
|
c8cc9d2941 | ||
|
|
0fdcce5d18 | ||
|
|
6fec134d3e | ||
|
|
cd440564d2 | ||
|
|
343ff94f3c | ||
|
|
90ebbff3f4 | ||
|
|
d03c92e246 | ||
|
|
6e8037255f | ||
|
|
7f516f0868 | ||
|
|
512ca0d549 | ||
|
|
03ad0385ad | ||
|
|
f29ee67d9d | ||
|
|
24a853b8da | ||
|
|
c1304523d4 | ||
|
|
46458a5cef | ||
|
|
487c273bfb | ||
|
|
cfcd84be3f | ||
|
|
c60f8dacf1 | ||
|
|
5d8260e90e | ||
|
|
eaa74d5582 | ||
|
|
5df05d5f0f | ||
|
|
f3ef5105d3 | ||
|
|
680fb1852a | ||
|
|
6bfaa8c034 | ||
|
|
ac3bdf753c | ||
|
|
a1579c1654 | ||
|
|
31f61ab7a2 | ||
|
|
5544d42a2d | ||
|
|
0b65cb23a4 | ||
|
|
5ec180f443 | ||
|
|
f8506259ad | ||
|
|
6682b3daa9 | ||
|
|
94848d42de | ||
|
|
eaf633ce76 | ||
|
|
bf14434cdb | ||
|
|
9fb92c8fc6 | ||
|
|
8daec06e20 | ||
|
|
e0b6aa42a2 | ||
|
|
fdb712234e | ||
|
|
d17397b070 | ||
|
|
86e8f2a4fb | ||
|
|
38199a2e7e | ||
|
|
bf23ddd0a5 | ||
|
|
e3d6aef109 | ||
|
|
806d3ad0e7 | ||
|
|
a0c5957065 | ||
|
|
23e20d01ad | ||
|
|
b8152a3259 | ||
|
|
0e41023889 | ||
|
|
2800903c9a | ||
|
|
da918bee83 | ||
|
|
f9ce01c1dc | ||
|
|
5b9700d454 | ||
|
|
b435a5bc4f | ||
|
|
697a060e3e | ||
|
|
2bed97bbcc | ||
|
|
119d7ba77f | ||
|
|
7567b2199b | ||
|
|
b47e9c14f2 | ||
|
|
789c2ec4da | ||
|
|
f29c70279f | ||
|
|
27a25d4af9 | ||
|
|
928ff002a1 | ||
|
|
5381c76b7d | ||
|
|
1ac63641b9 | ||
|
|
411ad1b190 | ||
|
|
2f1804b6d0 | ||
|
|
ecf2222f18 | ||
|
|
7084d2e61a | ||
|
|
1a1ea68216 | ||
|
|
1eaef8542b | ||
|
|
74bb660b97 | ||
|
|
316a2167e7 | ||
|
|
e47dd697f9 | ||
|
|
6776b2b162 | ||
|
|
a88fbb238a | ||
|
|
e5308553cb | ||
|
|
9cc3dd3c5a | ||
|
|
ff72f3a9a1 | ||
|
|
737e664792 | ||
|
|
047c16acd4 | ||
|
|
a8fa75a5c3 | ||
|
|
59bcce7a96 | ||
|
|
fcfad69195 |
25
.github/workflows/backend-check.yml
vendored
25
.github/workflows/backend-check.yml
vendored
@@ -2,8 +2,8 @@ name: Backend check
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "backend/**"
|
||||
- ".github/workflows/backend-check.yml"
|
||||
- "backend/**"
|
||||
- ".github/workflows/backend-check.yml"
|
||||
|
||||
jobs:
|
||||
check_oss:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -32,16 +32,15 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: install xmlsec1
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -71,16 +70,14 @@ jobs:
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -110,8 +107,8 @@ jobs:
|
||||
|
||||
- name: install xmlsec1
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
@@ -120,7 +117,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -130,5 +127,3 @@ jobs:
|
||||
run: |
|
||||
mkdir -p fake_frontend_build
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
|
||||
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
|
||||
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 30
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
|
||||
-c
|
||||
|
||||
4
.github/workflows/build-publish-rh-image.yml
vendored
4
.github/workflows/build-publish-rh-image.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
48
.github/workflows/deno_on_release.yml
vendored
48
.github/workflows/deno_on_release.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: Publish deno-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
env:
|
||||
repo: windmill-deno-client
|
||||
|
||||
jobs:
|
||||
build_deno_and_push_to_repo:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: generate_deno
|
||||
run: |
|
||||
cd deno-client
|
||||
rm .gitignore
|
||||
./build.sh
|
||||
- name: Pushes to another repository
|
||||
id: push_directory
|
||||
uses: cpina/github-action-push-to-another-repository@devel
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }}
|
||||
with:
|
||||
source-directory: deno-client/
|
||||
destination-github-username: ${{ github.repository_owner }}
|
||||
destination-repository-name: ${{ env.repo }}
|
||||
user-email: ruben@windmill.dev
|
||||
commit-message: See ORIGIN_COMMIT from $GITHUB_REF
|
||||
target-branch: main
|
||||
|
||||
tag_repo:
|
||||
needs: [build_deno_and_push_to_repo]
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/${{ env.repo }}
|
||||
token: ${{ secrets.DENO_PAT }}
|
||||
path: ./client
|
||||
|
||||
- name: Push client
|
||||
run: |
|
||||
cd ./client
|
||||
git config --global user.email "ruben@windmill.dev"
|
||||
git config --global user.name "rubenfiszel[bot]"
|
||||
git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}"
|
||||
git push --tags
|
||||
2
.github/workflows/docker-image-rpi4.yml
vendored
2
.github/workflows/docker-image-rpi4.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
|
||||
97
.github/workflows/docker-image.yml
vendored
97
.github/workflows/docker-image.yml
vendored
@@ -26,7 +26,11 @@ on:
|
||||
description: "Tag the image"
|
||||
required: true
|
||||
default: "test"
|
||||
|
||||
nsjail:
|
||||
description: "Build nsjail image (true, false)"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -42,6 +46,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
@@ -90,7 +95,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -100,10 +105,11 @@ jobs:
|
||||
|
||||
build_ee:
|
||||
runs-on: ubicloud
|
||||
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee)
|
||||
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
@@ -152,7 +158,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -160,68 +166,6 @@ jobs:
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
build_ee_312:
|
||||
runs-on: ubicloud
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
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: Docker meta
|
||||
id: meta-ee-public-py312
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-py312
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Substitute EE code
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public-py312.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
# disabled until we make it 100% reliable and add more meaningful tests
|
||||
# playwright:
|
||||
# runs-on: [self-hosted, new]
|
||||
@@ -263,7 +207,6 @@ jobs:
|
||||
ARCH: amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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 }}:${{ env.DEV_SHA }}
|
||||
@@ -349,6 +292,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Prepare test run
|
||||
if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
|
||||
@@ -372,7 +316,7 @@ jobs:
|
||||
needs: [run_integration_test, build]
|
||||
if:
|
||||
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v'))
|
||||
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -392,7 +336,7 @@ jobs:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build_ee]
|
||||
if:
|
||||
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
|
||||
github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch') && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -412,7 +356,7 @@ jobs:
|
||||
verify_ee_image_vulnerabilities:
|
||||
runs-on: ubicloud
|
||||
needs: [tag_latest_ee]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -455,12 +399,13 @@ jobs:
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
if:
|
||||
(github.event_name != 'pull_request') && (github.event_name !=
|
||||
'workflow_dispatch')
|
||||
(github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
@@ -489,6 +434,11 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update Dockerfile image reference
|
||||
run: |
|
||||
sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail
|
||||
cat ./docker/DockerfileNsjail | grep "FROM"
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
@@ -505,7 +455,7 @@ jobs:
|
||||
build_ee_reports_privately:
|
||||
needs: [build_ee_nsjail]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -600,6 +550,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,4 +7,4 @@ CaddyfileRemoteMalo
|
||||
*.swp
|
||||
**/.idea/
|
||||
.direnv
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
186
CHANGELOG.md
186
CHANGELOG.md
@@ -1,5 +1,191 @@
|
||||
# Changelog
|
||||
|
||||
## [1.456.0](https://github.com/windmill-labs/windmill/compare/v1.455.2...v1.456.0) (2025-02-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* flow history picker for flow status + load last flow state ([611d5e8](https://github.com/windmill-labs/windmill/commit/611d5e8bf3d64106c3e9698687d2ec3710988517))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only start smtp servers if email domain is set ([6d9edc8](https://github.com/windmill-labs/windmill/commit/6d9edc8c2b01c99339c156dc84def13b0a2205a6))
|
||||
|
||||
## [1.455.2](https://github.com/windmill-labs/windmill/compare/v1.455.1...v1.455.2) (2025-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix worker restart on otel setting set from undefined to null ([#5183](https://github.com/windmill-labs/windmill/issues/5183)) ([940fb03](https://github.com/windmill-labs/windmill/commit/940fb030232ea7944db94bfdf8b30a96f30baa21))
|
||||
* improve autoscaling num workers inc ([2638dfc](https://github.com/windmill-labs/windmill/commit/2638dfcf00522af733c49b2c02a78ec4028623e0))
|
||||
* improve history and saved inputs rever mechanism ([0b0e564](https://github.com/windmill-labs/windmill/commit/0b0e5640d8fa9682ad0a4349c863b743321be1f4))
|
||||
|
||||
## [1.455.1](https://github.com/windmill-labs/windmill/compare/v1.455.0...v1.455.1) (2025-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pin malachite version ([b74d3fd](https://github.com/windmill-labs/windmill/commit/b74d3fd6e08ea991f7a7360b79d54a45a8ee58be))
|
||||
|
||||
## [1.455.0](https://github.com/windmill-labs/windmill/compare/v1.454.1...v1.455.0) (2025-01-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add --parallel option to push + display timings ([73dbb8f](https://github.com/windmill-labs/windmill/commit/73dbb8fb80f56942d35e5b5cda9715bba5d5c98f))
|
||||
* duckdb connection settings with azure blob storage ([#5180](https://github.com/windmill-labs/windmill/issues/5180)) ([8ba0f3a](https://github.com/windmill-labs/windmill/commit/8ba0f3addd0a02dba87c30739b44f524019a5e5a))
|
||||
* polars connection settings with azure blob storage ([#5175](https://github.com/windmill-labs/windmill/issues/5175)) ([8b65f1b](https://github.com/windmill-labs/windmill/commit/8b65f1bc3829d6b02bf38bf1449d3061ef62a08c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* capture panel never stopping + http saved config ([#5179](https://github.com/windmill-labs/windmill/issues/5179)) ([ff3339d](https://github.com/windmill-labs/windmill/commit/ff3339dc4e9ea4641b847fa5ef51bedcf6a3a429))
|
||||
* restart zombie job correctly handle concurrency limits ([#5181](https://github.com/windmill-labs/windmill/issues/5181)) ([8a93418](https://github.com/windmill-labs/windmill/commit/8a93418c25d180859bda76b9218e750e4b407fed))
|
||||
|
||||
## [1.454.1](https://github.com/windmill-labs/windmill/compare/v1.454.0...v1.454.1) (2025-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix codebase script execution ([b1155a9](https://github.com/windmill-labs/windmill/commit/b1155a9012c726c1b1c3f1a62f3aa96b8504681c))
|
||||
|
||||
## [1.454.0](https://github.com/windmill-labs/windmill/compare/v1.453.1...v1.454.0) (2025-01-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* history for flow inputs ([#5117](https://github.com/windmill-labs/windmill/issues/5117)) ([cd44056](https://github.com/windmill-labs/windmill/commit/cd440564d29aff39f6dc30ecbf0055696b07cf12))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix app form password handling ([4812c0c](https://github.com/windmill-labs/windmill/commit/4812c0cc9e46fdb5944f7010efdf617f2fd23cff))
|
||||
* improve codebase support ([c8cc9d2](https://github.com/windmill-labs/windmill/commit/c8cc9d2941dda2fedb959e1156c4f9b4f7dfab43))
|
||||
|
||||
## [1.453.1](https://github.com/windmill-labs/windmill/compare/v1.453.0...v1.453.1) (2025-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fetching job result (db schema) separatly if too big ([#5171](https://github.com/windmill-labs/windmill/issues/5171)) ([f29ee67](https://github.com/windmill-labs/windmill/commit/f29ee67d9d8547d7c4a79e14698b7d88a722e730))
|
||||
* **typescript-client:** fix typescript client mocked api behavior ([7f516f0](https://github.com/windmill-labs/windmill/commit/7f516f08682876f5a7ca409ec002cba6ec0c5c5b))
|
||||
|
||||
## [1.453.0](https://github.com/windmill-labs/windmill/compare/v1.452.1...v1.453.0) (2025-01-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* custom response headers ([#5156](https://github.com/windmill-labs/windmill/issues/5156)) ([5544d42](https://github.com/windmill-labs/windmill/commit/5544d42a2db652660051b5f2e05f8db0102400fa))
|
||||
* **python:** add `custom_wheels` directory to `PYTHONPATH` ([#5169](https://github.com/windmill-labs/windmill/issues/5169)) ([c60f8da](https://github.com/windmill-labs/windmill/commit/c60f8dacf191c5255ef8fca462d6d3d571b86b07))
|
||||
* websocket trigger allow returning messages ([#5168](https://github.com/windmill-labs/windmill/issues/5168)) ([487c273](https://github.com/windmill-labs/windmill/commit/487c273bfbc5bf1731947c922f32cd026cca47f9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* oracledb tag for native workers + client not working on arm systems ([#5162](https://github.com/windmill-labs/windmill/issues/5162)) ([a1579c1](https://github.com/windmill-labs/windmill/commit/a1579c1654a2ddf9f6fc8f05f62b37bb09303752))
|
||||
* **python:** fix uv can't find ssl certificates ([#5157](https://github.com/windmill-labs/windmill/issues/5157)) ([680fb18](https://github.com/windmill-labs/windmill/commit/680fb1852a192e384e23e2f5ceea473383f9f5aa))
|
||||
|
||||
## [1.452.1](https://github.com/windmill-labs/windmill/compare/v1.452.0...v1.452.1) (2025-01-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix broken tabs for apps ([eaf633c](https://github.com/windmill-labs/windmill/commit/eaf633ce7675bf86108ebfc81ed735c26031ad71))
|
||||
|
||||
## [1.452.0](https://github.com/windmill-labs/windmill/compare/v1.451.0...v1.452.0) (2025-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* mssql executor supports azure db redirect ([#5150](https://github.com/windmill-labs/windmill/issues/5150)) ([fdb7122](https://github.com/windmill-labs/windmill/commit/fdb712234eb7f1a9374012d51e1cb25ee5aaa410))
|
||||
* **postgres, trigger:** support ssl ([#5149](https://github.com/windmill-labs/windmill/issues/5149)) ([e0b6aa4](https://github.com/windmill-labs/windmill/commit/e0b6aa42a28a4273f3a7bb7465396f2756e60a92))
|
||||
* test trigger connection ([#5145](https://github.com/windmill-labs/windmill/issues/5145)) ([d17397b](https://github.com/windmill-labs/windmill/commit/d17397b0703fb6a7dabf9db75d3c30ec26cdbe1e))
|
||||
|
||||
## [1.451.0](https://github.com/windmill-labs/windmill/compare/v1.450.1...v1.451.0) (2025-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* app builder accordion list and accordion tab component ([#5132](https://github.com/windmill-labs/windmill/issues/5132)) ([0e41023](https://github.com/windmill-labs/windmill/commit/0e41023889fb2f26ef5dc2a736dd59b819d92bf1))
|
||||
* **typescript-client:** add ability to mock typescript api for testing ([f9ce01c](https://github.com/windmill-labs/windmill/commit/f9ce01c1dcda581d943e3cf58008cc6c89d39c5f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** make argInput indent optional ([#5144](https://github.com/windmill-labs/windmill/issues/5144)) ([b8152a3](https://github.com/windmill-labs/windmill/commit/b8152a3259fa0f791990e41d825f196d82c4084d))
|
||||
|
||||
## [1.450.1](https://github.com/windmill-labs/windmill/compare/v1.450.0...v1.450.1) (2025-01-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix SCIM/SAML settings persistence ([697a060](https://github.com/windmill-labs/windmill/commit/697a060e3e01a183b6ac2371eee319b01ee891cf))
|
||||
|
||||
## [1.450.0](https://github.com/windmill-labs/windmill/compare/v1.449.3...v1.450.0) (2025-01-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** improve codebase support + remove --stateful + warn when pushing stale metadata ([#5139](https://github.com/windmill-labs/windmill/issues/5139)) ([b47e9c1](https://github.com/windmill-labs/windmill/commit/b47e9c14f277339ae51fd3a40e00e8ba4e8a1390))
|
||||
|
||||
## [1.449.3](https://github.com/windmill-labs/windmill/compare/v1.449.2...v1.449.3) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include postgres triggers in build [#5137](https://github.com/windmill-labs/windmill/issues/5137) ([27a25d4](https://github.com/windmill-labs/windmill/commit/27a25d4af9ca0f0e0ed1d06a462fdd7cf966ce2b))
|
||||
* update tests to rust 1.83.0 ([f29c702](https://github.com/windmill-labs/windmill/commit/f29c70279f14c2cdbf6e330ae266060f81f32bb6))
|
||||
|
||||
## [1.449.2](https://github.com/windmill-labs/windmill/compare/v1.449.1...v1.449.2) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update rust to 1.83.0 ([#5135](https://github.com/windmill-labs/windmill/issues/5135)) ([5381c76](https://github.com/windmill-labs/windmill/commit/5381c76b7d194d75f58d70b29aa3f5f8589fa899))
|
||||
|
||||
## [1.449.1](https://github.com/windmill-labs/windmill/compare/v1.449.0...v1.449.1) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix python refetching wheels from S3 ([#5133](https://github.com/windmill-labs/windmill/issues/5133)) ([2f1804b](https://github.com/windmill-labs/windmill/commit/2f1804b6d0e6f3492608967f8937d5e34481661b))
|
||||
|
||||
## [1.449.0](https://github.com/windmill-labs/windmill/compare/v1.448.1...v1.449.0) (2025-01-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** operator visibility setting per workspace ([#5124](https://github.com/windmill-labs/windmill/issues/5124)) ([1a1ea68](https://github.com/windmill-labs/windmill/commit/1a1ea682166af62133d3df2816490e3ebc92462f))
|
||||
* msft teams support for critical alerts ([#5113](https://github.com/windmill-labs/windmill/issues/5113)) ([a88fbb2](https://github.com/windmill-labs/windmill/commit/a88fbb238ae21ca53bccd53ccfd3c77e2946fda2))
|
||||
* postgres triggers ([#4860](https://github.com/windmill-labs/windmill/issues/4860)) ([316a216](https://github.com/windmill-labs/windmill/commit/316a2167e7648052afb3d7456c5ab9324348e56c))
|
||||
* **python:** Multiple runtime versions ([#4579](https://github.com/windmill-labs/windmill/issues/4579)) ([e47dd69](https://github.com/windmill-labs/windmill/commit/e47dd697f96883f8f97bf469e69d3344f12d9633))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bun, windows:** Fix "A required privilege is not held by the client." ([#5126](https://github.com/windmill-labs/windmill/issues/5126)) ([74bb660](https://github.com/windmill-labs/windmill/commit/74bb660b9799e79e31c33d96a4f06d77094287d5))
|
||||
|
||||
## [1.448.1](https://github.com/windmill-labs/windmill/compare/v1.448.0...v1.448.1) (2025-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve environment variables config ([737e664](https://github.com/windmill-labs/windmill/commit/737e6647926bfad88ef28ddbfe91aa56d58569b8))
|
||||
|
||||
## [1.448.0](https://github.com/windmill-labs/windmill/compare/v1.447.6...v1.448.0) (2025-01-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Migrate to bun.lock ([#5112](https://github.com/windmill-labs/windmill/issues/5112)) ([fcfad69](https://github.com/windmill-labs/windmill/commit/fcfad69195a776d4958c035dca16100368966a66))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve workspace specific tags behavior ([669a1ff](https://github.com/windmill-labs/windmill/commit/669a1ff8e6ab782ce1168170db684deb5f0ec99a))
|
||||
* improve workspace specific tags behavior ([e81c2c9](https://github.com/windmill-labs/windmill/commit/e81c2c983d139cd24e983f6a76de56c10a694954))
|
||||
|
||||
## [1.447.6](https://github.com/windmill-labs/windmill/compare/v1.447.5...v1.447.6) (2025-01-21)
|
||||
|
||||
|
||||
|
||||
16
Dockerfile
16
Dockerfile
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.82-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.83-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm
|
||||
|
||||
FROM ${RUST_IMAGE} AS rust_base
|
||||
@@ -12,7 +12,7 @@ RUN apt-get -y update \
|
||||
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version ^0.1
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version 0.1.68
|
||||
RUN cargo install sccache --version ^0.8
|
||||
ENV RUSTC_WRAPPER=sccache SCCACHE_DIR=/backend/sccache
|
||||
|
||||
@@ -95,6 +95,14 @@ ARG WITH_KUBECTL=true
|
||||
ARG WITH_HELM=true
|
||||
ARG WITH_GIT=true
|
||||
|
||||
# To change latest stable version:
|
||||
# 1. Change placeholder in instanceSettings.ts
|
||||
# 2. Change LATEST_STABLE_PY in dockerfile
|
||||
# 3. Change #[default] annotation for PyVersion in backend
|
||||
ARG LATEST_STABLE_PY=3.11.10
|
||||
ENV UV_PYTHON_INSTALL_DIR=/tmp/windmill/cache/py_runtime
|
||||
ENV UV_PYTHON_PREFERENCE=only-managed
|
||||
|
||||
RUN pip install --upgrade pip==24.2
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -161,6 +169,10 @@ ENV GO_PATH=/usr/local/go/bin/go
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.15/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
# Preinstall python runtimes
|
||||
RUN uv python install 3.11.10
|
||||
RUN uv python install $LATEST_STABLE_PY
|
||||
|
||||
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
|
||||
RUN apt-get -y update && apt-get install -y curl procps nodejs awscli && apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@@ -5,5 +5,4 @@ oauth2.json
|
||||
tracing.folded
|
||||
heaptrack*
|
||||
index/
|
||||
windmill-api/openapi-*.*
|
||||
.docker.nu/
|
||||
windmill-api/openapi-*.*
|
||||
24
backend/.sqlx/query-01e92a4ba3074f1dce6ec98bc6c3fad4878f48db8c17c6d58590bd5df2e3350a.json
generated
Normal file
24
backend/.sqlx/query-01e92a4ba3074f1dce6ec98bc6c3fad4878f48db8c17c6d58590bd5df2e3350a.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE postgres_trigger \n SET \n server_id = $1, \n last_server_ping = now(),\n error = 'Connecting...'\n WHERE \n enabled IS TRUE \n AND workspace_id = $2 \n AND path = $3 \n AND (last_server_ping IS NULL \n OR last_server_ping < now() - INTERVAL '15 seconds'\n ) \n RETURNING true\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "01e92a4ba3074f1dce6ec98bc6c3fad4878f48db8c17c6d58590bd5df2e3350a"
|
||||
}
|
||||
20
backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json
generated
Normal file
20
backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, expires_at = $7",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324"
|
||||
}
|
||||
23
backend/.sqlx/query-124b27de35b49fbdb13a1f772044665a84325e34ae04bf2795fafb7bb6f2f0c6.json
generated
Normal file
23
backend/.sqlx/query-124b27de35b49fbdb13a1f772044665a84325e34ae04bf2795fafb7bb6f2f0c6.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO postgres_trigger (\n publication_name,\n replication_slot_name,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n postgres_resource_path, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7, \n $8, \n $9, \n $10\n )",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "124b27de35b49fbdb13a1f772044665a84325e34ae04bf2795fafb7bb6f2f0c6"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,6 +22,11 @@
|
||||
"ordinal": 3,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -33,8 +38,9 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736"
|
||||
"hash": "1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14"
|
||||
}
|
||||
@@ -127,6 +127,11 @@
|
||||
"ordinal": 24,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -159,6 +164,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
40
backend/.sqlx/query-199a76c04e3f0891ad09af27b9534bbabdd8703bfdf4d43df2c65e50d4ca2c85.json
generated
Normal file
40
backend/.sqlx/query-199a76c04e3f0891ad09af27b9534bbabdd8703bfdf4d43df2c65e50d4ca2c85.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n schemaname AS schema_name,\n tablename AS table_name,\n attnames AS columns,\n rowfilter AS where_clause\n FROM\n pg_publication_tables\n WHERE\n pubname = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "schema_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "table_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "columns",
|
||||
"type_info": "NameArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "where_clause",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "199a76c04e3f0891ad09af27b9534bbabdd8703bfdf4d43df2c65e50d4ca2c85"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "websocket_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "http_routes_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "kafka_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "nats_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3"
|
||||
}
|
||||
46
backend/.sqlx/query-24178c21aadc1aed90f31e9362c6505a642c8f04b883c278b07e7ef5956ce121.json
generated
Normal file
46
backend/.sqlx/query-24178c21aadc1aed90f31e9362c6505a642c8f04b883c278b07e7ef5956ce121.json
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\", \n \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\"\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "websocket_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "http_routes_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "kafka_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "nats_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "postgres_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "24178c21aadc1aed90f31e9362c6505a642c8f04b883c278b07e7ef5956ce121"
|
||||
}
|
||||
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SHOW WAL_LEVEL;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "wal_level",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48"
|
||||
}
|
||||
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT pubname AS publication_name FROM pg_publication;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "publication_name",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45"
|
||||
}
|
||||
34
backend/.sqlx/query-44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b.json
generated
Normal file
34
backend/.sqlx/query-44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value, is_secret, expires_at FROM variable WHERE path = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "is_secret",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b"
|
||||
}
|
||||
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n slot_name,\n active\n FROM\n pg_replication_slots \n WHERE \n plugin = 'pgoutput' AND\n slot_type = 'logical';\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "slot_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13"
|
||||
}
|
||||
15
backend/.sqlx/query-508d267b0d77fd12446654a502bf4968ecebec1614580e55de3d5895f0595e52.json
generated
Normal file
15
backend/.sqlx/query-508d267b0d77fd12446654a502bf4968ecebec1614580e55de3d5895f0595e52.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM postgres_trigger \n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "508d267b0d77fd12446654a502bf4968ecebec1614580e55de3d5895f0595e52"
|
||||
}
|
||||
@@ -127,6 +127,11 @@
|
||||
"ordinal": 24,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -159,6 +164,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
15
backend/.sqlx/query-597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3.json
generated
Normal file
15
backend/.sqlx/query-597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as \"payload!: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "payload: _",
|
||||
"name": "payload!: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
@@ -71,9 +71,9 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4"
|
||||
"hash": "5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $10 AND path = $11",
|
||||
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $11 AND path = $12",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,11 +14,12 @@
|
||||
"Jsonb",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915"
|
||||
"hash": "63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select path from script where hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
25
backend/.sqlx/query-833a4ecec12dfe67f28016a135ffe682b023d1868a182b7cac16ce799433c257.json
generated
Normal file
25
backend/.sqlx/query-833a4ecec12dfe67f28016a135ffe682b023d1868a182b7cac16ce799433c257.json
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE \n postgres_trigger\n SET \n last_server_ping = now(),\n error = $1\n WHERE\n workspace_id = $2\n AND path = $3\n AND server_id = $4 \n AND enabled IS TRUE\n RETURNING 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "833a4ecec12dfe67f28016a135ffe682b023d1868a182b7cac16ce799433c257"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "last_ping",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85"
|
||||
}
|
||||
20
backend/.sqlx/query-84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf.json
generated
Normal file
20
backend/.sqlx/query-84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM global_settings WHERE name = 'teams'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf"
|
||||
}
|
||||
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n puballtables AS all_table,\n pubinsert AS insert,\n pubupdate AS update,\n pubdelete AS delete\n FROM\n pg_publication\n WHERE\n pubname = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "all_table",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "insert",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "update",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "delete",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783"
|
||||
}
|
||||
24
backend/.sqlx/query-8e1afb488096330890b1675d2b3052d2064fcc8f373fecfebd40914768b2b1cf.json
generated
Normal file
24
backend/.sqlx/query-8e1afb488096330890b1675d2b3052d2064fcc8f373fecfebd40914768b2b1cf.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM postgres_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "8e1afb488096330890b1675d2b3052d2064fcc8f373fecfebd40914768b2b1cf"
|
||||
}
|
||||
23
backend/.sqlx/query-946977f0d525abf6267bf02e7a887434abd3e213b8c3c488166ca58fe3321147.json
generated
Normal file
23
backend/.sqlx/query-946977f0d525abf6267bf02e7a887434abd3e213b8c3c488166ca58fe3321147.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE postgres_trigger \n SET \n script_path = $1, \n path = $2, \n is_flow = $3, \n edited_by = $4, \n email = $5, \n postgres_resource_path = $6, \n replication_slot_name = $7,\n publication_name = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "946977f0d525abf6267bf02e7a887434abd3e213b8c3c488166ca58fe3321147"
|
||||
}
|
||||
15
backend/.sqlx/query-95e420b60fba20b36b2c6675998587d8cad3b67d4dfa9de52777d4ea9490b6b7.json
generated
Normal file
15
backend/.sqlx/query-95e420b60fba20b36b2c6675998587d8cad3b67d4dfa9de52777d4ea9490b6b7.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE \n postgres_trigger \n SET\n last_server_ping = NULL \n WHERE \n workspace_id = $1 \n AND path = $2 \n AND server_id IS NULL",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "95e420b60fba20b36b2c6675998587d8cad3b67d4dfa9de52777d4ea9490b6b7"
|
||||
}
|
||||
34
backend/.sqlx/query-9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357.json
generated
Normal file
34
backend/.sqlx/query-9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH zombie_jobs AS (\n UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false \n RETURNING id, workspace_id, last_ping\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id, workspace_id, last_ping FROM zombie_jobs",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "last_ping",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357"
|
||||
}
|
||||
14
backend/.sqlx/query-9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14.json
generated
Normal file
14
backend/.sqlx/query-9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO global_settings (name, value)\n VALUES ('teams', $1)\n ON CONFLICT (name) DO UPDATE SET value = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14"
|
||||
}
|
||||
104
backend/.sqlx/query-9cb31818d4db8a0e294884ab3dec08bfc262f99c875bf16c25bfb5e987efe978.json
generated
Normal file
104
backend/.sqlx/query-9cb31818d4db8a0e294884ab3dec08bfc262f99c875bf16c25bfb5e987efe978.json
generated
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n workspace_id,\n path,\n script_path,\n replication_slot_name,\n publication_name,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n postgres_resource_path\n FROM\n postgres_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "replication_slot_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "publication_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "postgres_resource_path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9cb31818d4db8a0e294884ab3dec08bfc262f99c875bf16c25bfb5e987efe978"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value, is_secret\n FROM variable\n WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT value, is_secret \n FROM variable \n WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9"
|
||||
"hash": "a5a03b9235b25bca359235f2e546197f02ca1cf898d7f2686419749b1cb0679e"
|
||||
}
|
||||
16
backend/.sqlx/query-a6c168c60bc8c42f70b18565e824efe29311aabfba6e09efa10bab6a551d658b.json
generated
Normal file
16
backend/.sqlx/query-a6c168c60bc8c42f70b18565e824efe29311aabfba6e09efa10bab6a551d658b.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE postgres_trigger SET enabled = FALSE, error = $1, server_id = NULL, last_server_ping = NULL WHERE workspace_id = $2 AND path = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a6c168c60bc8c42f70b18565e824efe29311aabfba6e09efa10bab6a551d658b"
|
||||
}
|
||||
23
backend/.sqlx/query-aae8699bbaa4d6111eabee715a6f4a3600c1ccfe6847bd526a751bc7baf825c5.json
generated
Normal file
23
backend/.sqlx/query-aae8699bbaa4d6111eabee715a6f4a3600c1ccfe6847bd526a751bc7baf825c5.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1 \n FROM postgres_trigger \n WHERE \n path = $1 AND \n workspace_id = $2\n )",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "aae8699bbaa4d6111eabee715a6f4a3600c1ccfe6847bd526a751bc7baf825c5"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase AS \"codebase: String\" FROM script WHERE hash = $1 LIMIT 1",
|
||||
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -52,8 +52,8 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "codebase: String",
|
||||
"type_info": "Varchar"
|
||||
"name": "use_tar",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -66,8 +66,8 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664"
|
||||
"hash": "d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799"
|
||||
}
|
||||
61
backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json
generated
Normal file
61
backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload!: _\", trigger_extra as \"trigger_extra: _\" FROM capture WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "trigger_kind: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "trigger_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"webhook",
|
||||
"http",
|
||||
"websocket",
|
||||
"kafka",
|
||||
"email",
|
||||
"nats"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "payload!: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "trigger_extra: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7"
|
||||
}
|
||||
23
backend/.sqlx/query-fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1.json
generated
Normal file
23
backend/.sqlx/query-fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_version.value->>'early_return' as early_return\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1"
|
||||
}
|
||||
107
backend/.sqlx/query-fcc11a9353ea101109aec30f8bdd4b2ce906fffc3c51e77d083121dbd68dadd4.json
generated
Normal file
107
backend/.sqlx/query-fcc11a9353ea101109aec30f8bdd4b2ce906fffc3c51e77d083121dbd68dadd4.json
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n replication_slot_name,\n publication_name,\n postgres_resource_path\n FROM \n postgres_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "server_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "last_server_ping",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "replication_slot_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "publication_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "postgres_resource_path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fcc11a9353ea101109aec30f8bdd4b2ce906fffc3c51e77d083121dbd68dadd4"
|
||||
}
|
||||
26
backend/.sqlx/query-fd1db7530acf3c84b2ab696504905a50d1ed4f69629c43de7d874769c340d909.json
generated
Normal file
26
backend/.sqlx/query-fd1db7530acf3c84b2ab696504905a50d1ed4f69629c43de7d874769c340d909.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE postgres_trigger \n SET \n enabled = $1, \n email = $2, \n edited_by = $3, \n edited_at = now(), \n server_id = NULL, \n error = NULL\n WHERE \n path = $4 AND \n workspace_id = $5 \n RETURNING 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fd1db7530acf3c84b2ab696504905a50d1ed4f69629c43de7d874769c340d909"
|
||||
}
|
||||
12
backend/.sqlx/query-fe58ca3cef78c73867d309156df2b74f7127bf6cba25c9a0c5ca5eedd6a19aff.json
generated
Normal file
12
backend/.sqlx/query-fe58ca3cef78c73867d309156df2b74f7127bf6cba25c9a0c5ca5eedd6a19aff.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM _sqlx_migrations WHERE version=20250131115248",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fe58ca3cef78c73867d309156df2b74f7127bf6cba25c9a0c5ca5eedd6a19aff"
|
||||
}
|
||||
12
backend/.vscode/settings.json
vendored
12
backend/.vscode/settings.json
vendored
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"rust-analyzer.linkedProjects": ["./windmill-common/Cargo.toml"],
|
||||
"rust-analyzer.showUnlinkedFileNotification": false
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
"remote.portsAttributes": {
|
||||
"8000": {
|
||||
"label": "Development Server",
|
||||
"onAutoForward": "openPreview"
|
||||
}
|
||||
},
|
||||
"remote.autoForwardPorts": true,
|
||||
"conventionalCommits.scopes": [
|
||||
"restructring triggers, decoding trigger message on work"
|
||||
],
|
||||
}
|
||||
|
||||
523
backend/Cargo.lock
generated
523
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.447.6"
|
||||
version = "1.456.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -30,7 +30,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.447.6"
|
||||
version = "1.456.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -61,7 +61,7 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud"]
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "enterprise", "parquet"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:deno_core"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"]
|
||||
kafka = ["windmill-api/kafka"]
|
||||
nats = ["windmill-api/nats"]
|
||||
otel = ["windmill-common/otel", "windmill-worker/otel"]
|
||||
@@ -73,6 +73,7 @@ oracledb = ["windmill-worker/oracledb"]
|
||||
mssql = ["windmill-worker/mssql"]
|
||||
bigquery = ["windmill-worker/bigquery"]
|
||||
websocket = ["windmill-api/websocket"]
|
||||
postgres_trigger = ["windmill-api/postgres_trigger"]
|
||||
python = ["windmill-worker/python"]
|
||||
smtp = ["windmill-api/smtp", "windmill-common/smtp"]
|
||||
csharp = ["windmill-worker/csharp"]
|
||||
@@ -114,6 +115,9 @@ serde.workspace = true
|
||||
deno_core = { workspace = true, optional = true }
|
||||
object_store = { workspace = true, optional = true }
|
||||
quote.workspace = true
|
||||
memchr.workspace = true
|
||||
v8 = { workspace = true, optional = true }
|
||||
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { optional = true, workspace = true }
|
||||
@@ -130,6 +134,7 @@ windmill-api-client.workspace = true
|
||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] }
|
||||
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
windmill-api = { path = "./windmill-api", default-features = false }
|
||||
windmill-queue = { path = "./windmill-queue" }
|
||||
@@ -154,6 +159,8 @@ windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
|
||||
v8 = "=130.0.7" # Exact version
|
||||
memchr = "2.7.4"
|
||||
axum = { version = "^0.7", features = ["multipart"] }
|
||||
headers = "^0"
|
||||
hyper = { version = "^1", features = ["full"] }
|
||||
@@ -184,6 +191,8 @@ rand = "^0"
|
||||
rand_core = { version = "^0", features = ["std"] }
|
||||
magic-crypt = "^3"
|
||||
git-version = "^0"
|
||||
malachite = "=0.4.18"
|
||||
malachite-bigint = "=0.2.0"
|
||||
rustpython-parser = "^0"
|
||||
php-parser-rs = { git = "https://github.com/php-rust-tools/parser", rev = "ec4cb411dec09450946ef57920b7ffced7f6495d" }
|
||||
cron = "^0"
|
||||
@@ -234,7 +243,7 @@ sqlx = { version = "0.8.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"bigdecimal"
|
||||
] }
|
||||
bigdecimal = "^0"
|
||||
bigdecimal = {version = "^0"}
|
||||
dotenv = "^0"
|
||||
ulid = { version = "^1", features = ["uuid"] }
|
||||
futures = "^0"
|
||||
@@ -260,6 +269,8 @@ wasm-bindgen-test = "0.3.42"
|
||||
convert_case = "0.6.0"
|
||||
getrandom = "0.2"
|
||||
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
|
||||
rust-postgres = { package = "tokio-postgres", git = "https://github.com/imor/rust-postgres", rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b"}
|
||||
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/imor/rust-postgres", features = ["runtime"], rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b" }
|
||||
bit-vec = "=0.6.3"
|
||||
mappable-rc = "^0"
|
||||
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]}
|
||||
@@ -269,7 +280,7 @@ native-tls = "^0"
|
||||
# samael = { git="https://github.com/njaremko/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = ["xmlsec"] }
|
||||
samael = { version="0.0.14", features = ["xmlsec"] }
|
||||
gcp_auth = "0.9.0"
|
||||
rust_decimal = { version = "^1", features = ["db-postgres"]}
|
||||
rust_decimal = { version = "^1", features = ["db-postgres", "serde-float"]}
|
||||
jsonwebtoken = "8.3.0"
|
||||
pem = "3.0.1"
|
||||
nix = { version = "0.27.1", features = ["process", "signal"] }
|
||||
@@ -287,6 +298,7 @@ openssl = "=0.10"
|
||||
mail-parser = "^0"
|
||||
matchit = "=0.7.3"
|
||||
rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored"] }
|
||||
pg_escape = "0.1.1"
|
||||
async-nats = "0.38.0"
|
||||
nkeys = "0.4.4"
|
||||
|
||||
@@ -311,6 +323,7 @@ opentelemetry-semantic-conventions = { version = "*", features = ["semconv_exper
|
||||
bollard = "0.18.1"
|
||||
|
||||
tonic = { version = "^0", features = ["tls-native-roots"] }
|
||||
byteorder = "1.5.0"
|
||||
|
||||
tikv-jemallocator = { version = "0.5" }
|
||||
tikv-jemalloc-sys = { version = "^0.5" }
|
||||
|
||||
@@ -1 +1 @@
|
||||
ddcfdfc18a9833a5fc4e62ad62a265ef1e06a0aa
|
||||
15ddca63acd769e4e7355269a239639c50989def
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE IF EXISTS postgres_trigger;
|
||||
69
backend/migrations/20241123152203_postgres_triggers.up.sql
Normal file
69
backend/migrations/20241123152203_postgres_triggers.up.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE postgres_trigger(
|
||||
path VARCHAR(255) NOT NULL,
|
||||
script_path VARCHAR(255) NOT NULL,
|
||||
is_flow BOOLEAN NOT NULL,
|
||||
workspace_id VARCHAR(50) NOT NULL,
|
||||
edited_by VARCHAR(50) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
extra_perms JSONB NULL,
|
||||
postgres_resource_path VARCHAR(255) NOT NULL,
|
||||
error TEXT NULL,
|
||||
server_id VARCHAR(50) NULL,
|
||||
last_server_ping TIMESTAMPTZ NULL,
|
||||
replication_slot_name VARCHAR(255) NOT NULL,
|
||||
publication_name VARCHAR(255) NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
CONSTRAINT PK_postgres_trigger PRIMARY KEY (path,workspace_id),
|
||||
CONSTRAINT fk_postgres_trigger_workspace FOREIGN KEY (workspace_id)
|
||||
REFERENCES workspace(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT ALL ON postgres_trigger TO windmill_user;
|
||||
GRANT ALL ON postgres_trigger TO windmill_admin;
|
||||
|
||||
ALTER TABLE postgres_trigger ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY admin_policy ON postgres_trigger FOR ALL TO windmill_admin USING (true);
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_select ON postgres_trigger FOR SELECT TO windmill_user
|
||||
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_insert ON postgres_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_update ON postgres_trigger FOR UPDATE TO windmill_user
|
||||
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_delete ON postgres_trigger FOR DELETE TO windmill_user
|
||||
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_own ON postgres_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'u' AND SPLIT_PART(postgres_trigger.path, '/', 2) = current_setting('session.user'));
|
||||
CREATE POLICY see_member ON postgres_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'g' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_select ON postgres_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
|
||||
CREATE POLICY see_extra_perms_user_insert ON postgres_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_update ON postgres_trigger FOR UPDATE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_delete ON postgres_trigger FOR DELETE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_select ON postgres_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
|
||||
CREATE POLICY see_extra_perms_groups_insert ON postgres_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_update ON postgres_trigger FOR UPDATE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_delete ON postgres_trigger FOR DELETE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
@@ -0,0 +1 @@
|
||||
DELETE FROM global_settings WHERE name = 'teams';
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO global_settings (name, value) VALUES ('teams', '{}');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE workspace_settings DROP COLUMN operator_settings;
|
||||
11
backend/migrations/20250122211942_operator_settings.up.sql
Normal file
11
backend/migrations/20250122211942_operator_settings.up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE workspace_settings ADD COLUMN operator_settings JSONB DEFAULT '{
|
||||
"runs": true,
|
||||
"groups": true,
|
||||
"folders": true,
|
||||
"workers": true,
|
||||
"triggers": true,
|
||||
"resources": true,
|
||||
"schedules": true,
|
||||
"variables": true,
|
||||
"audit_logs": true
|
||||
}';
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cloud_workspace_settings DROP COLUMN last_warning_sent;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cloud_workspace_settings ADD COLUMN last_warning_sent TIMESTAMP NULL;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["oracledb"]'::jsonb) where name = 'worker__native' and config @> '{"worker_tags": ["nativets", "postgresql", "mysql", "graphql", "snowflake", "bigquery", "mssql"]}'::jsonb AND NOT config->'worker_tags' @> '"oracledb"'::jsonb;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE websocket_trigger DROP COLUMN can_return_message;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE websocket_trigger ADD COLUMN can_return_message BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -0,0 +1 @@
|
||||
DELETE FROM global_settings WHERE name = 'otel';
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO global_settings (name, value)
|
||||
VALUES ('otel', '{}')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::{
|
||||
alloc::{self, Layout},
|
||||
ffi::{c_char, c_int, c_void},
|
||||
mem::align_of,
|
||||
ptr,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/* -------------------------------- stdlib.h -------------------------------- */
|
||||
@@ -71,7 +71,6 @@ pub unsafe extern "C" fn free(buf: *mut c_void) {
|
||||
alloc::dealloc(buf, layout);
|
||||
}
|
||||
|
||||
|
||||
// In all these allocations, we store the layout before the data for later retrieval.
|
||||
// This is because we need to know the layout when deallocating the memory.
|
||||
// Here are some helper methods for that:
|
||||
|
||||
@@ -18,6 +18,8 @@ regex.workspace = true
|
||||
windmill-parser.workspace = true
|
||||
windmill-common.workspace = true
|
||||
rustpython-parser.workspace = true
|
||||
malachite.workspace = true
|
||||
malachite-bigint.workspace = true
|
||||
phf.workspace = true
|
||||
itertools.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -21,7 +21,7 @@ use rustpython_parser::{
|
||||
Parse,
|
||||
};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::error;
|
||||
use windmill_common::{error, worker::PythonAnnotations};
|
||||
|
||||
const DEF_MAIN: &str = "def main(";
|
||||
|
||||
@@ -37,6 +37,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
|
||||
"smb" => "pysmb",
|
||||
"PIL" => "Pillow",
|
||||
"googleapiclient" => "google-api-python-client",
|
||||
"googlecloudbigquery" => "google-cloud-bigquery",
|
||||
"dateutil" => "python-dateutil",
|
||||
"mailparser" => "mail-parser",
|
||||
"mailparser-reply" => "mail-parser-reply",
|
||||
@@ -171,14 +172,75 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<String>>
|
||||
return Ok(nimports);
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
pub async fn parse_python_imports(
|
||||
code: &str,
|
||||
w_id: &str,
|
||||
path: &str,
|
||||
db: &Pool<Postgres>,
|
||||
already_visited: &mut Vec<String>,
|
||||
annotated_pyv_numeric: &mut Option<u32>,
|
||||
) -> error::Result<Vec<String>> {
|
||||
parse_python_imports_inner(
|
||||
code,
|
||||
w_id,
|
||||
path,
|
||||
db,
|
||||
already_visited,
|
||||
annotated_pyv_numeric,
|
||||
&mut annotated_pyv_numeric.and_then(|_| Some(path.to_owned())),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn parse_python_imports_inner(
|
||||
code: &str,
|
||||
w_id: &str,
|
||||
path: &str,
|
||||
db: &Pool<Postgres>,
|
||||
already_visited: &mut Vec<String>,
|
||||
annotated_pyv_numeric: &mut Option<u32>,
|
||||
path_where_annotated_pyv: &mut Option<String>,
|
||||
) -> error::Result<Vec<String>> {
|
||||
let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code);
|
||||
|
||||
// we pass only if there is none or only one annotation
|
||||
|
||||
// Naive:
|
||||
// 1. Check if there are multiple annotated version
|
||||
// 2. If no, take one and compare with annotated version
|
||||
// 3. We continue if same or replace none with new one
|
||||
|
||||
// Optimized:
|
||||
// 1. Iterate over all annotations compare each with annotated_pyv and replace on flight
|
||||
// 2. If annotated_pyv is different version, throw and error
|
||||
|
||||
// This way we make sure there is no multiple annotations for same script
|
||||
// and we get detailed span on conflicting versions
|
||||
|
||||
let mut check = |is_py_xyz, numeric| -> error::Result<()> {
|
||||
if is_py_xyz {
|
||||
if let Some(v) = annotated_pyv_numeric {
|
||||
if *v != numeric {
|
||||
return Err(error::Error::from(anyhow::anyhow!(
|
||||
"Annotated 2 or more different python versions: \n - py{v} at {}\n - py{numeric} at {path}\nIt is possible to use only one.",
|
||||
path_where_annotated_pyv.clone().unwrap_or("Unknown".to_owned())
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
*annotated_pyv_numeric = Some(numeric);
|
||||
}
|
||||
|
||||
*path_where_annotated_pyv = Some(path.to_owned());
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
check(py310, 310)?;
|
||||
check(py311, 311)?;
|
||||
check(py312, 312)?;
|
||||
check(py313, 313)?;
|
||||
|
||||
let find_requirements = code
|
||||
.lines()
|
||||
.find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:"));
|
||||
@@ -225,11 +287,21 @@ pub async fn parse_python_imports(
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
|
||||
if already_visited.contains(&rpath) {
|
||||
vec![]
|
||||
} else {
|
||||
already_visited.push(rpath.clone());
|
||||
parse_python_imports(&code, w_id, &rpath, db, already_visited).await?
|
||||
parse_python_imports_inner(
|
||||
&code,
|
||||
w_id,
|
||||
&rpath,
|
||||
db,
|
||||
already_visited,
|
||||
annotated_pyv_numeric,
|
||||
path_where_annotated_pyv,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
vec![replace_import(n.to_string())]
|
||||
|
||||
@@ -25,6 +25,7 @@ def main():
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
// println!("{}", serde_json::to_string(&r)?);
|
||||
@@ -57,6 +58,7 @@ def main():
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
@@ -87,6 +89,7 @@ def main():
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
|
||||
95
backend/parsers/windmill-parser-wasm/flake.lock
generated
Normal file
95
backend/parsers/windmill-parser-wasm/flake.lock
generated
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1737885589,
|
||||
"narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1736320768,
|
||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1738117527,
|
||||
"narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "6a3dc6ce4132bd57359214d986db376f2333c14d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
37
backend/parsers/windmill-parser-wasm/flake.nix
Normal file
37
backend/parsers/windmill-parser-wasm/flake.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
rust-overlay,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [(import rust-overlay)];
|
||||
};
|
||||
rust = pkgs.rust-bin.nightly.latest.default.override {
|
||||
extensions = [
|
||||
"rust-src"
|
||||
];
|
||||
targets = ["wasm32-unknown-unknown"];
|
||||
};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rust
|
||||
nodejs
|
||||
wasm-pack
|
||||
sccache
|
||||
];
|
||||
RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache";
|
||||
CARGO_PATH = "${rust}/bin/cargo";
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -9,20 +9,19 @@
|
||||
use anyhow::Context;
|
||||
use monitor::{
|
||||
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config,
|
||||
reload_nuget_config_setting, reload_timeout_wait_result_setting,
|
||||
send_current_log_file_to_object_store, send_logs_to_object_store,
|
||||
reload_instance_python_version_setting, reload_nuget_config_setting,
|
||||
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
|
||||
send_logs_to_object_store,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sqlx::{postgres::PgListener, Pool, Postgres};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{create_dir_all, DirBuilder},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{
|
||||
fs::{create_dir_all, DirBuilder, File},
|
||||
io::AsyncReadExt,
|
||||
};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use uuid::Uuid;
|
||||
use windmill_api::HTTP_CLIENT;
|
||||
|
||||
@@ -33,19 +32,20 @@ use windmill_common::{
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
|
||||
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING,
|
||||
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
|
||||
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_ee::schedule_stats,
|
||||
utils::{hostname, rd_string, Mode, GIT_VERSION},
|
||||
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, WORKER_GROUP},
|
||||
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP},
|
||||
DB, METRICS_ENABLED,
|
||||
};
|
||||
|
||||
@@ -69,8 +69,9 @@ use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR, PY311_CACHE_DIR, RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY311_CACHE_DIR,
|
||||
TMP_LOGS_DIR, UV_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR,
|
||||
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR,
|
||||
TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -137,8 +138,8 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
)
|
||||
})?;
|
||||
|
||||
create_dir_all(HUB_CACHE_DIR).await?;
|
||||
create_dir_all(BUN_BUNDLE_CACHE_DIR).await?;
|
||||
create_dir_all(HUB_CACHE_DIR)?;
|
||||
create_dir_all(BUN_BUNDLE_CACHE_DIR)?;
|
||||
|
||||
for path in paths.values() {
|
||||
tracing::info!("Caching hub script at {path}");
|
||||
@@ -149,7 +150,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
.is_some_and(|x| x == &ScriptLang::Deno)
|
||||
{
|
||||
let job_dir = format!("{}/cache_init/{}", TMP_DIR, Uuid::new_v4());
|
||||
create_dir_all(&job_dir).await?;
|
||||
create_dir_all(&job_dir)?;
|
||||
let _ = windmill_worker::generate_deno_lock(
|
||||
&Uuid::nil(),
|
||||
&res.content,
|
||||
@@ -167,7 +168,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
} else if res.language.as_ref().is_some_and(|x| x == &ScriptLang::Bun) {
|
||||
let job_id = Uuid::new_v4();
|
||||
let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id);
|
||||
create_dir_all(&job_dir).await?;
|
||||
create_dir_all(&job_dir)?;
|
||||
if let Some(lockfile) = res.lockfile {
|
||||
let _ = windmill_worker::prepare_job_dir(&lockfile, &job_dir).await?;
|
||||
let envs = windmill_worker::get_common_bun_proc_envs(None).await;
|
||||
@@ -502,7 +503,6 @@ Windmill Community Edition {GIT_VERSION}
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create("/tmp/windmill")
|
||||
.await
|
||||
.expect("could not create initial server dir");
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
@@ -734,6 +734,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
SMTP_SETTING => {
|
||||
reload_smtp_config(&db).await;
|
||||
},
|
||||
TEAMS_SETTING => {
|
||||
tracing::info!("Teams setting changed.");
|
||||
},
|
||||
INDEXER_SETTING => {
|
||||
reload_indexer_config(&db).await;
|
||||
},
|
||||
@@ -762,6 +765,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
PIP_INDEX_URL_SETTING => {
|
||||
reload_pip_index_url_setting(&db).await
|
||||
},
|
||||
INSTANCE_PYTHON_VERSION_SETTING => {
|
||||
reload_instance_python_version_setting(&db).await
|
||||
},
|
||||
NPM_CONFIG_REGISTRY_SETTING => {
|
||||
reload_npm_config_registry_setting(&db).await
|
||||
},
|
||||
@@ -779,11 +785,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
},
|
||||
EXPOSE_METRICS_SETTING => {
|
||||
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");
|
||||
send_delayed_killpill(&tx, 40, "metrics setting change").await;
|
||||
},
|
||||
EMAIL_DOMAIN_SETTING => {
|
||||
tracing::info!("Email domain setting changed");
|
||||
if server_mode {
|
||||
send_delayed_killpill(&tx, 4, "email domain setting change").await;
|
||||
}
|
||||
},
|
||||
EXPOSE_DEBUG_METRICS_SETTING => {
|
||||
@@ -793,29 +800,17 @@ Windmill Community Edition {GIT_VERSION}
|
||||
},
|
||||
OTEL_SETTING => {
|
||||
tracing::info!("OTEL 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..4);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill");
|
||||
}
|
||||
send_delayed_killpill(&tx, 4, "OTEL setting change").await;
|
||||
},
|
||||
REQUEST_SIZE_LIMIT_SETTING => {
|
||||
if server_mode {
|
||||
tracing::info!("Request limit size change detected, killing server expecting to be restarted");
|
||||
// we wait a bit randomly to avoid having all servers 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");
|
||||
}
|
||||
send_delayed_killpill(&tx, 4, "request size limit change").await;
|
||||
}
|
||||
},
|
||||
SAML_METADATA_SETTING => {
|
||||
tracing::info!("SAML metadata change detected, killing server expecting to be restarted");
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
}
|
||||
send_delayed_killpill(&tx, 0, "SAML metadata change").await;
|
||||
},
|
||||
HUB_BASE_URL_SETTING => {
|
||||
if let Err(e) = reload_hub_base_url_setting(&db, server_mode).await {
|
||||
@@ -1013,12 +1008,18 @@ pub async fn run_workers(
|
||||
TMP_LOGS_DIR,
|
||||
UV_CACHE_DIR,
|
||||
TAR_PIP_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR,
|
||||
DENO_CACHE_DIR,
|
||||
DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM,
|
||||
BUN_CACHE_DIR,
|
||||
PY310_CACHE_DIR,
|
||||
PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR,
|
||||
PY313_CACHE_DIR,
|
||||
TAR_PY310_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR,
|
||||
TAR_PY312_CACHE_DIR,
|
||||
TAR_PY313_CACHE_DIR,
|
||||
PIP_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR,
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
@@ -1032,7 +1033,6 @@ pub async fn run_workers(
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(x)
|
||||
.await
|
||||
.expect("could not create initial worker dir");
|
||||
}
|
||||
|
||||
@@ -1083,3 +1083,18 @@ pub async fn run_workers(
|
||||
futures::future::try_join_all(handles).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delayed_killpill(
|
||||
tx: &tokio::sync::broadcast::Sender<()>,
|
||||
max_delay_secs: u64,
|
||||
context: &str,
|
||||
) {
|
||||
// Random delay to avoid all servers/workers shutting down simultaneously
|
||||
let rd_delay = rand::rng().random_range(0..max_delay_secs);
|
||||
tracing::info!("Scheduling {context} shutdown in {rd_delay}s");
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill for {context}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ use windmill_common::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
NUGET_CONFIG_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
@@ -68,8 +68,8 @@ use windmill_common::{
|
||||
use windmill_queue::cancel_job;
|
||||
use windmill_worker::{
|
||||
create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender,
|
||||
SendResult, BUNFIG_INSTALL_SCOPES, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, NPM_CONFIG_REGISTRY,
|
||||
NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
|
||||
SendResult, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR,
|
||||
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -198,6 +198,7 @@ pub async fn initial_load(
|
||||
reload_pip_index_url_setting(&db).await;
|
||||
reload_npm_config_registry_setting(&db).await;
|
||||
reload_bunfig_install_scopes_setting(&db).await;
|
||||
reload_instance_python_version_setting(&db).await;
|
||||
reload_nuget_config_setting(&db).await;
|
||||
}
|
||||
}
|
||||
@@ -908,6 +909,16 @@ pub async fn reload_pip_index_url_setting(db: &DB) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_instance_python_version_setting(db: &DB) {
|
||||
reload_option_setting_with_tracing(
|
||||
db,
|
||||
INSTANCE_PYTHON_VERSION_SETTING,
|
||||
"INSTANCE_PYTHON_VERSION",
|
||||
INSTANCE_PYTHON_VERSION.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_npm_config_registry_setting(db: &DB) {
|
||||
reload_option_setting_with_tracing(
|
||||
db,
|
||||
@@ -1083,7 +1094,7 @@ pub async fn reload_option_setting_with_tracing<T: FromStr + DeserializeOwned>(
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_value_from_global_settings(
|
||||
pub async fn load_value_from_global_settings(
|
||||
db: &DB,
|
||||
setting_name: &str,
|
||||
) -> error::Result<Option<serde_json::Value>> {
|
||||
@@ -1096,6 +1107,7 @@ async fn load_value_from_global_settings(
|
||||
.map(|x| x.value);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
|
||||
db: &DB,
|
||||
setting_name: &str,
|
||||
@@ -1479,7 +1491,7 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> {
|
||||
#[cfg(feature = "oauth2")]
|
||||
{
|
||||
let mut l = windmill_api::OAUTH_CLIENTS.write().await;
|
||||
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths)
|
||||
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths, db).await
|
||||
.map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e))
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1495,9 +1507,20 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> {
|
||||
async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker_name: &str) {
|
||||
if *RESTART_ZOMBIE_JOBS {
|
||||
let restarted = sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null
|
||||
WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
|
||||
"WITH zombie_jobs AS (
|
||||
UPDATE queue SET running = false, started_at = null
|
||||
WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false
|
||||
RETURNING id, workspace_id, last_ping
|
||||
),
|
||||
update_concurrency AS (
|
||||
UPDATE concurrency_counter cc
|
||||
SET job_uuids = job_uuids - zj.id::text
|
||||
FROM zombie_jobs zj
|
||||
INNER JOIN concurrency_key ck ON ck.job_id = zj.id
|
||||
WHERE cc.concurrency_id = ck.key
|
||||
)
|
||||
SELECT id, workspace_id, last_ping FROM zombie_jobs",
|
||||
*ZOMBIE_JOB_TIMEOUT,
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -1641,12 +1664,31 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
|
||||
tracing::error!(error_message);
|
||||
report_critical_error(error_message, db.clone(), Some(&flow.workspace_id), None).await;
|
||||
// if the flow hasn't started and is a zombie, we can simply restart it
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let concurrency_key =
|
||||
sqlx::query_scalar!("SELECT key FROM concurrency_key WHERE job_id = $1", flow.id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if let Some(key) = concurrency_key {
|
||||
sqlx::query!(
|
||||
"UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1",
|
||||
key,
|
||||
flow.id.hyphenated().to_string()
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false",
|
||||
flow.id
|
||||
)
|
||||
.execute(db)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
} else {
|
||||
let id = flow.id.clone();
|
||||
let last_ping = flow.last_ping.clone();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::future::Future;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use windmill_api_client::types::{NewScript, NewScriptLanguage};
|
||||
use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use chrono::Timelike;
|
||||
|
||||
@@ -28,6 +28,7 @@ zip = ["dep:async_zip"]
|
||||
oauth2 = ["dep:async-oauth2"]
|
||||
http_trigger = ["dep:matchit"]
|
||||
static_frontend = ["dep:rust-embed"]
|
||||
postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -107,8 +108,13 @@ rdkafka = { workspace = true, optional = true }
|
||||
async-nats = { workspace = true, optional = true }
|
||||
nkeys = { workspace = true, optional = true }
|
||||
const_format.workspace = true
|
||||
|
||||
pin-project.workspace = true
|
||||
http.workspace = true
|
||||
async-stream.workspace = true
|
||||
ulid.workspace = true
|
||||
rust-postgres = { workspace = true, optional = true }
|
||||
pg_escape = { workspace = true, optional = true }
|
||||
byteorder = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true, optional = true }
|
||||
rust_decimal = { workspace = true, optional = true }
|
||||
rust-postgres-native-tls = { workspace = true, optional = true}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
variables::decrypt,
|
||||
variables::get_variable_or_self,
|
||||
};
|
||||
|
||||
use anthropic::AnthropicCache;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
@@ -19,7 +20,6 @@ use serde::{Deserialize, Deserializer};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::error::{to_anyhow, Result};
|
||||
use windmill_common::variables::build_crypt;
|
||||
|
||||
use windmill_common::error::Error;
|
||||
|
||||
@@ -346,38 +346,11 @@ lazy_static! {
|
||||
pub static ref AI_KEY_CACHE: Cache<String, AiCache> = Cache::new(500);
|
||||
}
|
||||
|
||||
struct Variable {
|
||||
value: String,
|
||||
is_secret: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ProxyQueryParams {
|
||||
no_cache: Option<bool>,
|
||||
}
|
||||
|
||||
async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<String> {
|
||||
if !path.starts_with("$var:") {
|
||||
return Ok(path);
|
||||
}
|
||||
let path = path.strip_prefix("$var:").unwrap().to_string();
|
||||
let mut variable = sqlx::query_as!(
|
||||
Variable,
|
||||
"SELECT value, is_secret
|
||||
FROM variable
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if variable.is_secret {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
variable.value = decrypt(&mc, variable.value)?;
|
||||
}
|
||||
Ok(variable.value)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AiResource {
|
||||
pub path: String,
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::{
|
||||
resources::get_resource_value_interpolated_internal,
|
||||
users::{require_owner_of_path, OptAuthed},
|
||||
utils::WithStarredInfoQuery,
|
||||
variables::encrypt,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
@@ -54,6 +53,7 @@ use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
use windmill_common::variables::encrypt;
|
||||
use windmill_common::{
|
||||
apps::{AppScriptId, ListAppQuery},
|
||||
cache::{self, future::FutureCachedExt},
|
||||
|
||||
@@ -35,9 +35,9 @@ use windmill_queue::{PushArgs, PushArgsOwned};
|
||||
#[cfg(feature = "http_trigger")]
|
||||
use crate::http_triggers::{build_http_trigger_extra, HttpMethod};
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
use crate::kafka_triggers_ee::KafkaResourceSecurity;
|
||||
use crate::kafka_triggers_ee::KafkaTriggerConfigConnection;
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
use crate::nats_triggers_ee::NatsResourceAuth;
|
||||
use crate::nats_triggers_ee::NatsTriggerConfigConnection;
|
||||
use crate::{
|
||||
args::WebhookArgs,
|
||||
db::{ApiAuthed, DB},
|
||||
@@ -56,6 +56,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_configs/:runnable_kind/*path", get(get_configs))
|
||||
.route("/list/:runnable_kind/*path", get(list_captures))
|
||||
.route("/:id", delete(delete_capture))
|
||||
.route("/:id", get(get_capture))
|
||||
}
|
||||
|
||||
pub fn workspaced_unauthed_service() -> Router {
|
||||
@@ -110,14 +111,6 @@ struct HttpTriggerConfig {
|
||||
http_method: HttpMethod,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum KafkaTriggerConfigConnection {
|
||||
Resource { kafka_resource_path: String },
|
||||
Static { brokers: Vec<String>, security: KafkaResourceSecurity },
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct KafkaTriggerConfig {
|
||||
@@ -127,14 +120,6 @@ pub struct KafkaTriggerConfig {
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum NatsTriggerConfigConnection {
|
||||
Resource { nats_resource_path: String },
|
||||
Static { servers: Vec<String>, auth: NatsResourceAuth, require_tls: bool },
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NatsTriggerConfig {
|
||||
@@ -296,7 +281,7 @@ async fn list_captures(
|
||||
|
||||
let captures = sqlx::query_as!(
|
||||
Capture,
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload: _", trigger_extra as "trigger_extra: _"
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '"WINDMILL_TOO_BIG"'::jsonb END as "payload!: _", trigger_extra as "trigger_extra: _"
|
||||
FROM capture
|
||||
WHERE workspace_id = $1
|
||||
AND path = $2 AND is_flow = $3
|
||||
@@ -319,6 +304,24 @@ async fn list_captures(
|
||||
Ok(Json(captures))
|
||||
}
|
||||
|
||||
async fn get_capture(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, id)): Path<(String, i64)>,
|
||||
) -> JsonResult<Capture> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let capture = sqlx::query_as!(
|
||||
Capture,
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE id = $1 AND workspace_id = $2"#,
|
||||
id,
|
||||
&w_id,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(capture))
|
||||
}
|
||||
|
||||
async fn delete_capture(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -173,6 +173,13 @@ pub async fn migrate(db: &DB) -> Result<(), Error> {
|
||||
let migrator = db.acquire().await?;
|
||||
let mut custom_migrator = CustomMigrator { inner: migrator };
|
||||
|
||||
if let Err(err) = sqlx::query!("DELETE FROM _sqlx_migrations WHERE version=20250131115248")
|
||||
.execute(db)
|
||||
.await
|
||||
{
|
||||
tracing::info!("Could not remove sqlx migration with version=20250131115248: {err:#}");
|
||||
}
|
||||
|
||||
match sqlx::migrate!("../migrations")
|
||||
.run_direct(&mut custom_migrator)
|
||||
.await
|
||||
|
||||
@@ -521,7 +521,7 @@ async fn get_http_route_trigger(
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override.unwrap_or("anonymous".to_string())),
|
||||
Some(username_override.unwrap_or(format!("http-{}", trigger.path))),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -685,12 +685,6 @@ async fn route_job(
|
||||
.await,
|
||||
);
|
||||
|
||||
let label_prefix = Some(format!(
|
||||
"http-{}-{}-",
|
||||
method.as_str().to_lowercase(),
|
||||
trigger.route_path
|
||||
));
|
||||
|
||||
let run_query = RunJobQuery::default();
|
||||
|
||||
if trigger.is_flow {
|
||||
@@ -703,7 +697,7 @@ async fn route_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -716,7 +710,7 @@ async fn route_job(
|
||||
user_db,
|
||||
args,
|
||||
trigger.workspace_id.clone(),
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -731,7 +725,7 @@ async fn route_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -744,7 +738,7 @@ async fn route_job(
|
||||
user_db,
|
||||
trigger.workspace_id.clone(),
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::HeaderValue;
|
||||
use futures::TryFutureExt;
|
||||
use http::{HeaderMap, HeaderName};
|
||||
use itertools::Itertools;
|
||||
use quick_cache::sync::Cache;
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::Pool;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
#[cfg(feature = "prometheus")]
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -1966,20 +1968,34 @@ async fn resume_suspended_job_internal(
|
||||
resume_immediately_if_relevant(parent_flow_info, job_id, &mut tx).await?;
|
||||
}
|
||||
|
||||
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
|
||||
|
||||
let audit_author = match authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => {
|
||||
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
|
||||
AuditAuthor { email: approver.clone(), username: approver, username_override: None }
|
||||
}
|
||||
None => AuditAuthor {
|
||||
email: approver.clone(),
|
||||
username: approver.clone(),
|
||||
username_override: None,
|
||||
},
|
||||
};
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&audit_author,
|
||||
"jobs.approved",
|
||||
"jobs.suspend_resume",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&job_id.to_string()),
|
||||
Some(
|
||||
&serde_json::json!({
|
||||
"approved": approved,
|
||||
"job_id": job_id,
|
||||
"details": if approved {
|
||||
format!("Approved by {}", &approver)
|
||||
} else {
|
||||
format!("Cancelled by {}", &approver)
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -3522,15 +3538,17 @@ lazy_static::lazy_static! {
|
||||
pub struct WindmillCompositeResult {
|
||||
windmill_status_code: Option<u16>,
|
||||
windmill_content_type: Option<String>,
|
||||
windmill_headers: Option<HashMap<String, String>>,
|
||||
result: Option<Box<RawValue>>,
|
||||
}
|
||||
pub async fn run_wait_result(
|
||||
|
||||
pub async fn run_wait_result_internal(
|
||||
db: &DB,
|
||||
uuid: Uuid,
|
||||
w_id: String,
|
||||
node_id_for_empty_return: Option<String>,
|
||||
username: &str,
|
||||
) -> error::Result<Response> {
|
||||
) -> error::Result<(Box<RawValue>, bool)> {
|
||||
let mut result = None;
|
||||
let mut success = false;
|
||||
let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600);
|
||||
@@ -3602,81 +3620,110 @@ pub async fn run_wait_result(
|
||||
};
|
||||
tokio::time::sleep(core::time::Duration::from_millis(delay)).await;
|
||||
}
|
||||
|
||||
if let Some(result) = result {
|
||||
g.done = true;
|
||||
Ok((result, success))
|
||||
} else {
|
||||
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
|
||||
}
|
||||
}
|
||||
|
||||
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
|
||||
match composite_result {
|
||||
Ok(WindmillCompositeResult {
|
||||
windmill_status_code,
|
||||
windmill_content_type,
|
||||
result: result_value,
|
||||
}) => {
|
||||
if windmill_content_type.is_none() && windmill_status_code.is_none() {
|
||||
return Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
pub async fn run_wait_result(
|
||||
db: &DB,
|
||||
uuid: Uuid,
|
||||
w_id: String,
|
||||
node_id_for_empty_return: Option<String>,
|
||||
username: &str,
|
||||
) -> error::Result<Response> {
|
||||
let (result, success) =
|
||||
run_wait_result_internal(db, uuid, w_id, node_id_for_empty_return, username).await?;
|
||||
|
||||
let status_code_or_default = windmill_status_code
|
||||
.map(|val| match StatusCode::from_u16(val) {
|
||||
Ok(sc) => Ok(sc),
|
||||
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if !success {
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
} else if result_value.is_some() {
|
||||
Ok(StatusCode::OK)
|
||||
} else {
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
})?;
|
||||
|
||||
if windmill_content_type.is_some() {
|
||||
let serialized_json_result = result_value
|
||||
.map(|val| val.get().to_owned())
|
||||
.unwrap_or_else(String::new);
|
||||
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
|
||||
// it falls back to the original serialized JSON if it doesn't work.
|
||||
let serialized_result =
|
||||
serde_json::from_str::<String>(serialized_json_result.as_str())
|
||||
.ok()
|
||||
.unwrap_or(serialized_json_result);
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
[(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(windmill_content_type.unwrap().as_str()).unwrap(),
|
||||
)],
|
||||
serialized_result,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
|
||||
match composite_result {
|
||||
Ok(WindmillCompositeResult {
|
||||
windmill_status_code,
|
||||
windmill_content_type,
|
||||
windmill_headers,
|
||||
result: result_value,
|
||||
}) => {
|
||||
if windmill_content_type.is_none()
|
||||
&& windmill_status_code.is_none()
|
||||
&& windmill_headers.is_none()
|
||||
{
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
Json(result_value), // default to JSON result if no content type is provided
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
_ => Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response()),
|
||||
|
||||
let status_code_or_default = windmill_status_code
|
||||
.map(|val| match StatusCode::from_u16(val) {
|
||||
Ok(sc) => Ok(sc),
|
||||
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if !success {
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
} else if result_value.is_some() {
|
||||
Ok(StatusCode::OK)
|
||||
} else {
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
if let Some(windmill_headers) = windmill_headers {
|
||||
for (k, v) in windmill_headers {
|
||||
let k = HeaderName::from_str(k.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid header name {k}: {err}"))
|
||||
})?;
|
||||
let v = HeaderValue::from_str(v.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid header value {v}: {err}"))
|
||||
})?;
|
||||
headers.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(content_type) = windmill_content_type {
|
||||
let serialized_json_result = result_value
|
||||
.map(|val| val.get().to_owned())
|
||||
.unwrap_or_else(String::new);
|
||||
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
|
||||
// it falls back to the original serialized JSON if it doesn't work.
|
||||
let serialized_result =
|
||||
serde_json::from_str::<String>(serialized_json_result.as_str())
|
||||
.ok()
|
||||
.unwrap_or(serialized_json_result);
|
||||
headers.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(content_type.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid content type {content_type}: {err}"))
|
||||
})?,
|
||||
);
|
||||
return Ok((status_code_or_default, headers, serialized_result).into_response());
|
||||
}
|
||||
if let Some(result_value) = result_value {
|
||||
return Ok((status_code_or_default, headers, Json(result_value)).into_response());
|
||||
} else {
|
||||
Ok((status_code_or_default, headers).into_response())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
|
||||
_ => Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,12 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub async fn start_kafka_consumers(
|
||||
pub fn start_kafka_consumers(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum KafkaTriggerConfigConnection {}
|
||||
|
||||
@@ -34,7 +34,11 @@ use http::HeaderValue;
|
||||
use reqwest::Client;
|
||||
#[cfg(feature = "oauth2")]
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::global_settings::load_value_from_global_settings;
|
||||
use windmill_common::global_settings::EMAIL_DOMAIN_SETTING;
|
||||
use windmill_common::worker::HUB_CACHE_DIR;
|
||||
|
||||
use std::fs::DirBuilder;
|
||||
use std::time::Duration;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
@@ -73,6 +77,8 @@ mod http_triggers;
|
||||
mod indexer_ee;
|
||||
mod inputs;
|
||||
mod integration;
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
mod postgres_triggers;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod apps_ee;
|
||||
@@ -100,6 +106,8 @@ mod slack_approvals;
|
||||
mod smtp_server_ee;
|
||||
mod static_assets;
|
||||
mod stripe_ee;
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod teams_ee;
|
||||
mod tracing_init;
|
||||
mod triggers;
|
||||
mod users;
|
||||
@@ -199,6 +207,13 @@ pub async fn run_server(
|
||||
) -> anyhow::Result<()> {
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
for x in [HUB_CACHE_DIR] {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(x)
|
||||
.expect("could not create initial server dir");
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let ext_jwks = ExternalJwks::load().await;
|
||||
let auth_cache = Arc::new(crate::auth::AuthCache::new(
|
||||
@@ -238,16 +253,32 @@ pub async fn run_server(
|
||||
#[cfg(feature = "embedding")]
|
||||
load_embeddings_db(&db);
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
let mut start_smtp_server = false;
|
||||
if let Some(smtp_settings) =
|
||||
load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await?
|
||||
{
|
||||
let smtp_server = Arc::new(SmtpServer {
|
||||
db: db.clone(),
|
||||
user_db: user_db,
|
||||
auth_cache: auth_cache.clone(),
|
||||
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:#}");
|
||||
if smtp_settings.as_str().unwrap_or("") != "" {
|
||||
start_smtp_server = true;
|
||||
}
|
||||
}
|
||||
if !start_smtp_server {
|
||||
tracing::info!("SMTP server not started because email domain is not set");
|
||||
} else {
|
||||
#[cfg(feature = "smtp")]
|
||||
{
|
||||
let smtp_server = Arc::new(SmtpServer {
|
||||
db: db.clone(),
|
||||
user_db: user_db,
|
||||
auth_cache: auth_cache.clone(),
|
||||
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:#}");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "smtp"))]
|
||||
{
|
||||
tracing::info!("SMTP server not started because SMTP feature is not enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,19 +323,24 @@ pub async fn run_server(
|
||||
#[cfg(feature = "websocket")]
|
||||
{
|
||||
let ws_killpill_rx = rx.resubscribe();
|
||||
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx).await;
|
||||
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
let kafka_killpill_rx = rx.resubscribe();
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx).await;
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
{
|
||||
let nats_killpill_rx = rx.resubscribe();
|
||||
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx).await;
|
||||
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx);
|
||||
}
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
{
|
||||
let db_killpill_rx = rx.resubscribe();
|
||||
postgres_triggers::start_database(db.clone(), db_killpill_rx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +411,16 @@ pub async fn run_server(
|
||||
Router::new()
|
||||
})
|
||||
.nest("/kafka_triggers", kafka_triggers_service)
|
||||
.nest("/nats_triggers", nats_triggers_service),
|
||||
.nest("/nats_triggers", nats_triggers_service)
|
||||
.nest("/postgres_triggers", {
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
{
|
||||
postgres_triggers::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "postgres_trigger"))]
|
||||
Router::new()
|
||||
}),
|
||||
)
|
||||
.nest("/workspaces", workspaces::global_service())
|
||||
.nest(
|
||||
@@ -435,6 +480,17 @@ pub async fn run_server(
|
||||
jobs::workspace_unauthed_service().layer(cors.clone()),
|
||||
)
|
||||
.route("/slack", post(slack_approvals::slack_app_callback_handler))
|
||||
.nest("/teams", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
teams_ee::teams_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.route(
|
||||
"/w/:workspace_id/jobs/slack_approval/:job_id",
|
||||
get(slack_approvals::request_slack_approval),
|
||||
|
||||
@@ -9,9 +9,9 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub async fn start_nats_consumers(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum NatsTriggerConfigConnection {}
|
||||
|
||||
@@ -80,9 +80,10 @@ pub struct AllClients {
|
||||
}
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub fn build_oauth_clients(
|
||||
pub async fn build_oauth_clients(
|
||||
_base_url: &str,
|
||||
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
|
||||
_db: &DB,
|
||||
) -> anyhow::Result<AllClients> {
|
||||
// Implementation is not open source
|
||||
return Ok(AllClients {
|
||||
|
||||
24
backend/windmill-api/src/postgres_triggers/bool.rs
Normal file
24
backend/windmill-api/src/postgres_triggers/bool.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/**
|
||||
* This implementation is inspired by Postgres replication functionality
|
||||
* from https://github.com/supabase/pg_replicate
|
||||
*
|
||||
* Original implementation:
|
||||
* - https://github.dev/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/bool.rs
|
||||
*
|
||||
*/
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseBoolError {
|
||||
#[error("invalid input value: {0}")]
|
||||
InvalidInput(String),
|
||||
}
|
||||
|
||||
pub fn parse_bool(s: &str) -> Result<bool, ParseBoolError> {
|
||||
match s {
|
||||
"t" => Ok(true),
|
||||
"f" => Ok(false),
|
||||
_ => Err(ParseBoolError::InvalidInput(s.to_string())),
|
||||
}
|
||||
}
|
||||
255
backend/windmill-api/src/postgres_triggers/converter.rs
Normal file
255
backend/windmill-api/src/postgres_triggers/converter.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use core::str;
|
||||
use std::{
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{
|
||||
bool::{parse_bool, ParseBoolError},
|
||||
hex::{from_bytea_hex, ByteaHexParseError},
|
||||
};
|
||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use rust_postgres::types::Type;
|
||||
use serde_json::{to_value, Number, Value};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/**
|
||||
* This implementation is inspired by Postgres replication functionality
|
||||
* from https://github.com/supabase/pg_replicate
|
||||
*
|
||||
* Original implementation:
|
||||
* - https://github.com/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/text.rs
|
||||
*
|
||||
*/
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConverterError {
|
||||
#[error("invalid bool value")]
|
||||
InvalidBool(#[from] ParseBoolError),
|
||||
|
||||
#[error("invalid int value")]
|
||||
InvalidInt(#[from] ParseIntError),
|
||||
|
||||
#[error("invalid float value")]
|
||||
InvalidFloat(#[from] ParseFloatError),
|
||||
|
||||
#[error("invalid numeric: {0}")]
|
||||
InvalidNumeric(#[from] rust_decimal::Error),
|
||||
|
||||
#[error("invalid bytea: {0}")]
|
||||
InvalidBytea(#[from] ByteaHexParseError),
|
||||
|
||||
#[error("invalid uuid: {0}")]
|
||||
InvalidUuid(#[from] uuid::Error),
|
||||
|
||||
#[error("invalid json: {0}")]
|
||||
InvalidJson(#[from] serde_json::Error),
|
||||
|
||||
#[error("invalid timestamp: {0} ")]
|
||||
InvalidTimestamp(#[from] chrono::ParseError),
|
||||
|
||||
#[error("invalid array: {0}")]
|
||||
InvalidArray(#[from] ArrayParseError),
|
||||
|
||||
#[error("{0}")]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
fn convert_into<T>(number: T) -> Number
|
||||
where
|
||||
T: Sized,
|
||||
serde_json::Number: From<T>,
|
||||
{
|
||||
serde_json::Number::from(number)
|
||||
}
|
||||
|
||||
pub struct Converter;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ArrayParseError {
|
||||
#[error("input too short")]
|
||||
InputTooShort,
|
||||
|
||||
#[error("missing braces")]
|
||||
MissingBraces,
|
||||
}
|
||||
|
||||
fn f64_to_json_number(raw_val: f64) -> Result<Value, ConverterError> {
|
||||
let temp = serde_json::Number::from_f64(raw_val.into())
|
||||
.ok_or(ConverterError::Custom("invalid json-float".to_string()))?;
|
||||
Ok(Value::Number(temp))
|
||||
}
|
||||
|
||||
impl Converter {
|
||||
pub fn try_from_str(typ: Option<Type>, str: &str) -> Result<Value, ConverterError> {
|
||||
let value = match typ.unwrap_or(Type::TEXT) {
|
||||
Type::BOOL => Value::Bool(parse_bool(str)?),
|
||||
Type::BOOL_ARRAY => {
|
||||
Converter::parse_array(str, |str| Ok(Value::Bool(parse_bool(str)?)))?
|
||||
}
|
||||
Type::CHAR | Type::BPCHAR | Type::VARCHAR | Type::NAME | Type::TEXT => {
|
||||
Value::String(str.to_string())
|
||||
}
|
||||
Type::CHAR_ARRAY
|
||||
| Type::BPCHAR_ARRAY
|
||||
| Type::VARCHAR_ARRAY
|
||||
| Type::NAME_ARRAY
|
||||
| Type::TEXT_ARRAY => {
|
||||
Converter::parse_array(str, |str| Ok(Value::String(str.to_string())))?
|
||||
}
|
||||
Type::INT2 => Value::Number(convert_into(str.parse::<i16>()?)),
|
||||
Type::INT2_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(Value::Number(convert_into(str.parse::<i16>()?)))
|
||||
})?,
|
||||
Type::INT4 => Value::Number(convert_into(str.parse::<i32>()?)),
|
||||
Type::INT4_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(Value::Number(convert_into(str.parse::<i32>()?)))
|
||||
})?,
|
||||
Type::INT8 => Value::Number(convert_into(str.parse::<i64>()?)),
|
||||
Type::INT8_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(Value::Number(convert_into(str.parse::<i64>()?)))
|
||||
})?,
|
||||
Type::FLOAT4 => f64_to_json_number(str.parse::<f64>()?)?,
|
||||
Type::FLOAT4_ARRAY => {
|
||||
Converter::parse_array(str, |str| f64_to_json_number(str.parse::<f64>()?))?
|
||||
}
|
||||
Type::FLOAT8 => f64_to_json_number(str.parse::<f64>()?)?,
|
||||
Type::FLOAT8_ARRAY => {
|
||||
Converter::parse_array(str, |str| f64_to_json_number(str.parse::<f64>()?))?
|
||||
}
|
||||
Type::NUMERIC => serde_json::json!(Decimal::from_str(str)?),
|
||||
Type::NUMERIC_ARRAY => {
|
||||
Converter::parse_array(str, |str| Ok(serde_json::json!(Decimal::from_str(str)?)))?
|
||||
}
|
||||
Type::BYTEA => to_value(from_bytea_hex(str)?).unwrap(),
|
||||
Type::BYTEA_ARRAY => {
|
||||
Converter::parse_array(str, |str| Ok(to_value(from_bytea_hex(str)?).unwrap()))?
|
||||
}
|
||||
Type::DATE => {
|
||||
let date = NaiveDate::parse_from_str(str, "%Y-%m-%d")?;
|
||||
Value::String(date.to_string())
|
||||
}
|
||||
Type::DATE_ARRAY => Converter::parse_array(str, |str| {
|
||||
let date = NaiveDate::parse_from_str(str, "%Y-%m-%d")?;
|
||||
Ok(Value::String(date.to_string()))
|
||||
})?,
|
||||
Type::TIME => {
|
||||
let time = NaiveTime::parse_from_str(str, "%H:%M:%S%.f")?;
|
||||
Value::String(time.to_string())
|
||||
}
|
||||
Type::TIME_ARRAY => Converter::parse_array(str, |str| {
|
||||
let time = NaiveTime::parse_from_str(str, "%H:%M:%S%.f")?;
|
||||
Ok(Value::String(time.to_string()))
|
||||
})?,
|
||||
Type::TIMESTAMP => {
|
||||
let timestamp = NaiveDateTime::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f")?;
|
||||
Value::String(timestamp.to_string())
|
||||
}
|
||||
Type::TIMESTAMP_ARRAY => Converter::parse_array(str, |str| {
|
||||
let timestamp = NaiveDateTime::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f")?;
|
||||
Ok(Value::String(timestamp.to_string()))
|
||||
})?,
|
||||
Type::TIMESTAMPTZ => {
|
||||
let val =
|
||||
match DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%#z") {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%:z")?
|
||||
}
|
||||
};
|
||||
let utc: DateTime<Utc> = val.into();
|
||||
Value::String(utc.to_string())
|
||||
}
|
||||
Type::TIMESTAMPTZ_ARRAY => {
|
||||
match Converter::parse_array(str, |str| {
|
||||
let utc: DateTime<Utc> =
|
||||
DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%#z")?
|
||||
.into();
|
||||
Ok(Value::String(utc.to_string()))
|
||||
}) {
|
||||
Ok(val) => val,
|
||||
Err(_) => Converter::parse_array(str, |str| {
|
||||
let utc: DateTime<Utc> = DateTime::<FixedOffset>::parse_from_str(
|
||||
str,
|
||||
"%Y-%m-%d %H:%M:%S%.f%#z",
|
||||
)?
|
||||
.into();
|
||||
Ok(Value::String(utc.to_string()))
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Type::UUID => Value::String(Uuid::parse_str(str)?.to_string()),
|
||||
Type::UUID_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(Value::String(Uuid::parse_str(str)?.to_string()))
|
||||
})?,
|
||||
Type::JSON | Type::JSONB => serde_json::from_str::<serde_json::Value>(str)?,
|
||||
Type::JSON_ARRAY | Type::JSONB_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(serde_json::from_str::<serde_json::Value>(str)?)
|
||||
})?,
|
||||
Type::OID => Value::Number(convert_into(str.parse::<u32>()?)),
|
||||
Type::OID_ARRAY => Converter::parse_array(str, |str| {
|
||||
Ok(Value::Number(convert_into(str.parse::<u32>()?)))
|
||||
})?,
|
||||
_ => Value::String(str.to_string()),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn parse_array<P>(str: &str, mut parse: P) -> Result<Value, ConverterError>
|
||||
where
|
||||
P: FnMut(&str) -> Result<Value, ConverterError>,
|
||||
{
|
||||
if str.len() < 2 {
|
||||
return Err(ArrayParseError::InputTooShort.into());
|
||||
}
|
||||
|
||||
if !str.starts_with('{') || !str.ends_with('}') {
|
||||
return Err(ArrayParseError::MissingBraces.into());
|
||||
}
|
||||
|
||||
let mut res = vec![];
|
||||
let str = &str[1..(str.len() - 1)];
|
||||
let mut val_str = String::with_capacity(10);
|
||||
let mut in_quotes = false;
|
||||
let mut in_escape = false;
|
||||
let mut chars = str.chars();
|
||||
let mut done = str.is_empty();
|
||||
|
||||
while !done {
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some(c) => match c {
|
||||
c if in_escape => {
|
||||
val_str.push(c);
|
||||
in_escape = false;
|
||||
}
|
||||
'"' => in_quotes = !in_quotes,
|
||||
'\\' => in_escape = true,
|
||||
',' if !in_quotes => {
|
||||
break;
|
||||
}
|
||||
c => {
|
||||
val_str.push(c);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let val = if val_str.to_lowercase() == "null" {
|
||||
Value::Null
|
||||
} else {
|
||||
parse(&val_str)?
|
||||
};
|
||||
res.push(val);
|
||||
val_str.clear();
|
||||
}
|
||||
let arr = Value::Array(res);
|
||||
Ok(arr)
|
||||
}
|
||||
}
|
||||
1459
backend/windmill-api/src/postgres_triggers/handler.rs
Normal file
1459
backend/windmill-api/src/postgres_triggers/handler.rs
Normal file
File diff suppressed because it is too large
Load Diff
45
backend/windmill-api/src/postgres_triggers/hex.rs
Normal file
45
backend/windmill-api/src/postgres_triggers/hex.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::num::ParseIntError;
|
||||
|
||||
|
||||
/**
|
||||
* This implementation is inspired by Postgres replication functionality
|
||||
* from https://github.com/supabase/pg_replicate
|
||||
*
|
||||
* Original implementation:
|
||||
* - https://github.dev/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/hex.rs
|
||||
*
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ByteaHexParseError {
|
||||
#[error("missing prefix '\\x'")]
|
||||
InvalidPrefix,
|
||||
|
||||
#[error("invalid byte")]
|
||||
OddNumerOfDigits,
|
||||
|
||||
#[error("parse int result: {0}")]
|
||||
ParseInt(#[from] ParseIntError),
|
||||
}
|
||||
|
||||
pub fn from_bytea_hex(s: &str) -> Result<Vec<u8>, ByteaHexParseError> {
|
||||
if s.len() < 2 || &s[..2] != "\\x" {
|
||||
return Err(ByteaHexParseError::InvalidPrefix);
|
||||
}
|
||||
|
||||
let mut result = Vec::with_capacity((s.len() - 2) / 2);
|
||||
let s = &s[2..];
|
||||
|
||||
if s.len() % 2 != 0 {
|
||||
return Err(ByteaHexParseError::OddNumerOfDigits);
|
||||
}
|
||||
|
||||
for i in (0..s.len()).step_by(2) {
|
||||
let val = u8::from_str_radix(&s[i..i + 2], 16)?;
|
||||
result.push(val);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
134
backend/windmill-api/src/postgres_triggers/mapper.rs
Normal file
134
backend/windmill-api/src/postgres_triggers/mapper.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rust_postgres::types::Type;
|
||||
|
||||
use super::handler::Language;
|
||||
|
||||
fn postgres_to_typescript_type(postgres_type: Option<Type>) -> String {
|
||||
let data_type = match postgres_type {
|
||||
Some(postgres_type) => match postgres_type {
|
||||
Type::BOOL => "boolean",
|
||||
Type::BOOL_ARRAY => "Array<boolean>",
|
||||
Type::CHAR | Type::BPCHAR | Type::VARCHAR | Type::NAME | Type::TEXT => "string",
|
||||
Type::CHAR_ARRAY
|
||||
| Type::BPCHAR_ARRAY
|
||||
| Type::VARCHAR_ARRAY
|
||||
| Type::NAME_ARRAY
|
||||
| Type::TEXT_ARRAY => "Array<string>",
|
||||
Type::INT2 | Type::INT4 | Type::INT8 | Type::NUMERIC => "number",
|
||||
Type::INT2_ARRAY | Type::INT4_ARRAY | Type::INT8_ARRAY => "Array<number>",
|
||||
Type::FLOAT4 | Type::FLOAT8 => "number",
|
||||
Type::FLOAT8_ARRAY | Type::FLOAT4_ARRAY => "Array<number>",
|
||||
Type::NUMERIC_ARRAY => "Array<number>",
|
||||
Type::BYTEA => "Array<number>",
|
||||
Type::BYTEA_ARRAY => "Array<Array<number>>",
|
||||
Type::DATE => "string",
|
||||
Type::DATE_ARRAY => "Array<string>",
|
||||
Type::TIME => "string",
|
||||
Type::TIME_ARRAY => "Array<string>",
|
||||
Type::TIMESTAMPTZ | Type::TIMESTAMP => "Date",
|
||||
Type::TIMESTAMPTZ_ARRAY | Type::TIMESTAMP_ARRAY => "Array<Date>",
|
||||
Type::UUID => "string",
|
||||
Type::UUID_ARRAY => "Array<string>",
|
||||
Type::JSON | Type::JSONB | Type::JSON_ARRAY | Type::JSONB_ARRAY => "unknown",
|
||||
Type::OID => "number",
|
||||
Type::OID_ARRAY => "Array<number>",
|
||||
_ => "string",
|
||||
},
|
||||
None => "string",
|
||||
};
|
||||
|
||||
data_type.to_string()
|
||||
}
|
||||
|
||||
fn into_body_struct(language: Language, mapped_info: Vec<MappingInfo>) -> String {
|
||||
let mut block = String::new();
|
||||
match language {
|
||||
Language::Typescript => {
|
||||
block.push_str("{\r\n");
|
||||
for field in mapped_info {
|
||||
let typescript_type = postgres_to_typescript_type(field.data_type);
|
||||
let mut key = field.column_name;
|
||||
if field.is_nullable {
|
||||
key.push('?');
|
||||
}
|
||||
let full_field = format!("\t\t{}: {},\r\n", key, typescript_type);
|
||||
block.push_str(&full_field);
|
||||
}
|
||||
block.push_str("\t}");
|
||||
}
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MappingInfo {
|
||||
data_type: Option<Type>,
|
||||
is_nullable: bool,
|
||||
column_name: String,
|
||||
}
|
||||
|
||||
impl MappingInfo {
|
||||
pub fn new(column_name: String, data_type: Option<Type>, is_nullable: bool) -> Self {
|
||||
Self { column_name, data_type, is_nullable }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mapper {
|
||||
to_template: HashMap<String, HashMap<String, Vec<MappingInfo>>>,
|
||||
language: Language,
|
||||
}
|
||||
|
||||
impl Mapper {
|
||||
pub fn new(
|
||||
to_template: HashMap<String, HashMap<String, Vec<MappingInfo>>>,
|
||||
language: Language,
|
||||
) -> Self {
|
||||
Self { to_template, language }
|
||||
}
|
||||
|
||||
fn into_typescript_template(self) -> Vec<String> {
|
||||
let mut struct_definitions = Vec::new();
|
||||
for (_, mapping_info) in self.to_template {
|
||||
let last_elem = mapping_info.len() - 1;
|
||||
for (i, (_, mapped_info)) in mapping_info.into_iter().enumerate() {
|
||||
let mut struct_body = into_body_struct(Language::Typescript, mapped_info);
|
||||
let struct_body = if i != last_elem {
|
||||
struct_body.push_str("\r\n");
|
||||
struct_body
|
||||
} else {
|
||||
struct_body
|
||||
};
|
||||
struct_definitions.push(struct_body);
|
||||
}
|
||||
}
|
||||
struct_definitions
|
||||
}
|
||||
|
||||
pub fn get_template(self) -> String {
|
||||
let struct_definition = match self.language {
|
||||
Language::Typescript => self.into_typescript_template(),
|
||||
};
|
||||
|
||||
let struct_definition = if struct_definition.is_empty() {
|
||||
"any".to_string()
|
||||
} else {
|
||||
struct_definition.join("\t| ")
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
|
||||
|
||||
export async function main(
|
||||
transaction_type: "insert" | "update" | "delete",
|
||||
schema_name: string,
|
||||
table_name: string,
|
||||
row: {}
|
||||
) {{
|
||||
}}
|
||||
"#,
|
||||
struct_definition
|
||||
)
|
||||
}
|
||||
}
|
||||
154
backend/windmill-api/src/postgres_triggers/mod.rs
Normal file
154
backend/windmill-api/src/postgres_triggers/mod.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery},
|
||||
resources::get_resource_value_interpolated_internal,
|
||||
users::fetch_api_authed,
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::{
|
||||
routing::{delete, get, post},
|
||||
Router,
|
||||
};
|
||||
use handler::{
|
||||
alter_publication, create_postgres_trigger, create_publication, create_slot,
|
||||
create_template_script, delete_postgres_trigger, delete_publication, drop_slot_name,
|
||||
exists_postgres_trigger, get_postgres_trigger, get_publication_info, get_template_script,
|
||||
is_database_in_logical_level, list_database_publication, list_postgres_triggers,
|
||||
list_slot_name, set_enabled, update_postgres_trigger, Database, PostgresTrigger,
|
||||
};
|
||||
use windmill_common::{db::UserDB, error::Error, utils::StripPath};
|
||||
use windmill_queue::PushArgsOwned;
|
||||
|
||||
mod bool;
|
||||
mod converter;
|
||||
mod handler;
|
||||
mod hex;
|
||||
mod mapper;
|
||||
mod relation;
|
||||
mod replication_message;
|
||||
mod trigger;
|
||||
|
||||
pub use trigger::start_database;
|
||||
|
||||
pub async fn get_database_resource(
|
||||
authed: ApiAuthed,
|
||||
user_db: Option<UserDB>,
|
||||
db: &DB,
|
||||
database_resource_path: &str,
|
||||
w_id: &str,
|
||||
) -> Result<Database, Error> {
|
||||
let resource = get_resource_value_interpolated_internal(
|
||||
&authed,
|
||||
user_db,
|
||||
&db,
|
||||
&w_id,
|
||||
&database_resource_path,
|
||||
None,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::NotFound("Database resource do not exist".to_string()))?;
|
||||
|
||||
let resource = match resource {
|
||||
Some(resource) => serde_json::from_value::<Database>(resource).map_err(Error::SerdeJson)?,
|
||||
None => {
|
||||
return {
|
||||
Err(Error::NotFound(
|
||||
"Database resource do not exist".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(resource)
|
||||
}
|
||||
|
||||
fn publication_service() -> Router {
|
||||
Router::new()
|
||||
.route("/get/:publication_name/*path", get(get_publication_info))
|
||||
.route("/create/:publication_name/*path", post(create_publication))
|
||||
.route("/update/:publication_name/*path", post(alter_publication))
|
||||
.route(
|
||||
"/delete/:publication_name/*path",
|
||||
delete(delete_publication),
|
||||
)
|
||||
.route("/list/*path", get(list_database_publication))
|
||||
}
|
||||
|
||||
fn slot_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list/*path", get(list_slot_name))
|
||||
.route("/create/*path", post(create_slot))
|
||||
.route("/delete/*path", delete(drop_slot_name))
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(create_postgres_trigger))
|
||||
.route("/list", get(list_postgres_triggers))
|
||||
.route("/get/*path", get(get_postgres_trigger))
|
||||
.route("/update/*path", post(update_postgres_trigger))
|
||||
.route("/delete/*path", delete(delete_postgres_trigger))
|
||||
.route("/exists/*path", get(exists_postgres_trigger))
|
||||
.route("/setenabled/*path", post(set_enabled))
|
||||
.route("/get_template_script/:id", get(get_template_script))
|
||||
.route("/create_template_script", post(create_template_script))
|
||||
.route(
|
||||
"/is_valid_postgres_configuration/*path",
|
||||
get(is_database_in_logical_level),
|
||||
)
|
||||
.nest("/publication", publication_service())
|
||||
.nest("/slot", slot_service())
|
||||
}
|
||||
|
||||
async fn run_job(
|
||||
args: Option<HashMap<String, Box<RawValue>>>,
|
||||
extra: Option<HashMap<String, Box<RawValue>>>,
|
||||
db: &DB,
|
||||
trigger: &PostgresTrigger,
|
||||
) -> anyhow::Result<()> {
|
||||
let args = PushArgsOwned { args: args.unwrap_or_default(), extra };
|
||||
|
||||
let authed = fetch_api_authed(
|
||||
trigger.edited_by.clone(),
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
db,
|
||||
Some(format!("postgres-{}", trigger.path)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
let run_query = RunJobQuery::default();
|
||||
|
||||
if trigger.is_flow {
|
||||
run_flow_by_path_inner(
|
||||
authed,
|
||||
db.clone(),
|
||||
user_db,
|
||||
trigger.workspace_id.clone(),
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
run_script_by_path_inner(
|
||||
authed,
|
||||
db.clone(),
|
||||
user_db,
|
||||
trigger.workspace_id.clone(),
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
74
backend/windmill-api/src/postgres_triggers/relation.rs
Normal file
74
backend/windmill-api/src/postgres_triggers/relation.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use core::str;
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use std::{collections::HashMap, str::Utf8Error};
|
||||
|
||||
use super::{
|
||||
converter::{Converter, ConverterError},
|
||||
replication_message::{Columns, RelationBody, TupleData},
|
||||
};
|
||||
use rust_postgres::types::Oid;
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RelationConversionError {
|
||||
#[error("Could not find matching table")]
|
||||
FailToFindMatchingTable,
|
||||
|
||||
#[error("Binary data not supported")]
|
||||
BinaryFormatNotSupported,
|
||||
|
||||
#[error("decode error: {0}")]
|
||||
FromBytes(#[from] ConverterError),
|
||||
|
||||
#[error("invalid string value")]
|
||||
InvalidStr(#[from] Utf8Error),
|
||||
}
|
||||
|
||||
pub struct RelationConverter(HashMap<Oid, RelationBody>);
|
||||
|
||||
impl RelationConverter {
|
||||
pub fn new() -> Self {
|
||||
Self(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn add_relation(&mut self, relation: RelationBody) {
|
||||
self.0.insert(relation.o_id, relation);
|
||||
}
|
||||
|
||||
pub fn get_columns(&self, o_id: Oid) -> Result<&Columns, RelationConversionError> {
|
||||
self.0
|
||||
.get(&o_id)
|
||||
.map(|relation_body| &relation_body.columns)
|
||||
.ok_or(RelationConversionError::FailToFindMatchingTable)
|
||||
}
|
||||
|
||||
pub fn get_relation(&self, o_id: Oid) -> Result<&RelationBody, RelationConversionError> {
|
||||
self.0
|
||||
.get(&o_id)
|
||||
.ok_or(RelationConversionError::FailToFindMatchingTable)
|
||||
}
|
||||
|
||||
pub fn body_to_json(
|
||||
&self,
|
||||
to_decode: (Oid, Vec<TupleData>),
|
||||
) -> Result<Map<String, Value>, RelationConversionError> {
|
||||
let (o_id, tuple_data) = to_decode;
|
||||
let mut object: Map<String, Value> = Map::new();
|
||||
let columns = self.get_columns(o_id)?;
|
||||
|
||||
for (i, column) in columns.iter().enumerate() {
|
||||
let value = match &tuple_data[i] {
|
||||
TupleData::Null | TupleData::UnchangedToast => Value::Null,
|
||||
TupleData::Binary(_) => {
|
||||
return Err(RelationConversionError::BinaryFormatNotSupported)
|
||||
}
|
||||
TupleData::Text(bytes) => {
|
||||
let str = str::from_utf8(&bytes[..])?;
|
||||
Converter::try_from_str(column.type_o_id.clone(), str)?
|
||||
}
|
||||
};
|
||||
|
||||
object.insert(column.name.clone(), value);
|
||||
}
|
||||
Ok(object)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use core::str;
|
||||
use std::{
|
||||
cmp,
|
||||
io::{self, Cursor, Read},
|
||||
str::Utf8Error,
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use bytes::Bytes;
|
||||
use rust_postgres::types::{Oid, Type};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::trigger::LogicalReplicationSettings;
|
||||
const PRIMARY_KEEPALIVE_BYTE: u8 = b'k';
|
||||
const X_LOG_DATA_BYTE: u8 = b'w';
|
||||
|
||||
/**
|
||||
* This implementation is inspired by Postgres replication functionality
|
||||
* from https://github.com/supabase/pg_replicate
|
||||
*
|
||||
* Original implementation:
|
||||
* - https://github.com/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/cdc_event.rs
|
||||
*
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrimaryKeepAliveBody {
|
||||
pub wal_end: u64,
|
||||
pub timestamp: i64,
|
||||
pub reply: bool,
|
||||
}
|
||||
|
||||
impl PrimaryKeepAliveBody {
|
||||
pub fn new(wal_end: u64, timestamp: i64, reply: bool) -> PrimaryKeepAliveBody {
|
||||
PrimaryKeepAliveBody { wal_end, timestamp, reply }
|
||||
}
|
||||
}
|
||||
|
||||
const BEGIN_BYTE: u8 = b'B';
|
||||
const COMMIT_BYTE: u8 = b'C';
|
||||
const ORIGIN_BYTE: u8 = b'O';
|
||||
const RELATION_BYTE: u8 = b'R';
|
||||
const TYPE_BYTE: u8 = b'Y';
|
||||
const INSERT_BYTE: u8 = b'I';
|
||||
const UPDATE_BYTE: u8 = b'U';
|
||||
const DELETE_BYTE: u8 = b'D';
|
||||
const TUPLE_NEW_BYTE: u8 = b'N';
|
||||
const TUPLE_KEY_BYTE: u8 = b'K';
|
||||
const TUPLE_OLD_BYTE: u8 = b'O';
|
||||
const TUPLE_DATA_NULL_BYTE: u8 = b'n';
|
||||
const TUPLE_DATA_TOAST_BYTE: u8 = b'u';
|
||||
const TUPLE_DATA_TEXT_BYTE: u8 = b't';
|
||||
const TUPLE_DATA_BINARY_BYTE: u8 = b'b';
|
||||
|
||||
const REPLICA_IDENTITY_DEFAULT_BYTE: i8 = 0x64;
|
||||
const REPLICA_IDENTITY_NOTHING_BYTE: i8 = 0x6E;
|
||||
const REPLICA_IDENTITY_FULL_BYTE: i8 = 0x66;
|
||||
const REPLICA_IDENTITY_INDEX_BYTE: i8 = 0x69;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReplicaIdentity {
|
||||
Default,
|
||||
Nothing,
|
||||
Full,
|
||||
Index,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Column {
|
||||
pub flags: i8,
|
||||
pub name: String,
|
||||
pub type_o_id: Option<Type>,
|
||||
pub type_modifier: i32,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
pub fn new(flags: i8, name: String, type_o_id: Option<Type>, type_modifier: i32) -> Self {
|
||||
Self { flags, name, type_o_id, type_modifier }
|
||||
}
|
||||
}
|
||||
|
||||
pub type Columns = Vec<Column>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RelationBody {
|
||||
pub transaction_id: Option<i32>,
|
||||
pub o_id: Oid,
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub replica_identity: ReplicaIdentity,
|
||||
pub columns: Columns,
|
||||
}
|
||||
|
||||
impl RelationBody {
|
||||
pub fn new(
|
||||
transaction_id: Option<i32>,
|
||||
o_id: Oid,
|
||||
namespace: String,
|
||||
name: String,
|
||||
replica_identity: ReplicaIdentity,
|
||||
columns: Columns,
|
||||
) -> Self {
|
||||
Self { transaction_id, o_id, namespace, name, replica_identity, columns }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InsertBody {
|
||||
pub transaction_id: Option<i32>,
|
||||
pub o_id: Oid,
|
||||
pub tuple: Vec<TupleData>,
|
||||
}
|
||||
|
||||
impl InsertBody {
|
||||
pub fn new(transaction_id: Option<i32>, o_id: Oid, tuple: Vec<TupleData>) -> Self {
|
||||
Self { transaction_id, o_id, tuple }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateBody {
|
||||
transaction_id: Option<i32>,
|
||||
pub o_id: Oid,
|
||||
pub old_tuple: Option<Vec<TupleData>>,
|
||||
pub key_tuple: Option<Vec<TupleData>>,
|
||||
pub new_tuple: Vec<TupleData>,
|
||||
}
|
||||
|
||||
impl UpdateBody {
|
||||
pub fn new(
|
||||
transaction_id: Option<i32>,
|
||||
o_id: Oid,
|
||||
old_tuple: Option<Vec<TupleData>>,
|
||||
key_tuple: Option<Vec<TupleData>>,
|
||||
new_tuple: Vec<TupleData>,
|
||||
) -> Self {
|
||||
Self { transaction_id, o_id, old_tuple, key_tuple, new_tuple }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeleteBody {
|
||||
transaction_id: Option<i32>,
|
||||
pub o_id: Oid,
|
||||
pub old_tuple: Option<Vec<TupleData>>,
|
||||
pub key_tuple: Option<Vec<TupleData>>,
|
||||
}
|
||||
|
||||
impl DeleteBody {
|
||||
pub fn new(
|
||||
transaction_id: Option<i32>,
|
||||
o_id: Oid,
|
||||
old_tuple: Option<Vec<TupleData>>,
|
||||
key_tuple: Option<Vec<TupleData>>,
|
||||
) -> Self {
|
||||
Self { transaction_id, o_id, old_tuple, key_tuple }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TupleData {
|
||||
Null,
|
||||
UnchangedToast,
|
||||
Text(Bytes),
|
||||
Binary(Bytes),
|
||||
}
|
||||
|
||||
impl TupleData {
|
||||
fn parse(buf: &mut Buffer) -> Result<Vec<TupleData>, ConversionError> {
|
||||
let number_of_columns = buf.read_i16::<BigEndian>()?;
|
||||
let mut tuples = Vec::with_capacity(number_of_columns as usize);
|
||||
for _ in 0..number_of_columns {
|
||||
let byte = buf.read_u8()?;
|
||||
let tuple_data = match byte {
|
||||
TUPLE_DATA_NULL_BYTE => TupleData::Null,
|
||||
TUPLE_DATA_TOAST_BYTE => TupleData::UnchangedToast,
|
||||
TUPLE_DATA_TEXT_BYTE => {
|
||||
let len = buf.read_i32::<BigEndian>()?;
|
||||
let mut data = vec![0; len as usize];
|
||||
buf.read_exact(&mut data)?;
|
||||
TupleData::Text(data.into())
|
||||
}
|
||||
TUPLE_DATA_BINARY_BYTE => {
|
||||
let len = buf.read_i32::<BigEndian>()?;
|
||||
let mut data = vec![0; len as usize];
|
||||
buf.read_exact(&mut data)?;
|
||||
TupleData::Binary(data.into())
|
||||
}
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown replication message byte `{}`", byte),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
tuples.push(tuple_data);
|
||||
}
|
||||
|
||||
Ok(tuples)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TransactionBody {
|
||||
Insert(InsertBody),
|
||||
Update(UpdateBody),
|
||||
Delete(DeleteBody),
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub enum LogicalReplicationMessage {
|
||||
Begin,
|
||||
Commit,
|
||||
Relation(RelationBody),
|
||||
Type,
|
||||
Insert(InsertBody),
|
||||
Update(UpdateBody),
|
||||
Delete(DeleteBody),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XLogDataBody {
|
||||
pub wal_start: u64,
|
||||
pub wal_end: u64,
|
||||
pub timestamp: i64,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ConversionError {
|
||||
#[error("Error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Utf8Error conversion: {0}")]
|
||||
Utf8(#[from] Utf8Error),
|
||||
}
|
||||
|
||||
struct Buffer {
|
||||
bytes: Bytes,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new(bytes: Bytes, idx: usize) -> Buffer {
|
||||
Buffer { bytes, idx }
|
||||
}
|
||||
|
||||
fn slice(&self) -> &[u8] {
|
||||
&self.bytes[self.idx..]
|
||||
}
|
||||
|
||||
fn read_cstr(&mut self) -> Result<String, ConversionError> {
|
||||
match self.slice().iter().position(|&x| x == 0) {
|
||||
Some(pos) => {
|
||||
let start = self.idx;
|
||||
let end = start + pos;
|
||||
let cstr = str::from_utf8(&self.bytes[start..end])?.to_owned();
|
||||
self.idx = end + 1;
|
||||
Ok(cstr)
|
||||
}
|
||||
None => Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"unexpected EOF",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Buffer {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let len = {
|
||||
let slice = self.slice();
|
||||
let len = cmp::min(slice.len(), buf.len());
|
||||
buf[..len].copy_from_slice(&slice[..len]);
|
||||
len
|
||||
};
|
||||
self.idx += len;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl XLogDataBody {
|
||||
pub fn new(wal_start: u64, wal_end: u64, timestamp: i64, data: Bytes) -> XLogDataBody {
|
||||
XLogDataBody { wal_start, wal_end, timestamp, data }
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
self,
|
||||
logical_replication_settings: &LogicalReplicationSettings,
|
||||
) -> Result<LogicalReplicationMessage, ConversionError> {
|
||||
let mut buf = Buffer::new(self.data.clone(), 0);
|
||||
let byte = buf.read_u8()?;
|
||||
|
||||
let logical_replication_message = match byte {
|
||||
BEGIN_BYTE => {
|
||||
buf.read_i64::<BigEndian>()?;
|
||||
buf.read_i64::<BigEndian>()?;
|
||||
buf.read_i32::<BigEndian>()?;
|
||||
|
||||
LogicalReplicationMessage::Begin
|
||||
}
|
||||
COMMIT_BYTE => {
|
||||
buf.read_i8()?;
|
||||
buf.read_u64::<BigEndian>()?;
|
||||
buf.read_u64::<BigEndian>()?;
|
||||
buf.read_i64::<BigEndian>()?;
|
||||
LogicalReplicationMessage::Commit
|
||||
}
|
||||
RELATION_BYTE => {
|
||||
let transaction_id = match logical_replication_settings.streaming {
|
||||
true => Some(buf.read_i32::<BigEndian>()?),
|
||||
false => None,
|
||||
};
|
||||
|
||||
let o_id = buf.read_u32::<BigEndian>()?;
|
||||
let namespace = buf.read_cstr()?;
|
||||
let name = buf.read_cstr()?;
|
||||
let replica_identity = match buf.read_i8()? {
|
||||
REPLICA_IDENTITY_DEFAULT_BYTE => ReplicaIdentity::Default,
|
||||
REPLICA_IDENTITY_NOTHING_BYTE => ReplicaIdentity::Nothing,
|
||||
REPLICA_IDENTITY_FULL_BYTE => ReplicaIdentity::Full,
|
||||
REPLICA_IDENTITY_INDEX_BYTE => ReplicaIdentity::Index,
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown replica identity byte `{}`", byte),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let num_of_column = buf.read_i16::<BigEndian>()?;
|
||||
|
||||
let mut columns = Vec::with_capacity(num_of_column as usize);
|
||||
for _ in 0..num_of_column {
|
||||
let flags = buf.read_i8()?;
|
||||
let name = buf.read_cstr()?;
|
||||
let o_id = buf.read_u32::<BigEndian>()?;
|
||||
let type_modifier = buf.read_i32::<BigEndian>()?;
|
||||
let type_o_id = Type::from_oid(o_id);
|
||||
let column = Column::new(flags, name, type_o_id, type_modifier);
|
||||
|
||||
columns.push(column);
|
||||
}
|
||||
|
||||
LogicalReplicationMessage::Relation(RelationBody::new(
|
||||
transaction_id,
|
||||
o_id,
|
||||
namespace,
|
||||
name,
|
||||
replica_identity,
|
||||
columns,
|
||||
))
|
||||
}
|
||||
TYPE_BYTE => {
|
||||
buf.read_u32::<BigEndian>()?;
|
||||
buf.read_cstr()?;
|
||||
buf.read_cstr()?;
|
||||
|
||||
LogicalReplicationMessage::Type
|
||||
}
|
||||
INSERT_BYTE => {
|
||||
let transaction_id = match logical_replication_settings.streaming {
|
||||
true => Some(buf.read_i32::<BigEndian>()?),
|
||||
false => None,
|
||||
};
|
||||
let o_id = buf.read_u32::<BigEndian>()?;
|
||||
let byte = buf.read_u8()?;
|
||||
|
||||
let tuple = match byte {
|
||||
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unexpected tuple byte `{}`", byte),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
LogicalReplicationMessage::Insert(InsertBody::new(transaction_id, o_id, tuple))
|
||||
}
|
||||
UPDATE_BYTE => {
|
||||
let transaction_id = match logical_replication_settings.streaming {
|
||||
true => Some(buf.read_i32::<BigEndian>()?),
|
||||
false => None,
|
||||
};
|
||||
let o_id = buf.read_u32::<BigEndian>()?;
|
||||
let byte = buf.read_u8()?;
|
||||
let mut key_tuple = None;
|
||||
let mut old_tuple = None;
|
||||
|
||||
let new_tuple = match byte {
|
||||
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
|
||||
TUPLE_OLD_BYTE | TUPLE_KEY_BYTE => {
|
||||
if byte == TUPLE_OLD_BYTE {
|
||||
old_tuple = Some(TupleData::parse(&mut buf)?);
|
||||
} else {
|
||||
key_tuple = Some(TupleData::parse(&mut buf)?);
|
||||
}
|
||||
match buf.read_u8()? {
|
||||
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unexpected tuple byte `{}`", byte),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown tuple byte `{}`", byte),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
LogicalReplicationMessage::Update(UpdateBody::new(
|
||||
transaction_id,
|
||||
o_id,
|
||||
old_tuple,
|
||||
key_tuple,
|
||||
new_tuple,
|
||||
))
|
||||
}
|
||||
DELETE_BYTE => {
|
||||
let transaction_id = match logical_replication_settings.streaming {
|
||||
true => Some(buf.read_i32::<BigEndian>()?),
|
||||
false => None,
|
||||
};
|
||||
let o_id = buf.read_u32::<BigEndian>()?;
|
||||
let tag = buf.read_u8()?;
|
||||
|
||||
let mut key_tuple = None;
|
||||
let mut old_tuple = None;
|
||||
|
||||
match tag {
|
||||
TUPLE_OLD_BYTE => old_tuple = Some(TupleData::parse(&mut buf)?),
|
||||
TUPLE_KEY_BYTE => key_tuple = Some(TupleData::parse(&mut buf)?),
|
||||
tag => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown tuple tag `{}`", tag),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
LogicalReplicationMessage::Delete(DeleteBody::new(
|
||||
transaction_id,
|
||||
o_id,
|
||||
old_tuple,
|
||||
key_tuple,
|
||||
))
|
||||
}
|
||||
byte => {
|
||||
return Err(ConversionError::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown replication message tag `{}`", byte),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(logical_replication_message)
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub enum ReplicationMessage {
|
||||
XLogData(XLogDataBody),
|
||||
PrimaryKeepAlive(PrimaryKeepAliveBody),
|
||||
}
|
||||
|
||||
impl ReplicationMessage {
|
||||
pub fn parse(buf: Bytes) -> io::Result<Self> {
|
||||
let (byte, mut message) = buf.split_first().unwrap();
|
||||
|
||||
let replication_message = match *byte {
|
||||
X_LOG_DATA_BYTE => {
|
||||
let len = buf.len();
|
||||
let wal_start = message.read_u64::<BigEndian>()?;
|
||||
let wal_end = message.read_u64::<BigEndian>()?;
|
||||
let timestamp = message.read_i64::<BigEndian>()?;
|
||||
let len = len - message.len();
|
||||
let data = buf.slice(len..);
|
||||
ReplicationMessage::XLogData(XLogDataBody::new(wal_start, wal_end, timestamp, data))
|
||||
}
|
||||
PRIMARY_KEEPALIVE_BYTE => {
|
||||
let wal_end = message.read_u64::<BigEndian>()?;
|
||||
let timestamp = message.read_i64::<BigEndian>()?;
|
||||
let reply = message.read_u8()?;
|
||||
ReplicationMessage::PrimaryKeepAlive(PrimaryKeepAliveBody::new(
|
||||
wal_end,
|
||||
timestamp,
|
||||
reply == 1,
|
||||
))
|
||||
}
|
||||
byte => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unknown replication message byte `{}`", byte),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(replication_message)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user