Compare commits

...

87 Commits

Author SHA1 Message Date
hcourdent
9dcddf834e noopener noreferrer 2024-09-25 16:23:36 +02:00
hcourdent
e6ff31ca1d Small clarification message about all flow triggers 2024-09-25 16:18:31 +02:00
wendrul
398a09b741 feat: add static variable and resources support to ansible (#4435)
* Add static variable and resources to ansible

* Add wasm parser to cli

* Fix pulling of resources, rs and playbooks

* Remove unused import, add publish pkg-yaml

* Update package.json

* Update generate-metadata

* Fix null fields being serialized in script metadata

* npm install
2024-09-25 15:05:53 +02:00
Ruben Fiszel
fbd0071a54 chore(main): release 1.399.0 (#4436)
* chore(main): release 1.399.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-25 14:42:36 +02:00
Ruben Fiszel
81c2bb0691 fix(cli): version the whole client for deno compatibility 2024-09-25 14:38:35 +02:00
HugoCasa
aeb5b5bcd1 fix: correct AI generation for CRON and regex (#4437) 2024-09-25 12:53:32 +02:00
HugoCasa
590321fd3c feat: add tag filtering to external JWT authentication (#4425)
* feat: tag filtering jwt ext auth

* move tags to scopes

* fix symlink

* update ee ref
2024-09-25 11:21:19 +02:00
HugoCasa
8a277a0ad9 do not call workspace error handler if flow has error handler (#4434)
* do not call workspace error handler if flow has error handler

* optimize + UI improvements for error handler
2024-09-25 09:10:41 +02:00
Ruben Fiszel
8b7bbe80a0 nit + triggers sign 2024-09-24 20:04:15 +02:00
Ruben Fiszel
34a50ef8ca nit remove getLogs 2024-09-24 19:31:51 +02:00
Ruben Fiszel
93fa6a0e97 improve init script error handling 2024-09-24 19:27:00 +02:00
pyranota
74c2cabd9f Add Explicit Progress Hint (#4433)
* Add new component `ExecutionDuration`

Reusable component helps with tracking execution time of job
It is not using job.started_at, but instead uses it's own tracking mechanism

The key difference that it can give insights during execution of job and measures pure execution time
Accuracy is configurable with `updateResolution`

Can also detect if job is `longRunning` e.g. runs more than X-seconds

* Implement Hint for Explicit Progress
It uses ExecutionDuration component introduced in previous commit
and device's local storage to handle `Dont show again`

* Remove dublication of `FlowProgressBar` in `run` page

* Change Hint styling

* Change values in `ExecutionDuration` to match required

`longDefinition`: 3 -> 30
`updateResolution`: 2 -> 10

Meaning jobs running more than 30s counts as a `Long Running Job`

* Fix broken link

* Scope to langs: `python3`, `bun` and `deno` jobKinds: `script`

* Simplify ExecutionDuration for new scope

* Bring `preview` job kind into the scope

We need this to show this tip in preview pages.
e.g. By clicking on subjob details of flow (this subjob is preview)
2024-09-24 19:13:06 +02:00
HugoCasa
17a8700956 http routing docs link (#4431) 2024-09-24 17:23:14 +02:00
HugoCasa
8a469e7595 add bun/deno/python transform tests (#4432) 2024-09-24 17:14:00 +02:00
pyranota
ad69c08228 fix get_job_update returning error (#4427)
When get_progress specified to true, but there is no record of progress for given job
Endpoint will fail and return error.

Nothing cricial but should be fixed.
2024-09-24 10:24:26 +02:00
Henri Courdent
4d5415dbd2 Frontend update to docs (#4426) 2024-09-23 18:59:32 +02:00
Ruben Fiszel
99c24109de chore(main): release 1.398.1 (#4424)
* chore(main): release 1.398.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-23 18:04:41 +02:00
HugoCasa
61f27acbd5 fix: time handling in bun and deno (#4423)
* fix: time handling in bun and deno

* put dates conversion in try catch
2024-09-23 18:00:36 +02:00
Ruben Fiszel
5c6c0ff3cc chore(main): release 1.398.0 (#4412)
* chore(main): release 1.398.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-23 17:28:18 +02:00
wendrul
a500994cc2 fix: various improvements for ansible (#4419)
* Improve error messages, fix dependency job issue

* Add nsjail support for ansible

* Remove invalid comment syntax

* Change nsjail config and ansible controller config to be ephemeral

* Move ansible cfg to later to overwrite possible files named ansible.cfg

* Fix ansible.cfg env var

* Fix tmp dir issue
2024-09-23 16:44:37 +02:00
Ruben Fiszel
a17195d88f fix: update pip to 24.2 and python 3.11.8->3.11.10 2024-09-23 14:01:31 +02:00
HugoCasa
e99e7b2b0b feat(frontend): add http routing templates (#4421) 2024-09-23 12:47:51 +02:00
pyranota
01c16f29a8 floor progress in typescript client (#4420)
If user passes float to setProgress it will result BadRequest from server but without any explanation.

Sometimes it is non intuitive that endpoint accepts only integers, so to reduce confusion we convert progress to int.
2024-09-23 11:45:13 +02:00
Faton Ramadani
30017cc2b1 fix(frontend): Fix delete branch one index (#4418) 2024-09-23 11:32:47 +02:00
Ruben Fiszel
9c506a83cb critical channels build on non ee 2024-09-21 22:54:15 +02:00
Ruben Fiszel
50a6f789fa fix: migrate smtp instance settings to global settings (#4416)
* all

* all

* all

* fix

* update ee private

* all
2024-09-21 01:42:15 +02:00
HugoCasa
5be7be03a6 fix: no failed renewal alert if trial (#4414) 2024-09-20 18:47:17 +02:00
HugoCasa
70fa78c10d fix: allow no body in job requests (#4413) 2024-09-20 18:26:50 +02:00
HugoCasa
304dac3447 feat: http routing (#4339)
* feat: http routing

* all

* feat: improve UI

* final stuff

* fix: sqlx

* fix: nit

* fix: nits

* fix: error handler display

* fix: routes panel perms

* all

* fix: improve ability to paste from macos in vscode extension

* fix lock-write for deno

* all

* cleaning

* fix

* cli preprocessor

* nits

* nits

---------

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2024-09-20 15:38:38 +02:00
Ruben Fiszel
815f34b2af chore(main): release 1.397.4 (#4410)
* chore(main): release 1.397.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-20 14:53:16 +02:00
Ruben Fiszel
794f87aa0d fix(cli): make CLI not require latest version of windmill 2024-09-20 14:49:06 +02:00
Ruben Fiszel
9e9562e1a4 chore(main): release 1.397.3 (#4409)
* chore(main): release 1.397.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-20 11:38:40 +02:00
Ruben Fiszel
8212532b29 fix(cli): make CLI compatible with Node 18 2024-09-20 11:10:19 +02:00
Ruben Fiszel
2a9d1b587b fix cli build 2024-09-20 10:38:38 +02:00
Ruben Fiszel
aec6972e29 chore(main): release 1.397.2 (#4408)
* chore(main): release 1.397.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-20 09:52:35 +02:00
Ruben Fiszel
69b8754aef fix(bun): never pre-bundle BASE_URL env variable 2024-09-20 09:48:00 +02:00
Ruben Fiszel
a20ee6096a chore(main): release 1.397.1 (#4406)
* chore(main): release 1.397.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-20 09:17:15 +02:00
Ruben Fiszel
30fe28ceec fix: update git sync script 2024-09-20 09:12:00 +02:00
Alexander Petric
3fd626c8d2 properly count steps of flows in benchmark (#4407)
* fix(benchmark): properly count steps of flows

* signing commit

---------

Co-authored-by: Alexander Petric <alex@inpher.io>
2024-09-19 17:46:21 +02:00
Ruben Fiszel
07372e7e65 fix: improve ability to paste from macos in vscode extension 2024-09-19 16:15:32 +02:00
Ruben Fiszel
623ece8c67 fix(go): put shared directory in job dir and not go parent 2024-09-19 10:05:08 +02:00
Ruben Fiszel
63f70f67d7 chore(main): release 1.397.0 (#4405)
* chore(main): release 1.397.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-19 01:40:02 +02:00
Ruben Fiszel
1b3e90ce7a add ansible to default tags 2024-09-19 00:47:30 +02:00
wendrul
6855b8da9a feat: ansible playbook support (#4399)
* Ansible execution and parsing

* Working collections and pip dependencies

* Remove unused vars

* File resources logic for ansible

* Make frontend for ansible and resource file

* Format file

* Change naming of file resource and inventory fields

* Add autocomplete for file extension resource creation

* Add endpoint to list file formats for resource types

* Add CLI functionality to pull/push file resources

* Add beta tag to ansible

* Add Full image dockerfiles containing ansible

* Prepare sqlx

* Update cargo.lock

* Update ansible path

* Remove unused imports

* Add back import removed by rust-analyzer

* Improve ansible init code

* Prepare sqlx

* Change dockerfile to make the full windmill image

* Remove old dockerfile

* Improve autocomplete file resource extension select

* Remove comment

* Validate file extension

* Add icons to text file resources

* Remove editability of file resource types

* Remove unused import

* Missing space

* Add yaml parser
2024-09-19 00:21:15 +02:00
Ruben Fiszel
69b53603a5 embed typescript client in cli (#4403)
* all

* all

* all

* all

* all
2024-09-18 16:45:39 +02:00
Ruben Fiszel
5973e427c3 chore(main): release 1.396.1 (#4402)
* chore(main): release 1.396.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-18 14:36:40 +02:00
Ruben Fiszel
37d152feeb fix: postgres scripts that take longer than 20s do not timeout anymore 2024-09-18 14:32:16 +02:00
Ruben Fiszel
1f2d203f11 chore(main): release 1.396.0 (#4401)
* chore(main): release 1.396.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-18 11:27:13 +02:00
pyranota
d6d4756b7a feat: Allow setProgress and getProgress from within the script (#4400)
* Allow setting progress explicitly from script body.

This feature exposes:
 * `getProgress`
 * `setProgress`
 * `incProgress`

API in TypeScript client (python is coming soon).

NOTE: Progress cannot be out of range 0..100 and cannot decrease.

With exposed APIs there is also UI changes, so progress can be shown for individual jobs as well.

For optimization reasons, jobs start to ask for progress only after N-seconds of execution.

* feat: Add `shell.nix`

If you dont have anything but nix, dont worry, run nix-shell in root, or activate with direnv and get all needed dependencies

NOTE: You will still need docker

* feat: Add `dev.nu` to typescript client

Little helper function, allowing developer to work on ts client easier.

To use:
  `./dev.nu watch`

Now add import of windmill in body of your script and `//nobundle` on top of the file

Edit ts client in your favourite editor and hit save. Script will do the rest.

* Cleanup files

* Fix: Failed to deserialize query string: missing field `get_progress`

* perf: Implement non-naive polling mechanism for getting job progress

* Add independant delay for getProgress

Problem in `TestJobLoader`:

There should be 2 delays:

    One until we find our first progress (every 5s)
    Once we found our first progress, we can do it every second

* nit: Use `query_scalar!` instead of `query_as`

* Fix: Sql error, no rows returned by a query that expected to return at least one row

* refactor: Remove global CSS for JobProgressBar

* Change UI for progress of flow subjobs

* Replace `Step 1` with `Running` in ProgressBar for individual jobs

* Remove `incProgress`

incProgress is not very usefull and error-prone

* perf: Set metric only for jobs that are actually using it

(https://github.com/windmill-labs/windmill/pull/4373#discussion_r1759843773)

* Offload registering progress from clients to server

* Add `jobId?` argument to typescript-client's `setProgress` and `getProgress`

Allows to set progress of other jobs and flows,
if jobId specified, than flow id will be inferred automatically.

Could be used by SDK.

* Add `Error::MetricNotFound` for better error handling

* Fix: Make `JobProgressBar` display in red when failed

* Add persistant progress bar

Now you can reload the page after job is done and progress will be still there

* Allow succeeded individual job's progress bar stick to 100%

* Add python support 

* nit: Remove usage of undefined variable in python-client

* Add `async` in ts client (for error handling)

* nit(frontend): Remove unused import

* Dont load JobProgressBar when it is not needed

* nit: npm check fix

* cargo sqlx prepare

* fix sqlx

---------

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2024-09-18 01:16:19 +02:00
Ruben Fiszel
f7454e621c fix lock-write for deno 2024-09-18 00:57:53 +02:00
Ruben Fiszel
1576d33232 add main tag to ee-nsjail 2024-09-18 00:36:02 +02:00
Ruben Fiszel
a24365237c rm legacy hubScripts 2024-09-17 17:33:52 +02:00
Ruben Fiszel
c661ac8696 chore(main): release 1.395.0 (#4394)
* chore(main): release 1.395.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-17 10:41:26 +02:00
Ruben Fiszel
39374d7ee1 fix: update bun to 1.1.27 2024-09-17 10:27:51 +02:00
Ruben Fiszel
89b516450c nit display flowmodulevalue for tests 2024-09-17 09:11:23 +02:00
Ruben Fiszel
277d085dbe fix(deno): replace lock-write with frozen=false 2024-09-17 08:33:33 +02:00
Alexander Petric
9297ffcac4 fix(frontend): new resource type name must be snake case (#4396)
* check if newResourceType.name is camel case

* adding option to convert to snake case

* signing commit

* validate numbers in snake case, externalize 'valid' field of newResourceType
2024-09-17 00:15:55 +02:00
Alexander Petric
1b439218ef caddy not needed for frontend dev with cloud backend (#4397) 2024-09-17 00:14:14 +02:00
Faton Ramadani
97839a3583 fix(frontend): add support for step id change for forloops and branch… (#4395)
* fix(frontend): add support for step id change for forloops and branchone + add initial value for DebounchedInput

* fix(frontend): fix initial value of DebouncedInput
2024-09-16 23:12:23 +02:00
Ruben Fiszel
d57b139df2 fix: cannot create duplicate apps and raw_apps 2024-09-16 21:15:29 +02:00
Ruben Fiszel
8ae6c3262a fix: improve vscode extension handling of relative paths 2024-09-16 20:57:27 +02:00
HugoCasa
de78f6c192 feat: failed key renewal alert + renew on start if no recent renewal (#4387)
* feat: failed key renewal alert + renew on start if no recent renewal

* Update ee-repo-ref.txt
2024-09-16 16:54:35 +02:00
Ruben Fiszel
6ba77d533f fix(cli): update CLI schema parsers to latest 2024-09-16 11:28:24 +02:00
Ruben Fiszel
692c1dcb93 chore(main): release 1.394.6 (#4393)
* chore(main): release 1.394.6

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-15 21:07:50 +02:00
Ruben Fiszel
396258f637 fix: improve first time setup experience 2024-09-15 21:02:36 +02:00
Tsvetomir Bonev
36fc048004 chore: build full docker image for arm64 (#4391)
* chore: build `windmill-full` for `arm64`

* fix: use non-enterprise docker image as base
2024-09-14 18:30:31 +02:00
Ruben Fiszel
2e80107e03 chore(main): release 1.394.5 (#4385)
* chore(main): release 1.394.5

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-13 23:39:12 +02:00
HugoCasa
af85d4936d fix: queue metrics graph (#4388) 2024-09-13 23:34:48 +02:00
Ruben Fiszel
51cf420272 fix: parquet renderer display number of rows" (#4389)
* all

* all

* all

* nocheck
2024-09-13 23:34:36 +02:00
Ruben Fiszel
e70b36ea22 handle better false values as defaultValue of appselect 2024-09-13 17:49:03 +02:00
Ruben Fiszel
11ca14a2d3 fix: add filename to s3 upload 2024-09-13 11:52:17 +02:00
HugoCasa
f140daf4dc fix: update internal hub scritps to bun (#4384) 2024-09-13 11:41:58 +02:00
Ruben Fiszel
a0a36a7d52 update how monaco workers is loaded (#4383)
* update

* update 2

* Update package.json

* all

* update

* update

* Update package.json
2024-09-13 11:32:47 +02:00
Ruben Fiszel
e81c8384e4 chore(main): release 1.394.4 (#4375)
* chore(main): release 1.394.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-13 08:52:34 +02:00
Ruben Fiszel
bde3339de5 fix(image): correctly publish windmill-full image 2024-09-13 08:47:58 +02:00
Ruben Fiszel
bb620aba24 appselect default value fix nit + editor workers are url based 2024-09-12 21:41:06 +02:00
Manuel
03a2eae49a fix(frontend): prompt fix-AI not to rename existing variables (#4382)
* fix(frontend): prompt fix-AI not to rename existing variables

* fix(frontend): prompt fix-& edit-AI not to rename existing variables

---------

Co-authored-by: Manu [tennox] <tennox+git@txlab.io>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-09-12 18:55:46 +02:00
Ruben Fiszel
bc62930fe9 remove obsoelte flip on schemaform 2024-09-12 18:54:04 +02:00
Ruben Fiszel
4dc9ca7f4e fix: timeout in pg executor on postgresql connection after 20s 2024-09-12 18:49:43 +02:00
Ruben Fiszel
7c16f2cef4 fix: tighten number input validity if min or max is set 2024-09-12 14:51:03 +02:00
HugoCasa
ff08b5a9b5 fix: tighten inputs for granular kinds (#4379)
* fix(backend): acls perms

* nit
2024-09-12 14:40:22 +02:00
Ruben Fiszel
f222645dce fix: multiple secret picker candidates in resource adder 2024-09-12 13:52:16 +02:00
Ruben Fiszel
5bef077480 fix: improve password input for resources editor 2024-09-12 13:13:04 +02:00
HugoCasa
93e5ba1d16 fix(frontend): table footer display on safari (#4377) 2024-09-12 11:38:53 +02:00
Ruben Fiszel
fb4c8d266a fix: improve app select propagation to list inputs in apps 2024-09-12 09:18:06 +02:00
Ruben Fiszel
562b2fc671 chore(main): release 1.394.3 (#4374)
* chore(main): release 1.394.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-09-11 23:56:17 +02:00
Ruben Fiszel
51e6f36e13 fix: improve runFlowAsync and run_flow_async default behavior + time formatting of scheduled for 2024-09-11 23:52:02 +02:00
321 changed files with 70905 additions and 1966 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use nix

View File

@@ -11,8 +11,8 @@ SHELL ["/bin/bash", "-c"]
RUN apt update -y
RUN apt install -y unzip curl
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.41.0/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.41.0/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.46.3/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.46.3/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true
RUN unzip deno.zip && rm deno.zip
@@ -53,7 +53,7 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER
RUN /usr/local/bin/python3 -m pip install pip-tools
COPY --from=oven/bun:1.1.8 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.41.0/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true

View File

@@ -1,10 +1,8 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME:
${{ github.event_name != 'pull_request' && github.repository ||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository ||
'windmill-labs/windmill-test' }}
DEV_SHA:
${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
github.event.number) }}
name: Build windmill:main
@@ -476,6 +474,7 @@ jobs:
file: "./docker/DockerfileNsjail"
tags: |
${{ steps.meta-ee-public.outputs.tags }}
ghcr.io/windmill-labs/windmill-ee-nsjail:main
labels: |
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
@@ -720,11 +719,11 @@ jobs:
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public
id: meta-public
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-full
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-full
flavor: |
latest=false
tags: |
@@ -738,17 +737,17 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push publicly ee
- name: Build and push publicly
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
push: true
file: "./docker/DockerfileRust"
file: "./docker/DockerfileFull"
tags: |
${{ steps.meta-ee-public.outputs.tags }}
${{ steps.meta-public.outputs.tags }}
labels: |
${{ steps.meta-ee-public.outputs.labels }}
${{ steps.meta-public.outputs.labels }}
build_ee_full:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
@@ -787,9 +786,9 @@ jobs:
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
push: true
file: "./docker/DockerfileRustEe"
file: "./docker/DockerfileFullEe"
tags: |
${{ steps.meta-ee-public.outputs.tags }}
labels: |

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ frontend/src/routes/test.svelte
CaddyfileRemoteMalo
*.swp
**/.idea/
.direnv

View File

@@ -1,5 +1,151 @@
# Changelog
## [1.399.0](https://github.com/windmill-labs/windmill/compare/v1.398.1...v1.399.0) (2024-09-25)
### Features
* add tag filtering to external JWT authentication ([#4425](https://github.com/windmill-labs/windmill/issues/4425)) ([590321f](https://github.com/windmill-labs/windmill/commit/590321fd3c88046a657e0a751b62a19424102060))
### Bug Fixes
* **cli:** version the whole client for deno compatibility ([81c2bb0](https://github.com/windmill-labs/windmill/commit/81c2bb069176a95b8fb9c52b31e5e03c1cc78afc))
* correct AI generation for CRON and regex ([#4437](https://github.com/windmill-labs/windmill/issues/4437)) ([aeb5b5b](https://github.com/windmill-labs/windmill/commit/aeb5b5bcd163e2e7d1413d7e4aed8bd769996f24))
## [1.398.1](https://github.com/windmill-labs/windmill/compare/v1.398.0...v1.398.1) (2024-09-23)
### Bug Fixes
* time handling in bun and deno ([#4423](https://github.com/windmill-labs/windmill/issues/4423)) ([61f27ac](https://github.com/windmill-labs/windmill/commit/61f27acbd5b477f0e2453d4b02d406a7aef76009))
## [1.398.0](https://github.com/windmill-labs/windmill/compare/v1.397.4...v1.398.0) (2024-09-23)
### Features
* **frontend:** add http routing templates ([#4421](https://github.com/windmill-labs/windmill/issues/4421)) ([e99e7b2](https://github.com/windmill-labs/windmill/commit/e99e7b2b0bb1bc63d1253d147b80bac06eaff103))
* http routing ([#4339](https://github.com/windmill-labs/windmill/issues/4339)) ([304dac3](https://github.com/windmill-labs/windmill/commit/304dac34475b8871621c54a768275fae1e9f845e))
### Bug Fixes
* allow no body in job requests ([#4413](https://github.com/windmill-labs/windmill/issues/4413)) ([70fa78c](https://github.com/windmill-labs/windmill/commit/70fa78c10d8afb3e06be84e3449fdfdeb2673765))
* **frontend:** Fix delete branch one index ([#4418](https://github.com/windmill-labs/windmill/issues/4418)) ([30017cc](https://github.com/windmill-labs/windmill/commit/30017cc2b1401ea439e3fbec5d7764b206100a38))
* migrate smtp instance settings to global settings ([#4416](https://github.com/windmill-labs/windmill/issues/4416)) ([50a6f78](https://github.com/windmill-labs/windmill/commit/50a6f789fa8a03d70407b0429da049cc0740b6fa))
* no failed renewal alert if trial ([#4414](https://github.com/windmill-labs/windmill/issues/4414)) ([5be7be0](https://github.com/windmill-labs/windmill/commit/5be7be03a6df34a9fae96ed7f844d693f7cfce3e))
* update pip to 24.2 and python 3.11.8-&gt;3.11.10 ([a17195d](https://github.com/windmill-labs/windmill/commit/a17195d88ff52b72f0c2cdd95c6218df8ef6d7cf))
* various improvements for ansible ([#4419](https://github.com/windmill-labs/windmill/issues/4419)) ([a500994](https://github.com/windmill-labs/windmill/commit/a500994cc2cd9cc6cbad722059cebd0bf74a8850))
## [1.397.4](https://github.com/windmill-labs/windmill/compare/v1.397.3...v1.397.4) (2024-09-20)
### Bug Fixes
* **cli:** make CLI not require latest version of windmill ([794f87a](https://github.com/windmill-labs/windmill/commit/794f87aa0d33a0b782fcb2726754fdc51bc1d73d))
## [1.397.3](https://github.com/windmill-labs/windmill/compare/v1.397.2...v1.397.3) (2024-09-20)
### Bug Fixes
* **cli:** make CLI compatible with Node 18 ([8212532](https://github.com/windmill-labs/windmill/commit/8212532b29fbcf971cb320b8cfc84f3a8d8ad795))
## [1.397.2](https://github.com/windmill-labs/windmill/compare/v1.397.1...v1.397.2) (2024-09-20)
### Bug Fixes
* **bun:** never pre-bundle BASE_URL env variable ([69b8754](https://github.com/windmill-labs/windmill/commit/69b8754aef32682bf812be783f79b81cd8526c7a))
## [1.397.1](https://github.com/windmill-labs/windmill/compare/v1.397.0...v1.397.1) (2024-09-20)
### Bug Fixes
* **go:** put shared directory in job dir and not go parent ([623ece8](https://github.com/windmill-labs/windmill/commit/623ece8c6791b2c18648a68c562fcd5c8291be9b))
* improve ability to paste from macos in vscode extension ([07372e7](https://github.com/windmill-labs/windmill/commit/07372e7e65d77df338718929f133e477a1daf993))
* update git sync script ([30fe28c](https://github.com/windmill-labs/windmill/commit/30fe28ceecb9043eee6811dce8c3e2cd9224927b))
## [1.397.0](https://github.com/windmill-labs/windmill/compare/v1.396.1...v1.397.0) (2024-09-18)
### Features
* ansible playbook support ([#4399](https://github.com/windmill-labs/windmill/issues/4399)) ([6855b8d](https://github.com/windmill-labs/windmill/commit/6855b8da9ad92aed514ee1ab214c9550d04e7a22))
## [1.396.1](https://github.com/windmill-labs/windmill/compare/v1.396.0...v1.396.1) (2024-09-18)
### Bug Fixes
* postgres scripts that take longer than 20s do not timeout anymore ([37d152f](https://github.com/windmill-labs/windmill/commit/37d152feeb6abeb061daa5c93c24916e520b1fd0))
## [1.396.0](https://github.com/windmill-labs/windmill/compare/v1.395.0...v1.396.0) (2024-09-17)
### Features
* Allow setProgress and getProgress from within the script ([#4400](https://github.com/windmill-labs/windmill/issues/4400)) ([d6d4756](https://github.com/windmill-labs/windmill/commit/d6d4756b7a40249dbc622c8a93633e8d8b8333da))
## [1.395.0](https://github.com/windmill-labs/windmill/compare/v1.394.6...v1.395.0) (2024-09-17)
### Features
* failed key renewal alert + renew on start if no recent renewal ([#4387](https://github.com/windmill-labs/windmill/issues/4387)) ([de78f6c](https://github.com/windmill-labs/windmill/commit/de78f6c19266c67d43c7f80016a94b7988347e01))
### Bug Fixes
* cannot create duplicate apps and raw_apps ([d57b139](https://github.com/windmill-labs/windmill/commit/d57b139df2267d1650749b31a3cce5742a3654d9))
* **cli:** update CLI schema parsers to latest ([6ba77d5](https://github.com/windmill-labs/windmill/commit/6ba77d533fc1c50cf4153730adaf1b14a466f10a))
* **deno:** replace lock-write with frozen=false ([277d085](https://github.com/windmill-labs/windmill/commit/277d085dbed0b9b1424348cc580ba601d935759b))
* **frontend:** add support for step id change for forloops and branch… ([#4395](https://github.com/windmill-labs/windmill/issues/4395)) ([97839a3](https://github.com/windmill-labs/windmill/commit/97839a3583ef398c11e2f51058aaceed8e7382fb))
* **frontend:** new resource type name must be snake case ([#4396](https://github.com/windmill-labs/windmill/issues/4396)) ([9297ffc](https://github.com/windmill-labs/windmill/commit/9297ffcac4d2092022463cd68b4e8ce8f02dcb3c))
* improve vscode extension handling of relative paths ([8ae6c32](https://github.com/windmill-labs/windmill/commit/8ae6c3262a75af378a261b49f0e3a08d668c74b3))
* update bun to 1.1.27 ([39374d7](https://github.com/windmill-labs/windmill/commit/39374d7ee13ffa0fe1e4ded146c91869acad144c))
## [1.394.6](https://github.com/windmill-labs/windmill/compare/v1.394.5...v1.394.6) (2024-09-15)
### Bug Fixes
* improve first time setup experience ([396258f](https://github.com/windmill-labs/windmill/commit/396258f637e65361a9d9e0285b0a42ca5189fad4))
## [1.394.5](https://github.com/windmill-labs/windmill/compare/v1.394.4...v1.394.5) (2024-09-13)
### Bug Fixes
* add filename to s3 upload ([11ca14a](https://github.com/windmill-labs/windmill/commit/11ca14a2d32155e215c0d14d485e2c4048e5c66e))
* parquet renderer display number of rows" ([#4389](https://github.com/windmill-labs/windmill/issues/4389)) ([51cf420](https://github.com/windmill-labs/windmill/commit/51cf420272cce1948dc4295b587f5037455c4eba))
* queue metrics graph ([#4388](https://github.com/windmill-labs/windmill/issues/4388)) ([af85d49](https://github.com/windmill-labs/windmill/commit/af85d4936ddd01b390976c9989f3e4e72da3d01e))
* update internal hub scritps to bun ([#4384](https://github.com/windmill-labs/windmill/issues/4384)) ([f140daf](https://github.com/windmill-labs/windmill/commit/f140daf4dc18db0a11bf478451aa57ae4698fd32))
## [1.394.4](https://github.com/windmill-labs/windmill/compare/v1.394.3...v1.394.4) (2024-09-13)
### Bug Fixes
* **frontend:** prompt fix-AI not to rename existing variables ([#4382](https://github.com/windmill-labs/windmill/issues/4382)) ([03a2eae](https://github.com/windmill-labs/windmill/commit/03a2eae49ac5b7c858e642e9f264beebbb2e9b34))
* **frontend:** table footer display on safari ([#4377](https://github.com/windmill-labs/windmill/issues/4377)) ([93e5ba1](https://github.com/windmill-labs/windmill/commit/93e5ba1d16fcf52dea54ae4b4d7e5b02a899d0bd))
* **image:** correctly publish windmill-full image ([bde3339](https://github.com/windmill-labs/windmill/commit/bde3339de53ad8430df6d7add6cfb00b4eb895c6))
* improve app select propagation to list inputs in apps ([fb4c8d2](https://github.com/windmill-labs/windmill/commit/fb4c8d266a359af1ada4541e21b68bac9cb268a8))
* improve password input for resources editor ([5bef077](https://github.com/windmill-labs/windmill/commit/5bef077480f1d708c507d943abcb4d36de084175))
* multiple secret picker candidates in resource adder ([f222645](https://github.com/windmill-labs/windmill/commit/f222645dcead8f6608b81be36c683df679a5ae1c))
* tighten inputs for granular kinds ([#4379](https://github.com/windmill-labs/windmill/issues/4379)) ([ff08b5a](https://github.com/windmill-labs/windmill/commit/ff08b5a9b5606f02228d0b54b45d422704fed3cb))
* tighten number input validity if min or max is set ([7c16f2c](https://github.com/windmill-labs/windmill/commit/7c16f2cef4943a5d02e2bc6163761d80618aeae4))
* timeout in pg executor on postgresql connection after 20s ([4dc9ca7](https://github.com/windmill-labs/windmill/commit/4dc9ca7f4e1477c087fe4f9bf6ea03fbcc99099c))
## [1.394.3](https://github.com/windmill-labs/windmill/compare/v1.394.2...v1.394.3) (2024-09-11)
### Bug Fixes
* improve runFlowAsync and run_flow_async default behavior + time formatting of scheduled for ([51e6f36](https://github.com/windmill-labs/windmill/commit/51e6f36e13b251f01ddb8514de0d6d1a2bcd2244))
## [1.394.2](https://github.com/windmill-labs/windmill/compare/v1.394.1...v1.394.2) (2024-09-11)

Binary file not shown.

View File

@@ -1,6 +1,6 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.80-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.8-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm
FROM ${RUST_IMAGE} AS rust_base
@@ -95,6 +95,8 @@ ARG WITH_KUBECTL=true
ARG WITH_HELM=true
ARG WITH_GIT=true
RUN pip install --upgrade pip==24.2
RUN apt-get update \
&& apt-get install -y ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 software-properties-common \
&& apt-get clean \
@@ -172,7 +174,7 @@ COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
COPY --from=denoland/deno:1.46.3 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=oven/bun:1.1.25 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer

View File

@@ -352,14 +352,25 @@ you to have it being synced automatically everyday.
| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server |
## Run a local dev setup
See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all
running options.
### only Frontend
This will use the backend of <https://app.windmill.dev> but your own frontend
with hot-code reloading.
1. Install [caddy](https://caddyserver.com)
2. Go to `frontend/`:
1. `npm install`, `npm run generate-backend-client` then `npm run dev`
2. In another shell `sudo caddy run --config CaddyfileRemote`
3. Et voilà, windmill should be available at `http://localhost/`
with hot-code reloading. Note that you will need to use a username / password login due to CSRF checks using a different auth provider.
In the `frontend/` directory:
1. install the dependencies with `npm install` (or `pnpm install` or `yarn`)
2. generate the windmill client:
```
npm run generate-backend-client
## on mac use
npm run generate-backend-client-mac
```
3. Run your dev server with `npm run dev`
4. Et voilà, windmill should be available at `http://localhost/`
### Backend + Frontend
See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all
running options.

View File

@@ -0,0 +1,89 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\" FROM http_trigger WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "route_path",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_async",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "requires_auth",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "http_method: _",
"type_info": {
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
}
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "02f1a6eeb27067dc438459238e7b016f5ccf9e3fe0ffbe88471f15aad8f74441"
}

View File

@@ -32,6 +32,11 @@
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
}
],
"parameters": {
@@ -46,6 +51,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -57,7 +57,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT 1 FROM password WHERE email = 'admin@windmill.dev' AND password_hash = '$argon2id$v=19$m=4096,t=3,p=1$oLJo/lPn/gezXCuFOEyaNw$i0T2tCkw3xUFsrBIKZwr8jVNHlIfoxQe+HfDnLtd12I'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "0aa47e68f9e21115d96a8a1117e95f800c8bfb9bd6872a657c7e7131a21563c8"
}

View File

@@ -71,7 +71,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['failure_module'], $1), ARRAY['step'], $2)\n WHERE id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "0aff8b0f2cddc0dd696759f48368c7315ff39060be5430c59582e1e05a4b3d90"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE worker_ping SET \n ping_at = now(), \n jobs_executed = 1, \n current_job_id = $1, \n current_job_workspace_id = 'admins' \n WHERE worker = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": []
},
"hash": "0e52a588f3edeb8fb58d6d62247b8590e51171e2811c62737bdb81fb0ac8f182"
}

View File

@@ -0,0 +1,36 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND http_method = $2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
{
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
}
]
},
"nullable": [
null
]
},
"hash": "16c4fb122ceb30722f54c92db97427b0ddd36f495d549f8cd23d659d2c866a63"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1), ARRAY['step'], $2)\n WHERE id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "17e3e2a3232865c33fa535b5d99455942e30f932c5b97f1e5b508128f39a288f"
}

View File

@@ -51,7 +51,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM metrics WHERE id = 'license_key_renewal' AND created_at > NOW() - INTERVAL '24 hours')",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "388b3705f641621b0c2f4496e36cb7aa5a75b7e40fa312eff6590e54b02635a2"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM global_settings WHERE name = 'base_url'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "3d60b4f2cbedb61affeff94fd77b7c86493c30a4d3d648d0660a3281824156b9"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT tag, dedicated_worker, 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",
"query": "SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor \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": [
{
@@ -15,8 +15,8 @@
},
{
"ordinal": 2,
"name": "early_return",
"type_info": "Text"
"name": "has_preprocessor",
"type_info": "Bool"
}
],
"parameters": {
@@ -31,5 +31,5 @@
null
]
},
"hash": "872dcaec230579e4480adf23075e323557efbe52c813e3a6a0da6b855291951e"
"hash": "3fedf12ee5d44e8af9c3864cc70a8f607dccc015ce2d5150c56c68074cd0f84d"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH queue_metrics as (\n SELECT id, value, created_at\n FROM metrics\n WHERE id LIKE 'queue_%'\n AND created_at > now() - interval '14 day'\n ORDER BY created_at ASC\n )\n SELECT id, array_agg(json_build_object('value', value, 'created_at', created_at)) as \"values!\"\n FROM queue_metrics\n GROUP BY id\n ORDER BY id ASC",
"query": "WITH queue_metrics as (\n SELECT id, value, created_at\n FROM metrics\n WHERE id LIKE 'queue_%'\n AND created_at > now() - interval '14 day'\n )\n SELECT id, array_agg(json_build_object('value', value, 'created_at', created_at) ORDER BY created_at ASC) as \"values!\"\n FROM queue_metrics\n GROUP BY id\n ORDER BY id ASC",
"describe": {
"columns": [
{
@@ -22,5 +22,5 @@
null
]
},
"hash": "7c95f3652de4561b18c1a340db3e0113a4777d38de4468fc41b22ce5b6fd1db7"
"hash": "44dd7a66ecc9564ad5727970b5f60a1717eda8924999741be522ffb74cc173fa"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE completed_job SET args = '{\"reason\":\"PREPROCESSOR_ARGS_ARE_DISCARDED\"}'::jsonb WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "4c97fcc93b31c4b3262419d6ee183773a95de3b6694a398cb80d288bef4f130f"
}

View File

@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE http_trigger \n SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, edited_by = $7, email = $8, is_async = $9, requires_auth = $10, edited_at = now() \n WHERE workspace_id = $11 AND path = $12",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Bool",
{
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
},
"Varchar",
"Varchar",
"Bool",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "4d8640e84fccf1a0b799d8396a51e69345137e68d5096c70ba0a4332075d97ea"
}

View File

@@ -0,0 +1,108 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as \"http_method: _\", edited_by, email, edited_at, extra_perms, is_async, requires_auth\n FROM http_trigger\n WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "route_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "route_path_key",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "http_method: _",
"type_info": {
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
}
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 11,
"name": "is_async",
"type_info": "Bool"
},
{
"ordinal": 12,
"name": "requires_auth",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "4fb95eae1c871241efe2ef79615ce03cba0e4a12aad3274e4829d98e38ca1491"
}

View File

@@ -0,0 +1,36 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, edited_by = $5, email = $6, is_async = $7, requires_auth = $8, edited_at = now() \n WHERE workspace_id = $9 AND path = $10",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Bool",
{
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
},
"Varchar",
"Varchar",
"Bool",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "55d44f569f8ebfccddf975e1a330ef0dc286f4138efe923832371cdbac7157b0"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout from script where hash = $1 AND workspace_id = $2",
"query": "select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor from script where hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -56,7 +56,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}
@@ -81,6 +82,11 @@
"ordinal": 10,
"name": "timeout",
"type_info": "Int4"
},
{
"ordinal": 11,
"name": "has_preprocessor",
"type_info": "Bool"
}
],
"parameters": {
@@ -100,8 +106,9 @@
true,
true,
true,
true,
true
]
},
"hash": "8b10c9ade85c0307e300224e87962ceba8e7ccbe53b56c4670a1634bb9d0e89c"
"hash": "64ce3eabed6de7a4f99884640c21218ac19ab2a4bc5f8b42deb2476f85d0deac"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT 1 FROM password LIMIT 2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "74d5757014fc7a6818ae857bfdda9f6317cf2b9e0a78cbad80ebdc935d3246fd"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', flow_status->>'step', 'progress'], $1)\n WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "76ca60e456022cf3d1931245b7daf22783c81bc757d735a4b247cc693dfed719"
}

View File

@@ -32,6 +32,11 @@
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
}
],
"parameters": {
@@ -45,6 +50,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -1,11 +1,11 @@
{
"db_name": "PostgreSQL",
"query": "SELECT config FROM config WHERE name = 'server'",
"query": "SELECT value FROM global_settings WHERE name = 'smtp_settings'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "config",
"name": "value",
"type_info": "Jsonb"
}
],
@@ -13,8 +13,8 @@
"Left": []
},
"nullable": [
true
false
]
},
"hash": "8ca76fcadc659d7069b97930cdde2d5200f3cef9dedb83b76c8a6a433d2a4de3"
"hash": "83ec97f6aad154e0e06ee05a3647dab8f89b1b2d7a569c7eac8c4169e37b9f8b"
}

View File

@@ -0,0 +1,37 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND workspace_id = $2 AND http_method = $3)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text",
{
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
}
]
},
"nullable": [
null
]
},
"hash": "867e0f8299e22a6b8e02d21adeb0281b1a10593715db823a5adeeac3ab1058cd"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2), ARRAY['step'], $3)\n WHERE id = $4",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Jsonb",
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "870d7feec169f70d140b7477562315ed2fd4662975bf0d01cfbffac86a959368"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT scalar_int FROM job_stats WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "scalar_int",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text"
]
},
"nullable": [
true
]
},
"hash": "9422431d79de41518f651ef24e86819d7b6a2f5531740a7deea1b51775335977"
}

View File

@@ -0,0 +1,41 @@
{
"db_name": "PostgreSQL",
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor\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": "tag",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "early_return",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "has_preprocessor",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true,
true,
null,
null
]
},
"hash": "98081ee9a4a4a52ae70d74d26e8608a010722d7ea9dc343ae8f03e38978ffaba"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE path = $1 AND workspace_id = $2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "9a050ab74cfc13a4b855408e61ebcb0a9d27e5563fa7af5e00a4916b03a62ddb"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue SET args = (select result FROM completed_job WHERE id = $1) WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "9c17ddca92e0a93051b36bb688ad6c1c24108e45c94be52595ca6cb82135f4eb"
}

View File

@@ -56,7 +56,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -51,7 +51,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "9ebb9c16948a695a053068d9a1df0152691271ac33d8c82db47be11746ccbcef"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['failure_module'], $1), ARRAY['step'], $2)\n WHERE id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "ad8f9e0b06f288051cbec3c91877430a1d61353b9122fd42777d92e4cbc9f4fa"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)",
"describe": {
"columns": [],
"parameters": {
@@ -37,7 +37,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}
@@ -71,10 +72,11 @@
"Varchar",
"Bool",
"Bool",
"Varchar"
"Varchar",
"Bool"
]
},
"nullable": []
},
"hash": "8e7ff45c5378c3a3406ba94dc0653afa5d28c072203617c598caefaaf1bafcfb"
"hash": "b49b1eaf58e62063c7a03039d0c36e65991b1d21a3306390fb1c7ca38babafe3"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1 AND path = $2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "b57a188ca137162a2848ebf81fa3aeca9a14ac628c61a358e2c6612c57249b0f"
}

View File

@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2), ARRAY['step'], $3)\n WHERE id = $4",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Jsonb",
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "b64c7f3b483c74c51aecda65664c36d9686112182dbf35651e494da739b922f9"
}

View File

@@ -32,6 +32,11 @@
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
}
],
"parameters": {
@@ -45,6 +50,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1)\n WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Uuid"
]
},
"nullable": []
},
"hash": "c14009d133956710f4435a5984c18b9517e256978512f014e1bf5c270f499772"
}

View File

@@ -0,0 +1,37 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now())",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Bool",
"Bool",
{
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
},
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "c229744534f17f7622c3dee21bb1e7292ff17e6dffe58e80e53bff8baade07c8"
}

View File

@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT name, format_extension FROM resource_type WHERE format_extension IS NOT NULL AND (workspace_id = $1 OR workspace_id = 'admins')",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "format_extension",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
true
]
},
"hash": "cf1cef7e0fe2e7e3db96b0ec005360361b9eec023a6fc2a4a7a917f59d86af4d"
}

View File

@@ -77,7 +77,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT (scalar_int)::int FROM job_stats WHERE job_id = $1 AND workspace_id = $2 AND metric_id = 'progress_perc'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "scalar_int",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
true
]
},
"hash": "d600a0ad953ed131952d9c46deb8f65143894f86517f2b5b2d51bc9a2857695c"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM http_trigger WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "dc9a906d6c6156a84fccf4e3a2a7c08d8ed4984409b669162f0a1fc1aa48e188"
}

View File

@@ -32,6 +32,11 @@
"ordinal": 5,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
}
],
"parameters": {
@@ -43,6 +48,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT (flow_status->'step')::integer as step, jsonb_array_length(flow_status->'modules') as len FROM queue WHERE id = $1",
"query": "SELECT (flow_status->'step')::integer as step, jsonb_array_length(flow_status->'modules') as len FROM queue WHERE id = $1",
"describe": {
"columns": [
{
@@ -24,5 +24,5 @@
null
]
},
"hash": "f80e94176224ef23416da99b26ad5c330d65016fb93345b054cbb63a4bf37fc8"
"hash": "f9e0e35b4789a4da89f7bb21fa6b250fd64ee0e0b299471979db5ff8ee906929"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
"describe": {
"columns": [
{
@@ -56,7 +56,8 @@
"mssql",
"php",
"bunnative",
"rust"
"rust",
"ansible"
]
}
}
@@ -81,6 +82,11 @@
"ordinal": 10,
"name": "timeout",
"type_info": "Int4"
},
{
"ordinal": 11,
"name": "has_preprocessor",
"type_info": "Bool"
}
],
"parameters": {
@@ -100,8 +106,9 @@
true,
true,
true,
true,
true
]
},
"hash": "e1c715020f1efb00123171edfb6173bfeaaa95d4f870240e42b76e5df304ce99"
"hash": "fc7ac95d8d5f4532a2b0e50f84c1e273d25543401639a68f868e1e4fd8b0640a"
}

View File

@@ -0,0 +1,87 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\" FROM http_trigger",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "route_path",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_async",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "requires_auth",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "http_method: _",
"type_info": {
"Custom": {
"name": "http_method",
"kind": {
"Enum": [
"get",
"post",
"put",
"delete",
"patch"
]
}
}
}
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "fe4f91ca7e179e58041a5c0b1a36015175e9301c01a99e8f2b82cb853349a183"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now())",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, format_extension, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now())",
"describe": {
"columns": [],
"parameters": {
@@ -9,10 +9,11 @@
"Varchar",
"Jsonb",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "d249343a1d9fce2f7ca9e6e447eb32d7d11b61acdab80dda24db4a9ba3e1f7e6"
"hash": "ffedbb3a2676a6d7b71f81f89109a02a8dba90d40144e942527f8a3fc36dfbc1"
}

302
backend/Cargo.lock generated
View File

@@ -177,9 +177,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.88"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "arc-swap"
@@ -229,9 +229,9 @@ checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]]
name = "arrayref"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
@@ -696,9 +696,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.82"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@@ -823,9 +823,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.42.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bf24cd0d389daa923e974b0e7c38daf308fc21e963c049f57980235017175e"
checksum = "70a9d27ed1c12b1140c47daf1bc541606c43fdafd918c4797d520db0043ceef2"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -845,9 +845,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.43.0"
version = "1.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43b3220f1c46ac0e9dcc0a97d94b93305dacb36d1dd393996300c6b9b74364"
checksum = "44514a6ca967686cde1e2a1b81df6ef1883d0e3e570da8d8bc5c491dcb6fc29b"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -867,9 +867,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.42.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1c46924fb1add65bba55636e12812cae2febf68c0f37361766f627ddcca91ce"
checksum = "cd7a4d279762a35b9df97209f6808b95d4fe78547fe2316b4d200a0283960c5a"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1056,9 +1056,9 @@ dependencies = [
[[package]]
name = "axum"
version = "0.7.5"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec"
dependencies = [
"async-trait",
"axum-core",
@@ -1083,7 +1083,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tower 0.4.13",
"tower 0.5.1",
"tower-layer",
"tower-service",
"tracing",
@@ -1091,9 +1091,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00"
dependencies = [
"async-trait",
"bytes",
@@ -1104,7 +1104,7 @@ dependencies = [
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper 0.1.2",
"sync_wrapper 1.0.1",
"tower-layer",
"tower-service",
"tracing",
@@ -1505,9 +1505,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "bytes-utils"
@@ -1609,9 +1609,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.18"
version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [
"jobserver",
"libc",
@@ -1735,9 +1735,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1745,9 +1745,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [
"anstream",
"anstyle",
@@ -1757,9 +1757,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.13"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2795,7 +2795,7 @@ dependencies = [
"rustls-webpki 0.102.8",
"serde",
"tokio",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@@ -4313,9 +4313,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.60"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -4523,9 +4523,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.70"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
@@ -4670,9 +4670,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libgit2-sys"
@@ -4710,7 +4710,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.6.0",
"libc",
"redox_syscall 0.5.3",
"redox_syscall 0.5.5",
]
[[package]]
@@ -4892,7 +4892,7 @@ dependencies = [
"smtp-proto",
"tokio",
"tokio-rustls 0.26.0",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@@ -5025,9 +5025,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
"stable_deref_trait",
@@ -5058,16 +5058,6 @@ dependencies = [
"unicase",
]
[[package]]
name = "minicov"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169"
dependencies = [
"cc",
"walkdir",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -5452,7 +5442,7 @@ dependencies = [
"md-5 0.10.6",
"parking_lot",
"percent-encoding",
"quick-xml 0.36.1",
"quick-xml 0.36.2",
"rand 0.8.5",
"reqwest 0.12.7",
"ring 0.17.8",
@@ -5682,7 +5672,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.3",
"redox_syscall 0.5.5",
"smallvec",
"windows-targets 0.52.6",
]
@@ -6001,9 +5991,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "polling"
@@ -6022,9 +6012,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce"
[[package]]
name = "postgres-native-tls"
@@ -6059,9 +6049,9 @@ dependencies = [
[[package]]
name = "postgres-types"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9"
checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
dependencies = [
"array-init",
"bit-vec",
@@ -6130,7 +6120,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
dependencies = [
"toml_edit 0.22.20",
"toml_edit 0.22.22",
]
[[package]]
@@ -6384,9 +6374,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.36.1"
version = "0.36.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
dependencies = [
"memchr",
"serde",
@@ -6394,9 +6384,9 @@ dependencies = [
[[package]]
name = "quick_cache"
version = "0.6.6"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ec0b6fed0a0ff01fa82d0c8982389375dd59c72dae84d4f8a15b1a894c273f7"
checksum = "7d7c94f8935a9df96bb6380e8592c70edf497a643f94bd23b2f76b399385dbf4"
dependencies = [
"ahash 0.8.11",
"equivalent",
@@ -6633,9 +6623,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.3"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
checksum = "62871f2d65009c0256aed1b9cfeeb8ac272833c404e13d53d400cd0dad7a2ac0"
dependencies = [
"bitflags 2.6.0",
]
@@ -7425,9 +7415,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.11.1"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
dependencies = [
"core-foundation-sys",
"libc",
@@ -7765,9 +7755,9 @@ dependencies = [
[[package]]
name = "simdutf8"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "simple_asn1"
@@ -8061,7 +8051,7 @@ dependencies = [
"tracing",
"url",
"uuid 1.10.0",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@@ -9029,9 +9019,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.41"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909"
checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020"
dependencies = [
"filetime",
"libc",
@@ -9071,18 +9061,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
@@ -9325,9 +9315,9 @@ dependencies = [
[[package]]
name = "tokio-postgres"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3"
checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb"
dependencies = [
"async-trait",
"byteorder",
@@ -9443,7 +9433,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.20",
"toml_edit 0.22.22",
]
[[package]]
@@ -9470,15 +9460,15 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.20"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.18",
"winnow 0.6.19",
]
[[package]]
@@ -9507,7 +9497,6 @@ dependencies = [
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@@ -9516,8 +9505,14 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper 0.1.2",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@@ -9934,9 +9929,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-id"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f"
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
[[package]]
name = "unicode-id-start"
@@ -9952,9 +9947,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
@@ -9976,21 +9971,21 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-xid"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unicode_categories"
@@ -10000,9 +9995,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unicode_names2"
version = "1.2.2"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addeebf294df7922a1164f729fb27ebbbcea99cc32b3bf08afab62757f707677"
checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd"
dependencies = [
"phf",
"unicode_names2_generator",
@@ -10010,9 +10005,9 @@ dependencies = [
[[package]]
name = "unicode_names2_generator"
version = "1.2.2"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f444b8bba042fe3c1251ffaca35c603f2dc2ccc08d595c65a8c4f76f3e8426c0"
checksum = "b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e"
dependencies = [
"getopts",
"log",
@@ -10054,7 +10049,7 @@ dependencies = [
"serde",
"serde_json",
"url",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@@ -10211,20 +10206,19 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.93"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.93"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
@@ -10237,9 +10231,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.43"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
@@ -10249,9 +10243,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.93"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -10259,9 +10253,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.93"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
@@ -10272,19 +10266,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.93"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.43"
version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9"
checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
dependencies = [
"console_error_panic_hook",
"js-sys",
"minicov",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -10293,9 +10286,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.43"
version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
dependencies = [
"proc-macro2",
"quote",
@@ -10326,9 +10319,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.70"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -10352,9 +10345,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
version = "0.26.5"
version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
dependencies = [
"rustls-pki-types",
]
@@ -10389,7 +10382,7 @@ version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [
"redox_syscall 0.5.3",
"redox_syscall 0.5.5",
"wasite",
"web-sys",
]
@@ -10433,7 +10426,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"axum",
@@ -10475,7 +10468,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"argon2",
@@ -10509,6 +10502,7 @@ dependencies = [
"lazy_static",
"magic-crypt",
"mail-parser",
"matchit",
"mime_guess",
"native-tls",
"object_store",
@@ -10559,7 +10553,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"base64 0.21.7",
"chrono",
@@ -10577,7 +10571,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"chrono",
"serde",
@@ -10590,7 +10584,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"async-stream",
@@ -10634,7 +10628,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"regex",
"rsmq_async",
@@ -10649,7 +10643,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"bytes",
@@ -10670,7 +10664,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -10679,7 +10673,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10691,7 +10685,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"gosyn",
@@ -10703,7 +10697,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10715,7 +10709,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10726,7 +10720,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10737,7 +10731,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10755,7 +10749,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10772,7 +10766,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10784,7 +10778,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10802,7 +10796,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -10818,11 +10812,22 @@ dependencies = [
"windmill-parser-rust",
"windmill-parser-sql",
"windmill-parser-ts",
"windmill-parser-yaml",
]
[[package]]
name = "windmill-parser-yaml"
version = "1.399.0"
dependencies = [
"anyhow",
"serde_json",
"windmill-parser",
"yaml-rust",
]
[[package]]
name = "windmill-queue"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10855,7 +10860,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -10865,7 +10870,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.394.2"
version = "1.399.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10933,7 +10938,9 @@ dependencies = [
"windmill-parser-rust",
"windmill-parser-sql",
"windmill-parser-ts",
"windmill-parser-yaml",
"windmill-queue",
"yaml-rust",
"zstd 0.12.4",
]
@@ -11135,9 +11142,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.6.18"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
checksum = "c52ac009d615e79296318c1bcce2d422aaca15ad08515e344feeda07df67a587"
dependencies = [
"memchr",
]
@@ -11193,6 +11200,15 @@ dependencies = [
"lzma-sys",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi"
version = "0.5.1"

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.394.2"
version = "1.399.0"
authors.workspace = true
edition.workspace = true
@@ -23,10 +23,11 @@ members = [
"./parsers/windmill-parser-py",
"./parsers/windmill-parser-py-imports",
"./parsers/windmill-sql-datatype-parser-wasm",
"./parsers/windmill-parser-yaml",
]
[workspace.package]
version = "1.394.2"
version = "1.399.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -118,6 +119,7 @@ windmill-parser-py = { path = "./parsers/windmill-parser-py" }
windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" }
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
windmill-parser-yaml = { path = "./parsers/windmill-parser-yaml" }
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
@@ -219,7 +221,7 @@ rsmq_async = { version = "5.1.5" }
gosyn = "0.2.6"
bytes = "1.4.0"
gethostname = "0.4.3"
wasm-bindgen = "0.2.92"
wasm-bindgen = "=0.2.92"
serde-wasm-bindgen = "0.6.5"
wasm-bindgen-test = "0.3.42"
convert_case = "0.6.0"
@@ -250,6 +252,7 @@ indexmap = { version = "2.2.5", features = ["serde"]}
tokio-native-tls = "^0"
openssl = "=0.10"
mail-parser = "^0"
matchit = "=0.7.3"
datafusion = "39.0.0"
object_store = { version = "0.10.0", features = ["aws", "azure"] }
@@ -277,3 +280,4 @@ toml = "0.7"
syn = { version = "2.0.74", features = ["full"] }
quote = "1.0.36"
regex-lite = "0.1.6"
yaml-rust = "0.4.5"

View File

@@ -1 +1 @@
3d8ce9f1f15de94a40a0c349c7769598041a0d8a
3d37b6c31155265d8d026ae9d6ced0b433078f87

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'ansible';
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["ansible"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust"]}'::jsonb AND NOT config->'worker_tags' @> '"ansible"'::jsonb;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE resource_type
DROP COLUMN format_extension;

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE resource_type
ADD COLUMN format_extension VARCHAR(20);

View File

@@ -0,0 +1,7 @@
-- Add down migration script here
DROP TABLE http_trigger;
DROP TYPE http_method;
ALTER TABLE script DROP COLUMN has_preprocessor;
DROP FUNCTION prevent_route_path_change();

View File

@@ -0,0 +1,61 @@
-- Add up migration script here
CREATE TYPE http_method AS ENUM ('get', 'post', 'put', 'delete', 'patch');
CREATE TABLE http_trigger (
path VARCHAR(255) NOT NULL,
route_path VARCHAR(255) NOT NULL,
route_path_key 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 NOT NULL DEFAULT '{}',
is_async BOOLEAN NOT NULL DEFAULT FALSE,
requires_auth BOOLEAN NOT NULL DEFAULT FALSE,
http_method http_method NOT NULL,
PRIMARY KEY (path, workspace_id)
);
GRANT SELECT, UPDATE ON http_trigger TO windmill_user;
GRANT ALL ON http_trigger TO windmill_admin;
ALTER TABLE http_trigger ENABLE ROW LEVEL SECURITY;
CREATE POLICY see_folder_extra_perms_user_select ON http_trigger FOR SELECT TO windmill_user
USING (SPLIT_PART(http_trigger.path, '/', 1) = 'f' AND SPLIT_PART(http_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_update ON http_trigger FOR UPDATE TO windmill_user
USING (SPLIT_PART(http_trigger.path, '/', 1) = 'f' AND SPLIT_PART(http_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_extra_perms_user_select ON http_trigger FOR SELECT TO windmill_user
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
CREATE POLICY see_extra_perms_user_update ON http_trigger FOR UPDATE TO windmill_user
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_groups_select ON http_trigger FOR SELECT TO windmill_user
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
CREATE POLICY see_extra_perms_groups_update ON http_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 OR REPLACE FUNCTION prevent_route_path_change()
RETURNS TRIGGER AS $$
BEGIN
IF CURRENT_USER <> 'windmill_admin' AND NEW.route_path <> OLD.route_path THEN
RAISE EXCEPTION 'Modification of route_path is only allowed by admins';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_route_path_change
BEFORE UPDATE ON http_trigger
FOR EACH ROW
EXECUTE FUNCTION prevent_route_path_change();
ALTER TABLE script ADD COLUMN has_preprocessor BOOLEAN;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,8 @@
-- Add up migration script here
INSERT INTO global_settings (name, value)
SELECT 'smtp_settings', config
FROM config
WHERE name = 'server'
ON CONFLICT (name) DO UPDATE SET value = excluded.value;
DELETE FROM config WHERE name = 'server';

View File

@@ -16,7 +16,13 @@ pub fn parse_bash_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_bash_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing bash script".to_string()))
}
@@ -26,7 +32,13 @@ pub fn parse_powershell_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_powershell_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing powershell script".to_string()))
}
@@ -173,7 +185,8 @@ non_required="${5:-}"
oidx: None
}
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);

View File

@@ -42,6 +42,7 @@ pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
star_kwargs: false,
args,
no_main_func: Some(false),
has_preprocessor: None,
})
} else {
Ok(MainArgSignature {
@@ -49,6 +50,7 @@ pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: None,
})
}
}
@@ -241,7 +243,8 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
oidx: None
},
],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: None
}
);

View File

@@ -15,7 +15,13 @@ pub fn parse_graphql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_graphql_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -119,7 +125,8 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
oidx: None
}
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);

View File

@@ -82,6 +82,7 @@ pub fn parse_php_signature(
star_kwargs: false,
args,
no_main_func: Some(false),
has_preprocessor: None,
})
} else {
Ok(MainArgSignature {
@@ -89,6 +90,7 @@ pub fn parse_php_signature(
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: None,
})
}
}
@@ -159,7 +161,8 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
oidx: None
}
],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: None
}
);

View File

@@ -63,9 +63,17 @@ pub fn parse_python_signature(
) -> anyhow::Result<MainArgSignature> {
let main_name = override_main.unwrap_or("main".to_string());
let has_preprocessor = !filter_non_main(code, "preprocessor").is_empty();
let filtered_code = filter_non_main(code, &main_name);
if filtered_code.is_empty() {
return Err(anyhow::anyhow!("No main function found".to_string(),));
return Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: Some(has_preprocessor),
});
}
let ast = Suite::parse(&filtered_code, "main.py")
.map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?;
@@ -119,6 +127,7 @@ pub fn parse_python_signature(
})
.collect(),
no_main_func: Some(false),
has_preprocessor: Some(has_preprocessor),
})
} else {
Ok(MainArgSignature {
@@ -126,6 +135,7 @@ pub fn parse_python_signature(
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: Some(has_preprocessor),
})
}
}
@@ -325,6 +335,7 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
},
],
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -389,6 +400,7 @@ def main(test1: str,
}
],
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -448,6 +460,7 @@ def main(test1: str,
}
],
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -491,6 +504,7 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
}
],
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -521,6 +535,59 @@ def main(test1: DynSelect_foo): return
oidx: None
}],
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
Ok(())
}
#[test]
fn test_parse_python_sig_6() -> anyhow::Result<()> {
let code = r#"
import os
def hello(): return
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
parse_python_signature(code, None)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: Some(false)
}
);
Ok(())
}
#[test]
fn test_parse_python_sig_7() -> anyhow::Result<()> {
let code = r#"
import os
def preprocessor(): return
def main(): return
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
parse_python_signature(code, None)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![],
no_main_func: Some(false),
has_preprocessor: Some(true)
}
);

View File

@@ -29,6 +29,7 @@ pub fn parse_rust_signature(code: &str) -> anyhow::Result<MainArgSignature> {
star_kwargs: false,
args,
no_main_func: Some(false),
has_preprocessor: None,
})
} else {
Ok(MainArgSignature {
@@ -36,6 +37,7 @@ pub fn parse_rust_signature(code: &str) -> anyhow::Result<MainArgSignature> {
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: None,
})
}
}
@@ -455,10 +457,7 @@ fn main(
assert_eq!(ret.args[2].typ, Typ::Str(None));
assert_eq!(ret.args[3].name, "my_string_vec");
assert_eq!(
ret.args[3].otyp,
Some("Vec < String >".to_string())
);
assert_eq!(ret.args[3].otyp, Some("Vec < String >".to_string()));
assert_eq!(ret.args[3].typ, Typ::List(Box::new(Typ::Str(None))));
}

View File

@@ -20,7 +20,13 @@ pub fn parse_mysql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_mysql_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -30,7 +36,13 @@ pub fn parse_pgsql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_pg_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -40,7 +52,13 @@ pub fn parse_bigquery_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_bigquery_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -50,7 +68,13 @@ pub fn parse_snowflake_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_snowflake_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -60,7 +84,13 @@ pub fn parse_mssql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_mssql_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args, no_main_func: None })
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
@@ -570,7 +600,8 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
oidx: Some(2),
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -618,7 +649,8 @@ SELECT $2::TEXT;
oidx: Some(3),
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -744,7 +776,8 @@ SELECT ?, ?;
oidx: None,
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -791,7 +824,8 @@ SELECT :param2;
oidx: None,
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -830,7 +864,8 @@ SELECT @token;
oidx: None,
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -877,7 +912,8 @@ SELECT ?;
oidx: None,
}
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);
@@ -924,7 +960,8 @@ SELECT @p2;
oidx: None,
},
],
no_main_func: None
no_main_func: None,
has_preprocessor: None
}
);

View File

@@ -159,6 +159,14 @@ pub fn parse_deno_signature(
})?
.body;
let has_preprocessor = ast.iter().any(|x| match x {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Fn(FnDecl { ident: Ident { sym, .. }, .. }),
..
})) => &sym.to_string() == "preprocessor",
_ => false,
});
let main_name = main_override.unwrap_or("main".to_string());
let params = ast.into_iter().find_map(|x| match x {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
@@ -178,6 +186,7 @@ pub fn parse_deno_signature(
.map(|x| parse_param(x, &cm, skip_dflt, &mut c))
.collect::<anyhow::Result<Vec<Arg>>>()?,
no_main_func: Some(false),
has_preprocessor: Some(has_preprocessor),
};
Ok(r)
} else {
@@ -186,6 +195,7 @@ pub fn parse_deno_signature(
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: Some(has_preprocessor),
})
}
}

View File

@@ -25,6 +25,7 @@ ts-parser = [ "dep:windmill-parser-ts"]
php-parser = [ "dep:windmill-parser-php"]
rust-parser = [ "dep:windmill-parser-rust"]
graphql-parser = [ "dep:windmill-parser-graphql"]
ansible-parser = [ "dep:windmill-parser-yaml"]
[dependencies]
anyhow.workspace = true
@@ -37,6 +38,7 @@ windmill-parser-ts = { workspace = true, optional = true }
windmill-parser-php = { workspace = true, optional = true }
windmill-parser-graphql = { workspace = true, optional = true }
windmill-parser-rust = { workspace = true, optional = true }
windmill-parser-yaml = { workspace = true, optional = true }
wasm-bindgen.workspace = true
serde_json.workspace = true
getrandom = { workspace = true, features = ["js"] }

View File

@@ -0,0 +1,50 @@
#!/bin/bash
set -eou pipefail
# full pkg
OUT_DIR="pkg"
wasm-pack build --release --target web --out-dir $OUT_DIR --all-features \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
# bun and deno
OUT_DIR="pkg-ts"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "ts-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-ts"/' $OUT_DIR/package.json
# sql languages, graphql and bash/powershell, since they all use regex
OUT_DIR="pkg-regex"
wasm-pack build --release --target web --out-dir $OUT_DIR \
--features "sql-parser,graphql-parser,bash-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-regex"/' $OUT_DIR/package.json
# python
OUT_DIR="pkg-py"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "py-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-py"/' $OUT_DIR/package.json
# go
OUT_DIR="pkg-go"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "go-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-go"/' $OUT_DIR/package.json
# php
OUT_DIR="pkg-php"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "php-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-php"/' $OUT_DIR/package.json
# rust
OUT_DIR="pkg-rust"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "rust-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json
# ansible
OUT_DIR="pkg-yaml"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json

View File

@@ -42,3 +42,9 @@ OUT_DIR="pkg-rust"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "rust-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json
# ansible
OUT_DIR="pkg-yaml"
wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json

View File

@@ -1,4 +1,4 @@
#!/bin/bash
set -eou pipefail
deno task wasmbuild --out ../../../cli/wasm/ -p windmill-parser-wasm
deno task wasmbuild --out ../../../cli/wasm/ -p windmill-parser-wasm --all-features

View File

@@ -0,0 +1,126 @@
// deno-lint-ignore-file
// deno-fmt-ignore-file
export interface InstantiateResult {
instance: WebAssembly.Instance;
exports: {
parse_deno: typeof parse_deno;
parse_outputs: typeof parse_outputs;
parse_ts_imports: typeof parse_ts_imports;
parse_bash: typeof parse_bash;
parse_powershell: typeof parse_powershell;
parse_go: typeof parse_go;
parse_python: typeof parse_python;
parse_sql: typeof parse_sql;
parse_mysql: typeof parse_mysql;
parse_bigquery: typeof parse_bigquery;
parse_snowflake: typeof parse_snowflake;
parse_mssql: typeof parse_mssql;
parse_db_resource: typeof parse_db_resource;
parse_graphql: typeof parse_graphql;
parse_php: typeof parse_php;
parse_rust: typeof parse_rust
};
}
/** Gets if the Wasm module has been instantiated. */
export function isInstantiated(): boolean;
/** Options for instantiating a Wasm instance. */
export interface InstantiateOptions {
/** Optional url to the Wasm file to instantiate. */
url?: URL;
/** Callback to decompress the raw Wasm file bytes before instantiating. */
decompress?: (bytes: Uint8Array) => Uint8Array;
}
/** Instantiates an instance of the Wasm module returning its functions.
* @remarks It is safe to call this multiple times and once successfully
* loaded it will always return a reference to the same object. */
export function instantiate(opts?: InstantiateOptions): Promise<InstantiateResult["exports"]>;
/** Instantiates an instance of the Wasm module along with its exports.
* @remarks It is safe to call this multiple times and once successfully
* loaded it will always return a reference to the same object. */
export function instantiateWithInstance(opts?: InstantiateOptions): Promise<InstantiateResult>;
/**
* @param {string} code
* @returns {string}
*/
export function parse_deno(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_outputs(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_ts_imports(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_bash(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_powershell(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_go(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_python(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_sql(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_mysql(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_bigquery(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_snowflake(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_mssql(code: string): string;
/**
* @param {string} code
* @returns {string | undefined}
*/
export function parse_db_resource(code: string): string | undefined;
/**
* @param {string} code
* @returns {string}
*/
export function parse_graphql(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_php(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_rust(code: string): string;

File diff suppressed because it is too large Load Diff

View File

@@ -21,3 +21,6 @@ popd
pushd "pkg-rust" && npm publish ${args}
popd
pushd "pkg-yaml" && npm publish ${args}
popd

View File

@@ -17,8 +17,12 @@ fn wrap_sig(r: anyhow::Result<MainArgSignature>) -> String {
#[cfg(feature = "ts-parser")]
#[wasm_bindgen]
pub fn parse_deno(code: &str) -> String {
wrap_sig(windmill_parser_ts::parse_deno_signature(code, false, None))
pub fn parse_deno(code: &str, main_override: Option<String>) -> String {
wrap_sig(windmill_parser_ts::parse_deno_signature(
code,
false,
main_override,
))
}
#[cfg(feature = "ts-parser")]
@@ -65,8 +69,11 @@ pub fn parse_go(code: &str) -> String {
#[cfg(feature = "py-parser")]
#[wasm_bindgen]
pub fn parse_python(code: &str) -> String {
wrap_sig(windmill_parser_py::parse_python_signature(code, None))
pub fn parse_python(code: &str, main_override: Option<String>) -> String {
wrap_sig(windmill_parser_py::parse_python_signature(
code,
main_override,
))
}
#[cfg(feature = "sql-parser")]
@@ -122,3 +129,9 @@ pub fn parse_php(code: &str) -> String {
pub fn parse_rust(code: &str) -> String {
wrap_sig(windmill_parser_rust::parse_rust_signature(code))
}
#[cfg(feature = "ansible-parser")]
#[wasm_bindgen]
pub fn parse_ansible(code: &str) -> String {
wrap_sig(windmill_parser_yaml::parse_ansible_sig(code))
}

View File

@@ -139,7 +139,8 @@ export function main(test1?: string, test2: string = \"burkina\",
oidx: None
}
],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -215,7 +216,8 @@ export function main(test2 = \"burkina\",
oidx: None
}
],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -264,7 +266,8 @@ export function main(foo: FooBar, {a, b}: FooBar, {c, d}: FooBar = {a: \"foo\",
oidx: None
}
],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -294,7 +297,8 @@ export function main(foo: (\"foo\" | \"bar\")[]) {
has_default: false,
oidx: None
}],
no_main_func: Some(false)
no_main_func: Some(false),
has_preprocessor: Some(false)
}
);
@@ -434,6 +438,7 @@ Write-Output 'Testing...'
}
],
no_main_func: None,
has_preprocessor: None
}
);

View File

@@ -0,0 +1,15 @@
[package]
name = "windmill-parser-yaml"
version.workspace = true
authors.workspace = true
edition.workspace = true
[lib]
name = "windmill_parser_yaml"
path = "./src/lib.rs"
[dependencies]
yaml-rust.workspace = true
windmill-parser.workspace = true
anyhow.workspace = true
serde_json.workspace = true

View File

@@ -0,0 +1,482 @@
use anyhow::anyhow;
use serde_json::json;
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ};
use yaml_rust::{Yaml, YamlEmitter, YamlLoader};
pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result<MainArgSignature> {
let docs = YamlLoader::load_from_str(inner_content)
.map_err(|e| anyhow!("Failed to parse yaml: {}", e))?;
if docs.len() < 2 {
return Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![],
no_main_func: None,
has_preprocessor: None,
});
}
let mut args = vec![];
if let Yaml::Hash(doc) = &docs[0] {
for (key, value) in doc {
match key {
Yaml::String(key) if key == "extra_vars" => {
if let Yaml::Hash(v) = &value {
for (key, arg) in v {
if let Yaml::String(arg_name) = key {
// if not then it is a static resource or var, so skip
// it will be fetched by the worker
if let ArgTyp::Typ(typ) = parse_ansible_arg_typ(arg) {
let default = get_default_for_typ(&arg);
args.push(Arg {
name: arg_name.to_string(),
otyp: None,
typ,
has_default: default.is_some(),
default,
oidx: None,
})
}
}
}
}
}
Yaml::String(key) if key == "inventory" => {
for inv in parse_inventories(value)? {
if inv.pinned_resource.is_some() {
continue;
}
args.push(Arg {
name: inv.name,
otyp: None,
typ: Typ::Resource(inv.resource_type.unwrap_or("Unknown".to_string())),
has_default: inv.default.is_some(),
default: inv.default.map(|v| json!(format!("$res:{}", v))),
oidx: None,
})
}
}
_ => (),
}
}
}
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
}
fn get_default_for_typ(arg: &Yaml) -> Option<serde_json::Value> {
if let Yaml::Hash(arg) = arg {
if let Some(def) = arg.get(&Yaml::String("default".to_string())) {
if let Some(Yaml::String(typ)) = arg.get(&Yaml::String("type".to_string())) {
if let Yaml::String(path) = def {
if typ.as_str() == "windmill_resource" {
return Some(serde_json::Value::String(format!("$res:{}", path)));
}
}
}
return Some(yaml_to_json(def));
}
}
None
}
enum ArgTyp {
Typ(Typ),
StaticVar(String),
StaticResource(String),
}
fn parse_ansible_arg_typ(arg: &Yaml) -> ArgTyp {
if let Yaml::Hash(a) = arg {
if let Some(Yaml::String(typ)) = a.get(&Yaml::String("type".to_string())) {
if typ.as_str() == "windmill_variable" {
if let Some(Yaml::String(variable_path)) =
a.get(&Yaml::String("variable".to_string()))
{
return ArgTyp::StaticVar(variable_path.to_string());
}
}
if typ.as_str() == "windmill_resource" {
if let Some(Yaml::String(resource_path)) =
a.get(&Yaml::String("resource".to_string()))
{
return ArgTyp::StaticResource(resource_path.to_string());
}
}
}
}
ArgTyp::Typ(parse_ansible_typ(arg))
}
fn parse_ansible_typ(arg: &Yaml) -> Typ {
if let Yaml::Hash(arg) = arg {
if let Some(Yaml::String(typ)) = arg.get(&Yaml::String("type".to_string())) {
match typ.as_ref() {
"boolean" => Typ::Bool,
"integer" => Typ::Int,
"number" => Typ::Float,
"string" => {
if let Some(Yaml::String(fmt)) = arg.get(&Yaml::String("format".to_string())) {
match fmt.as_ref() {
"date-time" | "datetime" => Typ::Datetime,
"email" => Typ::Email,
_ => Typ::Str(None),
}
} else {
Typ::Str(None)
}
}
"object" => {
if let Some(Yaml::Hash(props)) =
arg.get(&Yaml::String("properties".to_string()))
{
let mut prop_vec = vec![];
for (key, value) in props {
if let Yaml::String(key) = key {
prop_vec.push(ObjectProperty {
key: key.clone(),
typ: Box::new(parse_ansible_typ(value)),
})
}
}
Typ::Object(prop_vec)
} else {
Typ::Object(vec![])
}
}
"array" => {
if let Some(items) = arg.get(&Yaml::String("items".to_string())) {
Typ::List(Box::new(parse_ansible_typ(items)))
} else {
Typ::List(Box::new(Typ::Unknown))
}
}
"windmill_resource" => {
if let Some(Yaml::String(res_name)) =
arg.get(&Yaml::String("resource_type".to_string()))
{
Typ::Resource(res_name.clone())
} else {
Typ::Resource("".to_string())
}
}
_ => Typ::Unknown,
}
} else {
Typ::Unknown
}
} else {
Typ::Unknown
}
}
#[derive(Debug, Clone)]
pub struct AnsiblePlaybookOptions {
// There are a lot more options
// TODO: Add the options as customers require them
// Add it here and then on the executors cmd_options
pub verbosity: Option<String>,
pub forks: Option<i64>,
pub timeout: Option<i64>,
pub flush_cache: Option<()>,
pub force_handlers: Option<()>,
}
#[derive(Debug, Clone)]
pub struct FileResource {
pub resource_path: String,
pub target_path: String,
}
#[derive(Debug, Clone)]
pub struct AnsibleInventory {
pub default: Option<String>,
pub name: String,
resource_type: Option<String>,
pub pinned_resource: Option<String>,
}
#[derive(Debug, Clone)]
pub struct AnsibleRequirements {
pub python_reqs: Vec<String>,
pub collections: Option<String>,
pub file_resources: Vec<FileResource>,
pub inventories: Vec<AnsibleInventory>,
pub vars: Vec<(String, String)>,
pub resources: Vec<(String, String)>,
pub options: AnsiblePlaybookOptions,
}
fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result<Vec<AnsibleInventory>> {
if let Yaml::Array(arr) = inventory_yaml {
let mut ret = vec![];
for (i, inv) in arr.iter().enumerate() {
if let Yaml::Hash(inv) = inv {
let resource_type = inv
.get(&Yaml::String("resource_type".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let windmill_path = inv
.get(&Yaml::String("default".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let name = if i == 0 {
"inventory.ini".to_string()
} else {
format!("inventory-{}.ini", i)
};
let name = inv
.get(&Yaml::String("name".to_string()))
.and_then(|v| v.as_str())
.unwrap_or(name.as_str())
.to_string();
let pin = inv
.get(&Yaml::String("resource".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
ret.push(AnsibleInventory {
default: windmill_path,
name,
resource_type,
pinned_resource: pin,
});
}
}
return Ok(ret);
}
return Err(anyhow!("Invalid inventory definition"));
}
pub fn parse_ansible_reqs(
inner_content: &str,
) -> anyhow::Result<(String, Option<AnsibleRequirements>, String)> {
let mut logs = String::new();
let docs = YamlLoader::load_from_str(inner_content)
.map_err(|e| anyhow!("Failed to parse yaml: {}", e))?;
if docs.len() < 2 {
return Ok((logs, None, inner_content.to_string()));
}
let opts = AnsiblePlaybookOptions {
verbosity: None,
forks: None,
timeout: None,
flush_cache: None,
force_handlers: None,
};
let mut ret = AnsibleRequirements {
python_reqs: vec![],
collections: None,
file_resources: vec![],
inventories: vec![],
vars: vec![],
resources: vec![],
options: opts,
};
if let Yaml::Hash(doc) = &docs[0] {
for (key, value) in doc {
match key {
Yaml::String(key) if key == "dependencies" => {
if let Yaml::Hash(deps) = value {
if let Some(galaxy_requirements) =
deps.get(&Yaml::String("galaxy".to_string()))
{
let mut out_str = String::new();
let mut emitter = YamlEmitter::new(&mut out_str);
emitter.dump(galaxy_requirements)?;
ret.collections = Some(out_str);
}
if let Some(Yaml::Array(py_reqs)) =
deps.get(&Yaml::String("python".to_string()))
{
ret.python_reqs = py_reqs
.iter()
.map(|d| d.as_str().map(|s| s.to_string()))
.filter_map(|x| x)
.collect();
}
}
}
Yaml::String(key) if key == "file_resources" => {
if let Yaml::Array(file_resources) = value {
let resources: anyhow::Result<Vec<FileResource>> =
file_resources.iter().map(parse_file_resource).collect();
ret.file_resources.append(&mut resources?);
}
}
Yaml::String(key) if key == "extra_vars" => {
if let Yaml::Hash(v) = &value {
for (key, arg) in v {
if let Yaml::String(arg_name) = key {
match parse_ansible_arg_typ(arg) {
ArgTyp::StaticVar(p) => {
ret.vars
.push((arg_name.to_string(), format!("$var:{}", p)));
}
ArgTyp::StaticResource(p) => {
ret.resources
.push((arg_name.to_string(), format!("$res:{}", p)));
}
ArgTyp::Typ(_) => (),
}
}
}
}
}
Yaml::String(key) if key == "inventory" => {
ret.inventories = parse_inventories(value)?;
}
Yaml::String(key) if key == "options" => {
if let Yaml::Array(opts) = &value {
ret.options = parse_ansible_options(opts);
}
}
Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)),
_ => (),
}
}
}
let mut out_str = String::new();
let mut emitter = YamlEmitter::new(&mut out_str);
for i in 1..docs.len() {
emitter.dump(&docs[i])?;
}
Ok((logs, Some(ret), out_str))
}
fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
let mut ret = AnsiblePlaybookOptions {
verbosity: None,
forks: None,
timeout: None,
flush_cache: None,
force_handlers: None,
};
for opt in opts {
if let Yaml::String(o) = opt {
match o.as_str() {
"flush_cache" => {
ret.flush_cache = Some(());
}
"force_handlers" => {
ret.force_handlers = Some(());
}
s if count_consecutive_vs(s) > 0 => {
ret.verbosity = Some("v".repeat(count_consecutive_vs(s).min(6)));
}
_ => (),
}
}
if let Yaml::Hash(m) = opt {
if let Some((Yaml::String(name), value)) = m.iter().last() {
match name.as_str() {
"forks" => {
if let Yaml::Integer(count) = value {
ret.forks = Some(*count);
}
}
"timeout" => {
if let Yaml::Integer(timeout) = value {
ret.timeout = Some(*timeout);
}
}
"verbosity" => {
if let Yaml::String(verbosity) = value {
let c = count_consecutive_vs(verbosity);
if c > 0 && c <= 6 {
ret.verbosity = Some("v".repeat(c.min(6)));
}
}
}
_ => ()
}
}
}
}
ret
}
fn count_consecutive_vs(s: &str) -> usize {
let mut max_count = 0;
let mut current_count = 0;
for c in s.chars() {
if c == 'v' {
current_count += 1;
if current_count == 6 {
return 6; // Stop early if we reach 6
}
} else {
current_count = 0; // Reset count if the character is not 'v'
}
max_count = max_count.max(current_count);
}
max_count
}
fn parse_file_resource(yaml: &Yaml) -> anyhow::Result<FileResource> {
if let Yaml::Hash(f) = yaml {
if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("resource".to_string())) {
let target_path = f
.get(&Yaml::String("target".to_string()))
.and_then(|x| x.as_str())
.map(|x| x.to_string())
.ok_or(anyhow!(
"No `target` provided for file resource {}. Please input a target relative path for the ansible playbook to see this file.",
resource_path
))?;
return Ok(FileResource { resource_path: resource_path.clone(), target_path });
}
return Err(anyhow!(
"File resource should have a `resource` field, linking to a text file resource"
));
}
return Err(anyhow!("Invalid file resource: Should be a dictionary."));
}
fn yaml_to_json(yaml: &Yaml) -> serde_json::Value {
match yaml {
Yaml::Array(arr) => {
let json_array: Vec<serde_json::Value> = arr.into_iter().map(yaml_to_json).collect();
serde_json::Value::Array(json_array)
}
Yaml::Hash(hash) => {
let json_object = hash
.into_iter()
.map(|(k, v)| {
let key = match k {
Yaml::String(s) => s.clone(),
_ => k.as_str().unwrap_or("").to_string(),
};
(key, yaml_to_json(v))
})
.collect();
serde_json::Value::Object(json_object)
}
Yaml::String(s) => serde_json::Value::String(s.to_string()),
Yaml::Integer(i) => serde_json::Value::Number(i.clone().into()),
Yaml::Real(r) => serde_json::Value::Number(r.parse().unwrap_or(0.into())),
Yaml::Boolean(b) => serde_json::Value::Bool(*b),
Yaml::Null => serde_json::Value::Null,
_ => serde_json::Value::Null,
}
}

View File

@@ -16,6 +16,7 @@ pub struct MainArgSignature {
pub star_kwargs: bool,
pub args: Vec<Arg>,
pub no_main_func: Option<bool>,
pub has_preprocessor: Option<bool>,
}
#[derive(Serialize, Clone, Debug, PartialEq)]
@@ -153,7 +154,10 @@ mod test {
#[test]
fn test_mixed_delimiters() {
assert_eq!(to_snake_case("test-Case_with Spaces"), "test_case_with_spaces");
assert_eq!(
to_snake_case("test-Case_with Spaces"),
"test_case_with_spaces"
);
}
#[test]

View File

@@ -8,7 +8,10 @@
use anyhow::Context;
use git_version::git_version;
use monitor::{send_current_log_file_to_object_store, send_logs_to_object_store};
use monitor::{
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::{
@@ -35,7 +38,7 @@ use windmill_common::{
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
scripts::ScriptLang,
stats_ee::schedule_stats,
@@ -74,7 +77,7 @@ use crate::monitor::{
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting,
reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting,
reload_retention_period_setting, reload_scim_token_setting, reload_server_config,
reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config,
reload_worker_config,
};
@@ -173,7 +176,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
None,
&job_dir,
"cache_init",
windmill_worker::get_common_bun_proc_envs("").await,
windmill_worker::get_common_bun_proc_envs(None).await,
false,
)
.await?;
@@ -371,6 +374,7 @@ async fn windmill_main() -> anyhow::Result<()> {
// migration code to avoid break
windmill_api::migrate_db(&db).await?;
}
let (killpill_tx, mut killpill_rx) = tokio::sync::broadcast::channel::<()>(2);
let mut monitor_killpill_rx = killpill_tx.subscribe();
let server_killpill_rx = killpill_tx.subscribe();
@@ -562,9 +566,7 @@ Windmill Community Edition {GIT_VERSION}
"notify_config_change" => {
match n.payload() {
"server" if server_mode => {
tracing::info!("Server config change detected: {}", n.payload());
reload_server_config(&db).await;
tracing::error!("Server config change detected but server config is obsolete: {}", n.payload());
},
a@ _ if worker_mode && a == format!("worker__{}", *WORKER_GROUP) => {
tracing::info!("Worker config change detected: {}", n.payload());
@@ -608,6 +610,12 @@ Windmill Community Edition {GIT_VERSION}
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
}
}
SMTP_SETTING => {
reload_smtp_config(&db).await;
},
TIMEOUT_WAIT_RESULT_SETTING => {
reload_timeout_wait_result_setting(&db).await
},
RETENTION_PERIOD_SECS_SETTING => {
reload_retention_period_setting(&db).await
},

View File

@@ -22,6 +22,7 @@ use tokio::{
#[cfg(feature = "embedding")]
use windmill_api::embeddings::update_embeddings_db;
use windmill_api::{
jobs::TIMEOUT_WAIT_RESULT,
oauth2_ee::{build_oauth_clients, OAuthClient},
DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT, SAML_METADATA, SCIM_TOKEN,
};
@@ -40,17 +41,17 @@ use windmill_common::{
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
jobs::QueuedJob,
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
server::load_server_config,
server::load_smtp_config,
tracing_init::JSON_FMT,
users::truncate_token,
utils::{now_from_db, rd_string, report_critical_error, Mode},
worker::{
load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting,
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SMTP_CONFIG, WORKER_CONFIG,
WORKER_GROUP,
},
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
@@ -172,8 +173,9 @@ pub async fn initial_load(
reload_s3_cache_setting(&db).await;
}
reload_smtp_config(&db).await;
if server_mode {
reload_server_config(&db).await;
reload_retention_period_setting(&db).await;
reload_request_size(&db).await;
reload_saml_metadata_setting(&db).await;
@@ -698,6 +700,15 @@ pub async fn reload_scim_token_setting(db: &DB) {
.await;
}
pub async fn reload_timeout_wait_result_setting(db: &DB) {
reload_option_setting_with_tracing(
db,
TIMEOUT_WAIT_RESULT_SETTING,
"TIMEOUT_WAIT_RESULT",
TIMEOUT_WAIT_RESULT.clone(),
)
.await;
}
pub async fn reload_saml_metadata_setting(db: &DB) {
reload_option_setting_with_tracing(
db,
@@ -1045,7 +1056,7 @@ pub async fn monitor_db(
let worker_groups_alerts_f = async {
#[cfg(feature = "enterprise")]
if server_mode {
if server_mode && !initial_load {
worker_groups_alerts(&db).await;
}
};
@@ -1129,14 +1140,14 @@ pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
}
}
pub async fn reload_server_config(db: &Pool<Postgres>) {
let config = load_server_config(&db).await;
if let Err(e) = config {
tracing::error!("Error reloading server config: {:?}", e)
pub async fn reload_smtp_config(db: &Pool<Postgres>) {
let smtp_config = load_smtp_config(&db).await;
if let Err(e) = smtp_config {
tracing::error!("Error reloading smtp config: {:?}", e)
} else {
let mut wc = SERVER_CONFIG.write().await;
tracing::info!("Reloading server config...");
*wc = config.unwrap()
let mut wc = SMTP_CONFIG.write().await;
tracing::info!("Reloading smtp config...");
*wc = smtp_config.unwrap()
}
}

View File

@@ -1912,6 +1912,109 @@ def main():
assert_eq!(result, serde_json::json!("test-workspace"));
}
#[sqlx::test(fixtures("base"))]
async fn test_bun_job_datetime(db: Pool<Postgres>) {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await;
let port = server.addr.port();
let content = r#"
export async function main(a: Date) {
return typeof a;
}
"#
.to_owned();
let result = RunJob::from(JobPayload::Code(RawCode {
hash: None,
content,
path: None,
lock: None,
language: ScriptLang::Bun,
custom_concurrency_key: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None,
dedicated_worker: None,
}))
.arg("a", json!("2024-09-24T10:00:00.000Z"))
.run_until_complete(&db, port)
.await
.json_result()
.unwrap();
assert_eq!(result, serde_json::json!("object"));
}
#[sqlx::test(fixtures("base"))]
async fn test_deno_job_datetime(db: Pool<Postgres>) {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await;
let port = server.addr.port();
let content = r#"
export async function main(a: Date) {
return typeof a;
}
"#
.to_owned();
let result = RunJob::from(JobPayload::Code(RawCode {
hash: None,
content,
path: None,
lock: None,
language: ScriptLang::Deno,
custom_concurrency_key: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None,
dedicated_worker: None,
}))
.arg("a", json!("2024-09-24T10:00:00.000Z"))
.run_until_complete(&db, port)
.await
.json_result()
.unwrap();
assert_eq!(result, serde_json::json!("object"));
}
#[sqlx::test(fixtures("base"))]
async fn test_python_job_datetime_and_bytes(db: Pool<Postgres>) {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await;
let port = server.addr.port();
let content = r#"
from datetime import datetime
def main(a: datetime, b: bytes):
return (isinstance(a, datetime), isinstance(b, bytes))
"#
.to_owned();
let result = RunJob::from(JobPayload::Code(RawCode {
hash: None,
content,
path: None,
lock: None,
language: ScriptLang::Python3,
custom_concurrency_key: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None,
dedicated_worker: None,
}))
.arg("a", json!("2024-09-24T10:00:00.000Z"))
.arg("b", json!("dGVzdA=="))
.run_until_complete(&db, port)
.await
.json_result()
.unwrap();
assert_eq!(result, serde_json::json!([true, true]));
}
#[sqlx::test(fixtures("base"))]
async fn test_empty_loop_1(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2734,7 +2837,8 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
language: windmill_api_client::types::RawScriptLanguage::Go | windmill_api_client::types::RawScriptLanguage::Python3 | windmill_api_client::types::RawScriptLanguage::Deno,
lock: Some(ref lock),
..
}) if lock.len() > 0)
}) if lock.len() > 0),
"{:?}", m.value
);
});
}
@@ -3293,6 +3397,7 @@ async fn run_deployed_relative_imports(
visible_to_runner_only: None,
no_main_func: None,
codebase: None,
has_preprocessor: None,
},
)
.await
@@ -3323,6 +3428,7 @@ async fn run_deployed_relative_imports(
dedicated_worker: None,
language,
priority: None,
apply_preprocessor: false,
})
.push(&db2)
.await;

View File

@@ -94,6 +94,7 @@ object_store = { workspace = true, optional = true}
openidconnect = { workspace = true, optional = true}
url = { workspace = true, optional = true}
jsonwebtoken = { workspace = true }
matchit.workspace = true
pin-project.workspace = true
crc.workspace = true

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.394.2
version: 1.399.0
title: Windmill API
contact:
@@ -742,6 +742,35 @@ paths:
schema:
type: string
/settings/test_critical_channels:
post:
summary: test critical channels
operationId: testCriticalChannels
tags:
- setting
requestBody:
description: test critical channel payload
required: true
content:
application/json:
schema:
type: array
items:
type: object
properties:
email:
type: string
slack_channel:
type: string
responses:
"200":
description: status
content:
text/plain:
schema:
type: string
/settings/test_license_key:
post:
summary: test license key
@@ -3088,6 +3117,22 @@ paths:
schema:
type: string
/w/{workspace}/resources/file_resource_type_to_file_ext_map:
get:
summary: get map from resource type to format extension
operationId: fileResourceTypeToFileExtMap
tags:
- resource
parameters:
- $ref: "#/components/parameters/WorkspaceId"
responses:
"200":
description: map from resource type to file ext
content:
application/json:
schema: {}
/w/{workspace}/resources/type/delete/{path}:
delete:
summary: delete resource_type
@@ -4166,6 +4211,11 @@ paths:
in: query
schema:
type: integer
- name: skip_preprocessor
description: skip the preprocessor
in: query
schema:
type: boolean
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/WorkerTag"
- $ref: "#/components/parameters/CacheTtl"
@@ -5312,6 +5362,11 @@ paths:
in: query
schema:
type: integer
- name: skip_preprocessor
description: skip the preprocessor
in: query
schema:
type: boolean
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/WorkerTag"
- $ref: "#/components/parameters/NewJobId"
@@ -5420,6 +5475,11 @@ paths:
in: query
schema:
type: integer
- name: skip_preprocessor
description: skip the preprocessor
in: query
schema:
type: boolean
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/WorkerTag"
- $ref: "#/components/parameters/CacheTtl"
@@ -5993,6 +6053,10 @@ paths:
in: query
schema:
type: integer
- name: get_progress
in: query
schema:
type: boolean
responses:
"200":
@@ -6012,6 +6076,8 @@ paths:
type: integer
mem_peak:
type: integer
progress:
type: integer
flow_status:
$ref: "#/components/schemas/WorkflowStatusRecord"
@@ -6800,6 +6866,190 @@ paths:
"201":
description: default error handler set
/w/{workspace}/http_triggers/create:
post:
summary: create http trigger
operationId: createHttpTrigger
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: new http trigger
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewHttpTrigger"
responses:
"201":
description: http trigger created
content:
text/plain:
schema:
type: string
/w/{workspace}/http_triggers/update/{path}:
post:
summary: update http trigger
operationId: updateHttpTrigger
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
description: updated trigger
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EditHttpTrigger"
responses:
"200":
description: http trigger updated
content:
text/plain:
schema:
type: string
/w/{workspace}/http_triggers/delete/{path}:
delete:
summary: delete http trigger
operationId: deleteHttpTrigger
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: http trigger deleted
content:
text/plain:
schema:
type: string
/w/{workspace}/http_triggers/get/{path}:
get:
summary: get http trigger
operationId: getHttpTrigger
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: http trigger deleted
content:
application/json:
schema:
$ref: "#/components/schemas/HttpTrigger"
/w/{workspace}/http_triggers/list:
get:
summary: list http triggers
operationId: listHttpTriggers
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
required: true
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
- name: path
description: filter by path
in: query
schema:
type: string
- name: is_flow
in: query
schema:
type: boolean
- name: path_start
in: query
schema:
type: string
responses:
"200":
description: http trigger list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/HttpTrigger"
/w/{workspace}/http_triggers/exists/{path}:
get:
summary: does http trigger exists
operationId: existsHttpTrigger
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: http trigger exists
content:
application/json:
schema:
type: boolean
/w/{workspace}/http_triggers/route_exists:
post:
summary: does route exists
operationId: existsRoute
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: route exists request
required: true
content:
application/json:
schema:
type: object
properties:
route_path:
type: string
http_method:
type: string
enum: ["get", "post", "put", "delete", "patch"]
required:
- kind
- route_path
- http_method
responses:
"200":
description: route exists
content:
application/json:
schema:
type: boolean
/w/{workspace}/http_triggers/used:
get:
summary: whether http triggers are used
operationId: used
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
responses:
"200":
description: whether http triggers are used
content:
application/json:
schema:
type: boolean
/groups/list:
get:
summary: list instance groups
@@ -7626,6 +7876,7 @@ paths:
folder,
app,
raw_app,
http_trigger,
]
responses:
"200":
@@ -7662,6 +7913,7 @@ paths:
folder,
app,
raw_app,
http_trigger,
]
requestBody:
description: acl to add
@@ -7709,6 +7961,7 @@ paths:
folder,
app,
raw_app,
http_trigger,
]
requestBody:
description: acl to add
@@ -8347,6 +8600,38 @@ paths:
application/json:
schema: {}
/w/{workspace}/job_helpers/load_table_count/{path}:
get:
summary: Load the table row count
operationId: loadTableRowCount
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
- name: search_col
in: query
schema:
type: string
- name: search_term
in: query
schema:
type: string
- name: storage
in: query
schema:
type: string
responses:
"200":
description: Table count
content:
application/json:
schema:
type: object
properties:
count:
type: number
/w/{workspace}/job_helpers/load_csv_preview/{path}:
get:
summary: Load a preview of a csv file
@@ -8615,6 +8900,52 @@ paths:
items:
$ref: "#/components/schemas/TimeseriesMetric"
/w/{workspace}/job_metrics/set_progress/{id}:
post:
summary: set job metrics
operationId: setJobProgress
tags:
- metrics
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
requestBody:
description: parameters for statistics retrieval
required: true
content:
application/json:
schema:
type: object
properties:
percent:
type: integer
flow_job_id:
type: string
format: uuid
responses:
"200":
description: Job progress updated
content:
application/json:
schema: {}
/w/{workspace}/job_metrics/get_progress/{id}:
get:
summary: get job progress
operationId: getJobProgress
tags:
- metrics
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
responses:
"200":
description: job progress between 0 and 99
content:
application/json:
schema:
type: integer
/service_logs/list_files:
get:
summary: list log files ordered by timestamp
@@ -8679,7 +9010,6 @@ paths:
schema:
type: string
/concurrency_groups/list:
get:
summary: List all concurrency groups
@@ -9279,6 +9609,7 @@ components:
bun,
php,
rust,
ansible,
]
kind:
type: string
@@ -9321,6 +9652,8 @@ components:
type: boolean
codebase:
type: string
has_preprocessor:
type: boolean
required:
- hash
@@ -9338,6 +9671,7 @@ components:
- kind
- starred
- no_main_func
- has_preprocessor
NewScript:
@@ -9378,6 +9712,7 @@ components:
bun,
php,
rust,
ansible,
]
kind:
type: string
@@ -9418,6 +9753,8 @@ components:
type: boolean
codebase:
type: string
has_preprocessor:
type: boolean
required:
- path
- summary
@@ -9596,6 +9933,7 @@ components:
bun,
php,
rust,
ansible,
]
email:
type: string
@@ -9712,6 +10050,7 @@ components:
bun,
php,
rust,
ansible,
]
is_skipped:
type: boolean
@@ -10216,6 +10555,9 @@ components:
no_main_func:
type: boolean
nullable: true
has_preprocessor:
type: boolean
nullable: true
required:
- star_args
- start_kwargs
@@ -10223,6 +10565,7 @@ components:
- type
- error
- no_main_func
- has_preprocessor
Preview:
type: object
@@ -10252,6 +10595,7 @@ components:
bun,
php,
rust,
ansible,
]
tag:
type: string
@@ -10399,6 +10743,8 @@ components:
edited_at:
type: string
format: date-time
format_extension:
type: string
required:
- name
@@ -10609,6 +10955,122 @@ components:
- is_flow
- args
HttpTrigger:
type: object
properties:
path:
type: string
edited_by:
type: string
edited_at:
type: string
format: date-time
script_path:
type: string
route_path:
type: string
is_flow:
type: boolean
extra_perms:
type: object
additionalProperties:
type: boolean
email:
type: string
workspace_id:
type: string
http_method:
type: string
enum:
- get
- post
- put
- delete
- patch
is_async:
type: boolean
requires_auth:
type: boolean
required:
- path
- edited_by
- edited_at
- script_path
- route_path
- extra_perms
- is_flow
- email
- workspace_id
- is_async
- requires_auth
- http_method
NewHttpTrigger:
type: object
properties:
path:
type: string
script_path:
type: string
route_path:
type: string
is_flow:
type: boolean
http_method:
type: string
enum:
- get
- post
- put
- delete
- patch
is_async:
type: boolean
requires_auth:
type: boolean
required:
- path
- script_path
- route_path
- is_flow
- is_async
- requires_auth
- http_method
EditHttpTrigger:
type: object
properties:
path:
type: string
script_path:
type: string
route_path:
type: string
is_flow:
type: boolean
http_method:
type: string
enum:
- get
- post
- put
- delete
- patch
is_async:
type: boolean
requires_auth:
type: boolean
required:
- path
- script_path
- is_flow
- kind
- is_async
- requires_auth
- http_method
Group:
type: object
properties:
@@ -11395,6 +11857,7 @@ components:
bun,
php,
rust,
ansible,
]
required:
- raw_code

View File

@@ -609,7 +609,7 @@ async fn create_app(
}
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM raw_app WHERE path = $1 AND workspace_id = $2)",
"SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)",
&app.path,
w_id
)

View File

@@ -144,7 +144,7 @@ async fn get_concurrent_intervals(
Query(iq): Query<ExtendedJobsParams>,
Query(lq): Query<ListCompletedQuery>,
) -> JsonResult<ExtendedJobs> {
check_scopes(&authed, || format!("listjobs"))?;
check_scopes(&authed, || format!("jobs:listjobs"))?;
if lq.success.is_some() && lq.running.is_some_and(|x| x) {
return Err(error::Error::BadRequest(

View File

@@ -1258,6 +1258,7 @@ mod tests {
delete_after_use: None,
continue_on_error: None,
})),
preprocessor_module: None,
same_worker: false,
concurrent_limit: None,
concurrency_time_window_s: None,
@@ -1330,7 +1331,8 @@ mod tests {
"expr": "previous.isEmpty()",
"skip_if_stopped": false
}
}
},
"preprocessor_module": Option::<String>::None
});
assert_eq!(dbg!(serde_json::json!(fv)), dbg!(expect));
}

View File

@@ -23,6 +23,19 @@ use windmill_common::{
utils::{not_found_if_none, StripPath},
};
const KINDS: [&str; 10] = [
"script",
"group_",
"resource",
"schedule",
"variable",
"flow",
"folder",
"app",
"raw_app",
"http_trigger",
];
pub fn workspaced_service() -> Router {
Router::new()
.route("/get/*path", get(get_granular_acls))
@@ -49,6 +62,11 @@ async fn add_granular_acl(
let (kind, path) = path
.split_once('/')
.ok_or_else(|| Error::BadRequest("Invalid path or kind".to_string()))?;
if !KINDS.contains(&kind) {
return Err(Error::BadRequest("Invalid kind".to_string()));
}
let mut tx = user_db.begin(&authed).await?;
let identifier = if kind == "group_" || kind == "folder" {
@@ -69,9 +87,10 @@ async fn add_granular_acl(
}
let obj_o = sqlx::query_scalar::<_, serde_json::Value>(&format!(
"UPDATE {kind} SET extra_perms = jsonb_set(extra_perms, '{{\"{owner}\"}}', to_jsonb($1), \
true) WHERE {identifier} = $2 AND workspace_id = $3 RETURNING extra_perms"
"UPDATE {kind} SET extra_perms = jsonb_set(extra_perms, $1, to_jsonb($2), \
true) WHERE {identifier} = $3 AND workspace_id = $4 RETURNING extra_perms"
))
.bind(vec![owner])
.bind(write.unwrap_or(false))
.bind(path)
.bind(&w_id)
@@ -158,6 +177,10 @@ async fn remove_granular_acl(
.split_once('/')
.ok_or_else(|| Error::BadRequest("Invalid path or kind".to_string()))?;
if !KINDS.contains(&kind) {
return Err(Error::BadRequest("Invalid kind".to_string()));
}
if !authed.is_admin {
if kind == "folder" {
crate::folders::require_is_owner(&authed, path)?;
@@ -267,6 +290,10 @@ async fn get_granular_acls(
.split_once('/')
.ok_or_else(|| Error::BadRequest("Invalid path or kind".to_string()))?;
if !KINDS.contains(&kind) {
return Err(Error::BadRequest("Invalid kind".to_string()));
}
let mut tx = user_db.begin(&authed).await?;
let identifier = if kind == "group_" { "name" } else { "path" };

View File

@@ -0,0 +1,651 @@
use axum::{
extract::{Path, Query},
response::IntoResponse,
routing::{delete, get, post},
Extension, Json, Router,
};
use http::{HeaderMap, StatusCode};
use serde::{Deserialize, Serialize};
use sql_builder::{bind::Bind, SqlBuilder};
use sqlx::prelude::FromRow;
use std::collections::HashMap;
use tower_http::cors::CorsLayer;
use windmill_audit::{audit_ee::audit_log, ActionKind};
use windmill_common::{
auth::fetch_authed_from_permissioned_as,
db::UserDB,
error::{self, JsonResult},
users::username_to_permissioned_as,
utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath},
worker::{to_raw_value, CLOUD_HOSTED},
};
use windmill_queue::PushArgsOwned;
use crate::{
db::{ApiAuthed, DB},
jobs::{
run_flow_by_path_inner, run_script_by_path_inner, run_wait_result_flow_by_path_internal,
run_wait_result_script_by_path_internal, RunJobQuery,
},
users::OptAuthed,
};
lazy_static::lazy_static! {
static ref ROUTE_PATH_KEY_RE: regex::Regex = regex::Regex::new(r"/:\w+").unwrap();
}
pub fn routes_global_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([
http::Method::GET,
http::Method::POST,
http::Method::DELETE,
http::Method::PUT,
http::Method::PATCH,
])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(tower_http::cors::Any);
Router::new()
.route(
"/*path",
get(route_job)
.post(route_job)
.delete(route_job)
.put(route_job)
.patch(route_job)
.head(|| async { "" }),
)
.layer(cors)
}
pub fn workspaced_service() -> Router {
Router::new()
.route("/create", post(create_trigger))
.route("/list", get(list_triggers))
.route("/get/*path", get(get_trigger))
.route("/update/*path", post(update_trigger))
.route("/delete/*path", delete(delete_trigger))
.route("/exists/*path", get(exists_trigger))
.route("/used", get(used))
.route("/route_exists", post(exists_route))
}
#[derive(Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "HTTP_METHOD", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
impl From<HttpMethod> for http::Method {
fn from(method: HttpMethod) -> Self {
match method {
HttpMethod::Get => http::Method::GET,
HttpMethod::Post => http::Method::POST,
HttpMethod::Put => http::Method::PUT,
HttpMethod::Delete => http::Method::DELETE,
HttpMethod::Patch => http::Method::PATCH,
}
}
}
#[derive(Deserialize)]
struct NewTrigger {
path: String,
route_path: String,
script_path: String,
is_flow: bool,
is_async: bool,
requires_auth: bool,
http_method: HttpMethod,
}
#[derive(FromRow, Serialize)]
struct Trigger {
workspace_id: String,
path: String,
route_path: String,
route_path_key: String,
script_path: String,
is_flow: bool,
edited_by: String,
email: String,
edited_at: chrono::DateTime<chrono::Utc>,
extra_perms: serde_json::Value,
is_async: bool,
requires_auth: bool,
http_method: HttpMethod,
}
#[derive(Deserialize)]
struct EditTrigger {
path: String,
route_path: Option<String>,
script_path: String,
is_flow: bool,
is_async: bool,
requires_auth: bool,
http_method: HttpMethod,
}
#[derive(Deserialize)]
pub struct ListTriggerQuery {
pub page: Option<usize>,
pub per_page: Option<usize>,
pub path: Option<String>,
pub is_flow: Option<bool>,
pub path_start: Option<String>,
}
async fn list_triggers(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(lst): Query<ListTriggerQuery>,
) -> error::JsonResult<Vec<Trigger>> {
let mut tx = user_db.begin(&authed).await?;
let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page });
let mut sqlb = SqlBuilder::select_from("http_trigger")
.field("*")
.order_by("edited_at", true)
.and_where("workspace_id = ?".bind(&w_id))
.offset(offset)
.limit(per_page)
.clone();
if let Some(path) = lst.path {
sqlb.and_where_eq("script_path", "?".bind(&path));
}
if let Some(is_flow) = lst.is_flow {
sqlb.and_where_eq("is_flow", "?".bind(&is_flow));
}
if let Some(path_start) = &lst.path_start {
sqlb.and_where_like_left("path", path_start);
}
let sql = sqlb
.sql()
.map_err(|e| error::Error::InternalErr(e.to_string()))?;
let rows = sqlx::query_as::<_, Trigger>(&sql)
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(rows))
}
async fn get_trigger(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> error::JsonResult<Trigger> {
let mut tx = user_db.begin(&authed).await?;
let path = path.to_path();
let trigger = sqlx::query_as!(
Trigger,
r#"SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as "http_method: _", edited_by, email, edited_at, extra_perms, is_async, requires_auth
FROM http_trigger
WHERE workspace_id = $1 AND path = $2"#,
w_id,
path,
)
.fetch_optional(&mut *tx)
.await?;
tx.commit().await?;
let trigger = not_found_if_none(trigger, "Trigger", path)?;
Ok(Json(trigger))
}
async fn create_trigger(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Json(ct): Json<NewTrigger>,
) -> error::Result<(StatusCode, String)> {
require_admin(authed.is_admin, &authed.username)?;
let route_path_key = ROUTE_PATH_KEY_RE.replace_all(ct.route_path.as_str(), ":key");
let mut tx = user_db.begin(&authed).await?;
sqlx::query!(
"INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now())",
w_id,
ct.path,
ct.route_path,
&route_path_key,
ct.script_path,
ct.is_flow,
ct.is_async,
ct.requires_auth,
ct.http_method as HttpMethod,
&authed.username,
&authed.email
)
.execute(&mut *tx).await?;
audit_log(
&mut *tx,
&authed,
"http_triggers.create",
ActionKind::Create,
&w_id,
Some(ct.path.as_str()),
None,
)
.await?;
tx.commit().await?;
Ok((StatusCode::CREATED, format!("{}", ct.path)))
}
async fn update_trigger(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
Json(ct): Json<EditTrigger>,
) -> error::Result<String> {
let path = path.to_path();
let mut tx = user_db.begin(&authed).await?;
if authed.is_admin {
if ct.route_path.is_none() {
return Err(error::Error::BadRequest(
"route_path is required".to_string(),
));
}
let route_path_key =
ROUTE_PATH_KEY_RE.replace_all(ct.route_path.as_ref().unwrap().as_str(), ":key");
sqlx::query!(
"UPDATE http_trigger
SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, edited_by = $7, email = $8, is_async = $9, requires_auth = $10, edited_at = now()
WHERE workspace_id = $11 AND path = $12",
ct.route_path,
&route_path_key,
ct.script_path,
ct.path,
ct.is_flow,
ct.http_method as HttpMethod,
&authed.username,
&authed.email,
ct.is_async,
ct.requires_auth,
w_id,
path,
)
.execute(&mut *tx).await?;
} else {
sqlx::query!(
"UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, edited_by = $5, email = $6, is_async = $7, requires_auth = $8, edited_at = now()
WHERE workspace_id = $9 AND path = $10",
ct.script_path,
ct.path,
ct.is_flow,
ct.http_method as HttpMethod,
&authed.username,
&authed.email,
ct.is_async,
ct.requires_auth,
w_id,
path,
)
.execute(&mut *tx).await?;
}
audit_log(
&mut *tx,
&authed,
"http_triggers.update",
ActionKind::Create,
&w_id,
Some(path),
None,
)
.await?;
tx.commit().await?;
Ok(path.to_string())
}
async fn delete_trigger(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> error::Result<String> {
require_admin(authed.is_admin, &authed.username)?;
let path = path.to_path();
let mut tx = user_db.begin(&authed).await?;
sqlx::query!(
"DELETE FROM http_trigger WHERE workspace_id = $1 AND path = $2",
w_id,
path,
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed,
"http_triggers.delete",
ActionKind::Delete,
&w_id,
Some(path),
None,
)
.await?;
tx.commit().await?;
Ok(format!("HTTP trigger {path} deleted"))
}
async fn used(Extension(db): Extension<DB>, Path(w_id): Path<String>) -> JsonResult<bool> {
let used = sqlx::query_scalar!(
r#"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1)"#,
w_id,
)
.fetch_one(&db)
.await?
.unwrap_or(false);
Ok(Json(used))
}
async fn exists_trigger(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<bool> {
let path = path.to_path();
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE path = $1 AND workspace_id = $2)",
path,
w_id,
)
.fetch_one(&db)
.await?
.unwrap_or(false);
Ok(Json(exists))
}
#[derive(Deserialize)]
struct RouteExists {
route_path: String,
http_method: HttpMethod,
}
async fn exists_route(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
Json(RouteExists { route_path, http_method }): Json<RouteExists>,
) -> JsonResult<bool> {
let route_path_key = ROUTE_PATH_KEY_RE.replace_all(route_path.as_str(), ":key");
let exists = if *CLOUD_HOSTED {
sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND workspace_id = $2 AND http_method = $3)",
&route_path_key,
w_id,
http_method as HttpMethod
)
.fetch_one(&db)
.await?
.unwrap_or(false)
} else {
sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND http_method = $2)",
&route_path_key,
http_method as HttpMethod
)
.fetch_one(&db)
.await?
.unwrap_or(false)
};
Ok(Json(exists))
}
struct TriggerRoute {
path: String,
script_path: String,
is_flow: bool,
route_path: String,
workspace_id: String,
is_async: bool,
requires_auth: bool,
edited_by: String,
email: String,
http_method: HttpMethod,
}
async fn fetch_api_authed(
username: String,
email: String,
w_id: &str,
db: &DB,
username_override: String,
) -> error::Result<ApiAuthed> {
let permissioned_as = username_to_permissioned_as(username.as_str());
let authed =
fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?;
Ok(ApiAuthed {
username: username,
email: email,
is_admin: authed.is_admin,
is_operator: authed.is_operator,
groups: authed.groups,
folders: authed.folders,
scopes: authed.scopes,
username_override: Some(username_override),
})
}
async fn get_http_route_trigger(
route_path: &str,
opt_authed: Option<ApiAuthed>,
db: &DB,
user_db: UserDB,
) -> error::Result<(TriggerRoute, String, HashMap<String, String>, ApiAuthed)> {
let (mut triggers, route_path) = if *CLOUD_HOSTED {
let mut splitted = route_path.split("/");
let w_id = splitted.next().ok_or_else(|| {
error::Error::BadRequest("Missing workspace id in route path".to_string())
})?;
let route_path = StripPath(splitted.collect::<Vec<_>>().join("/"));
let triggers = sqlx::query_as!(
TriggerRoute,
r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _" FROM http_trigger WHERE workspace_id = $1"#,
w_id
)
.fetch_all(db)
.await?;
(triggers, route_path)
} else {
let triggers = sqlx::query_as!(
TriggerRoute,
r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _" FROM http_trigger"#,
)
.fetch_all(db)
.await?;
(triggers, StripPath(route_path.to_string()))
};
let mut router = matchit::Router::new();
for (idx, trigger) in triggers.iter().enumerate() {
let route_path = trigger.route_path.clone();
router.insert(route_path.as_str(), idx).unwrap_or_else(|e| {
tracing::warn!(
"Failed to consider http trigger route {}: {:?}",
route_path,
e,
);
});
}
let trigger_idx = router.at(route_path.0.as_str()).ok();
let matchit::Match { value: trigger_idx, params } =
not_found_if_none(trigger_idx, "Trigger", route_path.0.as_str())?;
let trigger = triggers.remove(trigger_idx.to_owned());
let params: HashMap<String, String> = params
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let username_override = if trigger.requires_auth {
if let Some(authed) = opt_authed {
// check that the user has access to the trigger
let mut tx = user_db.begin(&authed).await?;
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1 AND path = $2)",
trigger.workspace_id,
trigger.path
)
.fetch_one(&mut *tx)
.await?
.unwrap_or(false);
tx.commit().await?;
if exists {
Some(authed.display_username().to_owned())
} else {
return Err(error::Error::NotAuthorized("Unauthorized".to_string()));
}
} else {
return Err(error::Error::NotAuthorized(
"Requires authentication".to_string(),
));
}
} else {
None
};
let authed = fetch_api_authed(
trigger.edited_by.clone(),
trigger.email.clone(),
&trigger.workspace_id,
&db,
username_override.unwrap_or("anonymous".to_string()),
)
.await?;
Ok((trigger, route_path.0, params, authed))
}
async fn route_job(
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path(route_path): Path<StripPath>,
OptAuthed(opt_authed): OptAuthed,
Query(query): Query<HashMap<String, String>>,
method: http::Method,
headers: HeaderMap,
mut args: PushArgsOwned,
) -> impl IntoResponse {
let route_path = route_path.to_path();
let (trigger, called_path, params, authed) =
match get_http_route_trigger(route_path, opt_authed, &db, user_db.clone()).await {
Ok(trigger) => trigger,
Err(e) => return e.into_response(),
};
let headers = headers
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect::<HashMap<String, String>>();
let extra = args.extra.get_or_insert_with(HashMap::new);
extra.insert(
"wm_trigger".to_string(),
to_raw_value(&serde_json::json!({
"kind": "http",
"http": {
"route": trigger.route_path,
"path": called_path,
"method": method.to_string().to_lowercase(),
"params": params,
"query": query,
"headers": headers
},
})),
);
let http_method = http::Method::from(trigger.http_method);
if http_method != method {
return error::Error::BadRequest("Invalid HTTP method".to_string()).into_response();
}
let label_prefix = Some(format!(
"http-{}-{}-",
http_method.as_str().to_lowercase(),
trigger.route_path
));
let run_query = RunJobQuery::default();
if trigger.is_flow {
if trigger.is_async {
run_flow_by_path_inner(
authed,
db,
user_db,
rsmq,
trigger.workspace_id.clone(),
StripPath(trigger.script_path.to_owned()),
run_query,
args,
label_prefix,
)
.await
.into_response()
} else {
run_wait_result_flow_by_path_internal(
db,
run_query,
StripPath(trigger.script_path.to_owned()),
authed,
rsmq,
user_db,
args,
trigger.workspace_id.clone(),
label_prefix,
)
.await
.into_response()
}
} else {
if trigger.is_async {
run_script_by_path_inner(
authed,
db,
user_db,
rsmq,
trigger.workspace_id.clone(),
StripPath(trigger.script_path.to_owned()),
run_query,
args,
label_prefix,
)
.await
.into_response()
} else {
run_wait_result_script_by_path_internal(
db,
run_query,
StripPath(trigger.script_path.to_owned()),
authed,
rsmq,
user_db,
trigger.workspace_id.clone(),
args,
label_prefix,
)
.await
.into_response()
}
}
}

View File

@@ -1,12 +1,18 @@
use crate::db::DB;
use axum::{extract::Path, routing::post, Extension, Json, Router};
use axum::{
extract::Path,
routing::{get, post},
Extension, Json, Router,
};
use serde::{Deserialize, Serialize};
use tower_http::cors::{Any, CorsLayer};
use uuid::Uuid;
use windmill_common::{
error,
job_metrics::{JobStatsRecord, MetricKind},
error::{self, Error},
job_metrics::{
record_metric, register_metric_for_job, JobStatsRecord, MetricKind, MetricNumericValue,
},
};
pub fn workspaced_service() -> Router {
@@ -15,7 +21,16 @@ pub fn workspaced_service() -> Router {
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new().route("/get/:id", post(get_job_metrics).layer(cors.clone()))
Router::new()
.route("/get/:id", post(get_job_metrics).layer(cors.clone()))
.route(
"/set_progress/:id",
post(set_job_progress).layer(cors.clone()),
)
.route(
"/get_progress/:id",
get(get_job_progress).layer(cors.clone()),
)
}
#[derive(Deserialize)]
@@ -137,6 +152,93 @@ async fn get_job_metrics(
let response = JobStatsResponse { metrics_metadata, scalar_metrics, timeseries_metrics };
Ok(Json(response))
}
#[derive(Deserialize)]
struct JobProgressSetRequest {
percent: i32,
/// Optional parent flow id
/// Used to modify flow status
/// Specifically `progress` field in corresponding FlowStatusModule in `InProgress` state
flow_job_id: Option<Uuid>,
}
async fn set_job_progress(
Extension(db): Extension<DB>,
Path((w_id, job_id)): Path<(String, Uuid)>,
Json(JobProgressSetRequest { percent, flow_job_id }): Json<JobProgressSetRequest>,
) -> error::JsonResult<()> {
// If flow_job_id exists, than we should modify flow_status of corresponding module
// Individual jobs and flows are handled differently
if let Some(flow_job_id) = flow_job_id {
// TODO: Return error if trying to set completed job?
sqlx::query!(
"UPDATE queue
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', flow_status->>'step', 'progress'], $1)
WHERE id = $2",
serde_json::json!(percent.clamp(0, 99)),
flow_job_id
)
.execute(&db)
.await?;
}
let record_progress = || {
record_metric(
&db,
w_id.clone(),
job_id,
"progress_perc".to_owned(),
MetricNumericValue::Integer(percent),
)
};
// Try to record
if let Err(err) = record_progress().await {
if matches!(err, Error::MetricNotFound(..)) {
// Register
// TODO: Reset progress after job is finished (in case it reruns same job)?
_ = register_metric_for_job(
&db,
w_id.clone(),
job_id,
"progress_perc".to_string(),
MetricKind::ScalarInt,
Some("Job Execution Progress (%)".to_owned()),
)
.await?;
// Retry recording progress
record_progress().await.map_err(|err| {
// If for some reason it still returns same error, this error will be converted to BadRequest and returned
if let Error::MetricNotFound(body) = err {
Error::BadRequest(body)
} else {
err
}
})?;
} else {
return Err(err);
}
};
return Ok(Json(()));
}
async fn get_job_progress(
Extension(db): Extension<DB>,
Path((w_id, job_id)): Path<(String, Uuid)>,
) -> error::JsonResult<Option<i32>> {
let progress: Option<Option<i32>> = sqlx::query_scalar!(
"SELECT (scalar_int)::int FROM job_stats WHERE job_id = $1 AND workspace_id = $2 AND metric_id = 'progress_perc'",
job_id, w_id)
.fetch_optional(&db)
.await?;
let respond_value = if let Some(Some(progress)) = progress {
Some(progress.clamp(0, 99))
} else {
None
};
Ok(Json(respond_value))
}
fn timeseries_sample<T: Copy>(
from: Option<chrono::DateTime<chrono::Utc>>,

View File

@@ -33,6 +33,7 @@ use crate::add_webhook_allowed_origin;
use crate::concurrency_groups::join_concurrency_key;
use crate::db::ApiAuthed;
use crate::users::get_scope_tags;
use crate::utils::content_plain;
use crate::{
db::DB,
@@ -58,7 +59,7 @@ use tower_http::cors::{Any, CorsLayer};
use urlencoding::encode;
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
use windmill_audit::ActionKind;
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, SERVER_CONFIG};
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE};
use windmill_common::{
db::UserDB,
error::{self, to_anyhow, Error},
@@ -556,9 +557,10 @@ pub async fn get_path_tag_limits_cache_for_hash<'c, R: rsmq_async::RsmqConnectio
Option<i16>,
Option<bool>,
Option<i32>,
Option<bool>,
)> {
let script = sqlx::query!(
"select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout from script where hash = $1 AND workspace_id = $2",
"select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor from script where hash = $1 AND workspace_id = $2",
hash,
w_id
)
@@ -583,6 +585,7 @@ pub async fn get_path_tag_limits_cache_for_hash<'c, R: rsmq_async::RsmqConnectio
script.priority,
script.delete_after_use,
script.timeout,
script.has_preprocessor,
))
}
@@ -1058,6 +1061,7 @@ pub struct RunJobQuery {
pub tag: Option<String>,
pub timeout: Option<i32>,
pub cache_ttl: Option<i32>,
pub skip_preprocessor: Option<bool>,
}
impl RunJobQuery {
@@ -1245,13 +1249,18 @@ pub fn list_queue_jobs_query(
lq: &ListQueueQuery,
fields: &[&str],
join_outstanding_wait_times: bool,
tags: Option<Vec<&str>>,
) -> SqlBuilder {
let sqlb = SqlBuilder::select_from("queue")
let mut sqlb = SqlBuilder::select_from("queue")
.fields(fields)
.order_by("created_at", lq.order_desc.unwrap_or(true))
.limit(1000)
.clone();
if let Some(tags) = tags {
sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::<Vec<_>>());
}
filter_list_queue_query(sqlb, lq, w_id, join_outstanding_wait_times)
}
@@ -1307,6 +1316,7 @@ async fn list_queue_jobs(
"workspace_id",
],
false,
get_scope_tags(&authed),
)
.sql()?;
let mut tx = user_db.begin(&authed).await?;
@@ -1495,6 +1505,10 @@ async fn list_filtered_uuids(
sqlb.and_where_is_null("schedule_path");
if let Some(tags) = get_scope_tags(&authed) {
sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::<Vec<_>>());
}
sqlb = filter_list_queue_query(sqlb, &lq, w_id.as_str(), false);
let sql = sqlb.query()?;
@@ -1554,7 +1568,7 @@ async fn list_jobs(
Query(lq): Query<ListCompletedQuery>,
Extension(_api_list_jobs_query_duration): Extension<Option<Histo>>,
) -> error::JsonResult<Vec<Job>> {
check_scopes(&authed, || format!("listjobs"))?;
check_scopes(&authed, || format!("jobs:listjobs"))?;
let (per_page, offset) = paginate(pagination);
let lqc = lq.clone();
@@ -1572,6 +1586,7 @@ async fn list_jobs(
&ListCompletedQuery { order_desc: Some(true), ..lqc },
UnifiedJob::completed_job_fields(),
true,
get_scope_tags(&authed),
))
} else {
None
@@ -1587,6 +1602,7 @@ async fn list_jobs(
&ListQueueQuery { order_desc: Some(true), ..lq.into() },
UnifiedJob::queued_job_fields(),
true,
get_scope_tags(&authed),
);
if let Some(sqlc) = sqlc {
@@ -1637,7 +1653,7 @@ pub async fn resume_suspended_flow_as_owner(
Path((_w_id, flow_id)): Path<(String, Uuid)>,
QueryOrBody(value): QueryOrBody<serde_json::Value>,
) -> error::Result<StatusCode> {
check_scopes(&authed, || format!("resumeflow"))?;
check_scopes(&authed, || format!("jobs:resumeflow"))?;
let value = value.unwrap_or(serde_json::Value::Null);
let mut tx = db.begin().await?;
@@ -2784,14 +2800,18 @@ pub async fn run_flow_by_path_inner(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (tag, dedicated_worker) = sqlx::query!(
"SELECT tag, dedicated_worker from flow WHERE path = $1 and workspace_id = $2",
let (tag, dedicated_worker, has_preprocessor) = sqlx::query!(
"SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor
FROM flow
LEFT JOIN flow_version
ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]
WHERE flow.path = $1 and flow.workspace_id = $2",
flow_path,
w_id
)
.fetch_optional(&mut tx)
.await?
.map(|x| (x.tag, x.dedicated_worker))
.map(|x| (x.tag, x.dedicated_worker, x.has_preprocessor))
.ok_or_else(|| {
Error::NotFound(format!(
"flow not found at path {flow_path} in workspace {w_id}"
@@ -2807,7 +2827,12 @@ pub async fn run_flow_by_path_inner(
&db,
tx,
&w_id,
JobPayload::Flow { path: flow_path.to_string(), dedicated_worker },
JobPayload::Flow {
path: flow_path.to_string(),
dedicated_worker,
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
&& has_preprocessor.unwrap_or(false),
},
PushArgs { args: &args.args, extra: args.extra },
&label_prefix
.map(|x| x + authed.display_username())
@@ -2973,7 +2998,7 @@ pub async fn run_script_by_path_inner(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, _delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id).await?;
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
let scheduled_for = run_query.get_scheduled_for(&db).await?;
let tag = run_query.tag.clone().or(tag);
@@ -3049,7 +3074,15 @@ pub async fn run_workflow_as_code(
None,
run_query.timeout,
),
JobKind::Script => script_path_to_payload(job.script_path(), &mut tx, &w_id).await?,
JobKind::Script => {
script_path_to_payload(
job.script_path(),
&mut tx,
&w_id,
run_query.skip_preprocessor,
)
.await?
}
_ => return Err(anyhow::anyhow!("Not supported").into()),
};
@@ -3145,6 +3178,17 @@ impl Drop for Guard {
}
}
use std::sync::Arc;
use tokio::sync::RwLock;
lazy_static::lazy_static! {
pub static ref TIMEOUT_WAIT_RESULT: Arc<RwLock<Option<u64>>> = Arc::new(RwLock::new(
std::env::var("TIMEOUT_WAIT_RESULT")
.ok()
.and_then(|x| x.parse::<u64>().ok())
));
}
#[derive(Deserialize)]
pub struct WindmillCompositeResult {
windmill_status_code: Option<u16>,
@@ -3159,7 +3203,7 @@ async fn run_wait_result(
username: &str,
) -> error::Result<Response> {
let mut result = None;
let timeout = SERVER_CONFIG.read().await.timeout_wait_result.clone();
let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600);
let timeout_ms = if timeout <= 0 {
2000
} else {
@@ -3451,7 +3495,7 @@ pub async fn run_wait_result_job_by_path_get(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id).await?;
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
@@ -3527,7 +3571,7 @@ pub async fn run_wait_result_flow_by_path_get(
let args = PushArgsOwned { extra: Some(payload_args), args: HashMap::new() };
run_wait_result_flow_by_path_internal(
db, run_query, flow_path, authed, rsmq, user_db, args, w_id,
db, run_query, flow_path, authed, rsmq, user_db, args, w_id, None,
)
.await
}
@@ -3553,11 +3597,12 @@ pub async fn run_wait_result_script_by_path(
user_db,
w_id,
args,
None,
)
.await
}
async fn run_wait_result_script_by_path_internal(
pub async fn run_wait_result_script_by_path_internal(
db: sqlx::Pool<Postgres>,
run_query: RunJobQuery,
script_path: StripPath,
@@ -3566,6 +3611,7 @@ async fn run_wait_result_script_by_path_internal(
user_db: UserDB,
w_id: String,
args: PushArgsOwned,
label_prefix: Option<String>,
) -> error::Result<Response> {
check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?;
let script_path = script_path.to_path();
@@ -3574,7 +3620,7 @@ async fn run_wait_result_script_by_path_internal(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id).await?;
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
@@ -3587,7 +3633,9 @@ async fn run_wait_result_script_by_path_internal(
&w_id,
job_payload,
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
&label_prefix
.map(|x| x + authed.display_username())
.unwrap_or_else(|| authed.display_username().to_string()),
&authed.email,
username_to_permissioned_as(&authed.username),
None,
@@ -3644,6 +3692,7 @@ pub async fn run_wait_result_script_by_hash(
priority,
delete_after_use,
timeout,
has_preprocessor,
) = get_path_tag_limits_cache_for_hash(&mut tx, &w_id, hash).await?;
if let Some(run_query_cache_ttl) = run_query.cache_ttl {
cache_ttl = Some(run_query_cache_ttl);
@@ -3669,6 +3718,8 @@ pub async fn run_wait_result_script_by_hash(
language,
dedicated_worker,
priority,
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
&& has_preprocessor.unwrap_or(false),
},
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
@@ -3712,12 +3763,12 @@ pub async fn run_wait_result_flow_by_path(
check_license_key_valid().await?;
run_wait_result_flow_by_path_internal(
db, run_query, flow_path, authed, rsmq, user_db, args, w_id,
db, run_query, flow_path, authed, rsmq, user_db, args, w_id, None,
)
.await
}
async fn run_wait_result_flow_by_path_internal(
pub async fn run_wait_result_flow_by_path_internal(
db: sqlx::Pool<Postgres>,
run_query: RunJobQuery,
flow_path: StripPath,
@@ -3726,6 +3777,7 @@ async fn run_wait_result_flow_by_path_internal(
user_db: UserDB,
args: PushArgsOwned,
w_id: String,
label_prefix: Option<String>,
) -> error::Result<Response> {
check_queue_too_long(&db, run_query.queue_limit).await?;
@@ -3736,8 +3788,8 @@ async fn run_wait_result_flow_by_path_internal(
let scheduled_for = run_query.get_scheduled_for(&db).await?;
let (tag, dedicated_worker, early_return) = sqlx::query!(
"SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return
let (tag, dedicated_worker, early_return, has_preprocessor) = sqlx::query!(
"SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor
FROM flow
LEFT JOIN flow_version
ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]
@@ -3747,7 +3799,7 @@ async fn run_wait_result_flow_by_path_internal(
)
.fetch_optional(&mut tx)
.await?
.map(|x| (x.tag, x.dedicated_worker, x.early_return))
.map(|x| (x.tag, x.dedicated_worker, x.early_return, x.has_preprocessor))
.ok_or_else(|| {
Error::NotFound(format!(
"flow not found at path {flow_path} in workspace {w_id}"
@@ -3763,9 +3815,16 @@ async fn run_wait_result_flow_by_path_internal(
&db,
tx,
&w_id,
JobPayload::Flow { path: flow_path.to_string(), dedicated_worker },
JobPayload::Flow {
path: flow_path.to_string(),
dedicated_worker,
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
&& has_preprocessor.unwrap_or(false),
},
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
&label_prefix
.map(|x| x + authed.display_username())
.unwrap_or_else(|| authed.display_username().to_string()),
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
@@ -3801,7 +3860,7 @@ async fn run_preview_script(
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
check_scopes(&authed, || format!("runscript"))?;
check_scopes(&authed, || format!("jobs:runscript"))?;
if authed.is_operator {
return Err(error::Error::NotAuthorized(
"Operators cannot run preview jobs for security reasons".to_string(),
@@ -3871,7 +3930,7 @@ async fn run_bundle_preview_script(
check_license_key_valid().await?;
check_scopes(&authed, || format!("runscript"))?;
check_scopes(&authed, || format!("jobs:runscript"))?;
if authed.is_operator {
return Err(error::Error::NotAuthorized(
"Operators cannot run preview jobs for security reasons".to_string(),
@@ -4202,6 +4261,7 @@ async fn add_batch_jobs(
_priority,
_delete_after_use,
timeout,
_,
) = get_latest_deployed_hash_for_path(&db, &w_id, &path).await?;
(
Some(script_hash),
@@ -4228,7 +4288,11 @@ async fn add_batch_jobs(
JobPayload::RawFlow { value: fv.clone(), path: None, restarted_from: None }
} else {
if let Some(path) = batch_info.path.as_ref() {
JobPayload::Flow { path: path.to_string(), dedicated_worker: None }
JobPayload::Flow {
path: path.to_string(),
dedicated_worker: None,
apply_preprocessor: false,
}
} else {
Err(anyhow::anyhow!(
"Path is required if no value is not provided"
@@ -4352,7 +4416,7 @@ async fn run_preview_flow_job(
Query(run_query): Query<RunJobQuery>,
Json(raw_flow): Json<PreviewFlow>,
) -> error::Result<(StatusCode, String)> {
check_scopes(&authed, || format!("runflow"))?;
check_scopes(&authed, || format!("jobs:runflow"))?;
if authed.is_operator {
return Err(error::Error::NotAuthorized(
"Operators cannot run preview jobs for security reasons".to_string(),
@@ -4450,6 +4514,7 @@ pub async fn run_job_by_hash_inner(
priority,
_delete_after_use, // not taken into account in async endpoints
timeout,
has_preprocessor,
) = get_path_tag_limits_cache_for_hash(&mut tx, &w_id, hash).await?;
check_scopes(&authed, || format!("run:script/{path}"))?;
if let Some(run_query_cache_ttl) = run_query.cache_ttl {
@@ -4475,6 +4540,8 @@ pub async fn run_job_by_hash_inner(
language,
dedicated_worker,
priority,
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
&& has_preprocessor.unwrap_or(false),
},
PushArgs { args: &args.args, extra: args.extra },
&label_prefix
@@ -4507,6 +4574,7 @@ pub async fn run_job_by_hash_inner(
pub struct JobUpdateQuery {
pub running: bool,
pub log_offset: i32,
pub get_progress: Option<bool>,
}
#[derive(Serialize)]
@@ -4516,6 +4584,7 @@ pub struct JobUpdate {
pub new_logs: Option<String>,
pub log_offset: Option<i32>,
pub mem_peak: Option<i32>,
pub progress: Option<i32>,
pub flow_status: Option<Box<serde_json::value::RawValue>>,
}
@@ -4583,7 +4652,7 @@ async fn get_job_update(
OptAuthed(opt_authed): OptAuthed,
Extension(db): Extension<DB>,
Path((w_id, job_id)): Path<(String, Uuid)>,
Query(JobUpdateQuery { running, log_offset }): Query<JobUpdateQuery>,
Query(JobUpdateQuery { running, log_offset, get_progress }): Query<JobUpdateQuery>,
) -> error::JsonResult<JobUpdate> {
let record = sqlx::query_as::<_, JobUpdateRow>(
"SELECT running, substr(concat(coalesce(queue.logs, ''), job_logs.logs), greatest($1 - job_logs.log_offset, 0)) as logs, mem_peak,
@@ -4599,6 +4668,20 @@ async fn get_job_update(
.fetch_optional(&db)
.await?;
let progress: Option<i32> = if get_progress == Some(true){
sqlx::query_scalar!(
"SELECT scalar_int FROM job_stats WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
&w_id,
job_id,
"progress_perc"
)
.fetch_optional(&db)
.await?.and_then(|inner| inner)
} else {
None
};
if let Some(record) = record {
if opt_authed.is_none() && record.created_by != "anonymous" {
return Err(Error::BadRequest(
@@ -4616,6 +4699,7 @@ async fn get_job_update(
completed: None,
new_logs: record.logs,
mem_peak: record.mem_peak,
progress,
flow_status: record
.flow_status
.map(|x: sqlx::types::Json<Box<RawValue>>| x.0),
@@ -4648,6 +4732,7 @@ async fn get_job_update(
log_offset: record.log_offset,
new_logs: record.logs,
mem_peak: record.mem_peak,
progress,
flow_status: record
.flow_status
.map(|x: sqlx::types::Json<Box<RawValue>>| x.0),
@@ -4771,14 +4856,19 @@ pub fn list_completed_jobs_query(
lq: &ListCompletedQuery,
fields: &[&str],
join_outstanding_wait_times: bool,
tags: Option<Vec<&str>>,
) -> SqlBuilder {
let sqlb = SqlBuilder::select_from("completed_job")
let mut sqlb = SqlBuilder::select_from("completed_job")
.fields(fields)
.order_by("created_at", lq.order_desc.unwrap_or(true))
.offset(offset)
.limit(per_page)
.clone();
if let Some(tags) = tags {
sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::<Vec<_>>());
}
filter_list_completed_query(sqlb, lq, w_id, join_outstanding_wait_times)
}
#[derive(Deserialize, Clone)]
@@ -4823,7 +4913,7 @@ async fn list_completed_jobs(
Query(pagination): Query<Pagination>,
Query(lq): Query<ListCompletedQuery>,
) -> error::JsonResult<Vec<ListableCompletedJob>> {
check_scopes(&authed, || format!("listjobs"))?;
check_scopes(&authed, || format!("jobs:listjobs"))?;
let (per_page, offset) = paginate(pagination);
@@ -4865,6 +4955,7 @@ async fn list_completed_jobs(
"'CompletedJob' as type",
],
false,
get_scope_tags(&authed),
)
.sql()?;
let mut tx = user_db.begin(&authed).await?;
@@ -5089,7 +5180,7 @@ async fn delete_completed_job<'a>(
Extension(user_db): Extension<UserDB>,
Path((w_id, id)): Path<(String, Uuid)>,
) -> error::Result<Response> {
check_scopes(&authed, || format!("deletejob"))?;
check_scopes(&authed, || format!("jobs:deletejob"))?;
let mut tx = user_db.begin(&authed).await?;

Some files were not shown because too many files have changed in this diff Show More