Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca4ec9d32 | ||
|
|
88149c6d93 | ||
|
|
f43e31b967 | ||
|
|
cc805a06b5 | ||
|
|
58c4c0b5c2 | ||
|
|
4f4ca264ae | ||
|
|
6fefdb5438 | ||
|
|
9a79850fd4 | ||
|
|
b2f705d01e | ||
|
|
1fdc55c816 | ||
|
|
651d99824d | ||
|
|
b85103e3b5 | ||
|
|
b65657d0f8 | ||
|
|
b0894492f1 | ||
|
|
8c4faa05fa | ||
|
|
535202c1fe | ||
|
|
993812319f | ||
|
|
2964569962 | ||
|
|
0ce857c4a8 | ||
|
|
033000fed3 | ||
|
|
4bce61f2b6 | ||
|
|
a9b5d6bebb | ||
|
|
009a83dd9b | ||
|
|
e7141c147d | ||
|
|
8896a8cacf | ||
|
|
e250897237 | ||
|
|
d26730ad34 | ||
|
|
7c64a36624 | ||
|
|
c4a1054a5a | ||
|
|
a3eced0029 | ||
|
|
d42ddcbe90 | ||
|
|
eb8e75d519 | ||
|
|
bb93bd8b89 | ||
|
|
f7d7746327 | ||
|
|
2f7fb9bfd4 | ||
|
|
b10df30245 | ||
|
|
e11b257b2d | ||
|
|
c0ed1ace54 | ||
|
|
da15a56a07 | ||
|
|
f7f71302f7 | ||
|
|
5aedc6832d | ||
|
|
4bda35cd35 | ||
|
|
2386ba16e4 | ||
|
|
b75e8a55ba | ||
|
|
d85555d3fb | ||
|
|
9c4f1e0333 | ||
|
|
4cb7a121e6 | ||
|
|
4f6a116e46 | ||
|
|
c22afa3a8b | ||
|
|
c58190ef78 | ||
|
|
e4da819cda | ||
|
|
4de62a9019 | ||
|
|
2b36ffb9cf | ||
|
|
256ee646b4 | ||
|
|
90668fb0e5 | ||
|
|
d822beba63 | ||
|
|
abb50fac93 | ||
|
|
8ad8d20136 | ||
|
|
0b13f2d782 | ||
|
|
b5f53556a9 | ||
|
|
0333ce78bd | ||
|
|
042504f905 | ||
|
|
d9458e96eb | ||
|
|
f36a510025 | ||
|
|
874b7a50f7 | ||
|
|
20b0500a28 | ||
|
|
c728c93a3a | ||
|
|
b67d00540b | ||
|
|
a6d1e7c79b | ||
|
|
1cc54ffb9a | ||
|
|
0c3d4a64c4 | ||
|
|
60cd14ff59 | ||
|
|
f3a0c81639 | ||
|
|
cb8bc75b30 | ||
|
|
5fcdf12ef5 | ||
|
|
2d8bca8d38 | ||
|
|
bea0da3d6f | ||
|
|
df865380bc | ||
|
|
1cad51b594 | ||
|
|
194ee830cc | ||
|
|
5cb3c34e44 | ||
|
|
6c0f921b3a | ||
|
|
2170f6504f | ||
|
|
f50cef0367 | ||
|
|
51d648d450 | ||
|
|
8ca04e49b0 | ||
|
|
2f72be36e5 | ||
|
|
d47d4ccf85 | ||
|
|
583dae6a72 | ||
|
|
8fc0afce71 | ||
|
|
6eaec47162 | ||
|
|
a3f1111ca7 | ||
|
|
60c4860233 | ||
|
|
85805bdf82 | ||
|
|
225f675fb3 | ||
|
|
a122b6cab6 | ||
|
|
dd980ee296 | ||
|
|
45648bbf7f | ||
|
|
1d90434f12 | ||
|
|
cb42f10dc6 | ||
|
|
62de305908 |
2
.github/DockerfileBackendTests
vendored
2
.github/DockerfileBackendTests
vendored
@@ -41,7 +41,7 @@ RUN apt-get update \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
RUN wget https://golang.org/dl/go1.19.1.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz
|
||||
RUN wget https://golang.org/dl/go1.21.5.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
|
||||
ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV GO_PATH=/usr/local/go/bin/go
|
||||
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -42,5 +42,5 @@ jobs:
|
||||
mkdir frontend/build && cd backend && touch
|
||||
windmill-api/openapi-deref.yaml &&
|
||||
DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill
|
||||
DISABLE_EMBEDDING=true cargo test --features enterprise --all --
|
||||
DISABLE_EMBEDDING=true RUST_LOG=info cargo test --features enterprise --all --
|
||||
--nocapture
|
||||
|
||||
2
.github/workflows/docker-image.yml
vendored
2
.github/workflows/docker-image.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise
|
||||
features=enterprise,saml
|
||||
nsjail=true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
|
||||
124
CHANGELOG.md
124
CHANGELOG.md
@@ -1,6 +1,130 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.224.0](https://github.com/windmill-labs/windmill/compare/v1.223.1...v1.224.0) (2023-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add authelia sso support ([#2824](https://github.com/windmill-labs/windmill/issues/2824)) ([6fefdb5](https://github.com/windmill-labs/windmill/commit/6fefdb5438d0fd7546759d93b5f4465731a4cac5))
|
||||
* embedded server only get killed after last job of worker ([4f4ca26](https://github.com/windmill-labs/windmill/commit/4f4ca264aef3e1d3ee4748593f0ebcb425d333ef))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make cleanup_module backcompatible ([58c4c0b](https://github.com/windmill-labs/windmill/commit/58c4c0b5c2b009ff56e3eb6bd19e766dbff4cf54))
|
||||
|
||||
## [1.223.1](https://github.com/windmill-labs/windmill/compare/v1.223.0...v1.223.1) (2023-12-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve aggrid selectedRow output + make first selectedRow configurable ([651d998](https://github.com/windmill-labs/windmill/commit/651d99824d1932a48fdb9ffd5dff944fa2c2c8aa))
|
||||
|
||||
## [1.223.0](https://github.com/windmill-labs/windmill/compare/v1.222.0...v1.223.0) (2023-12-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add a git sync test button ([#2817](https://github.com/windmill-labs/windmill/issues/2817)) ([535202c](https://github.com/windmill-labs/windmill/commit/535202c1fe1e3a90b1aff2dad6632f615ca8aef8))
|
||||
* add mysql datetime ([#2808](https://github.com/windmill-labs/windmill/issues/2808)) ([8896a8c](https://github.com/windmill-labs/windmill/commit/8896a8cacffb5da5575203a5fc9aa6863b303fde))
|
||||
* Add possibility to delete flow step results when the flow is complete ([#2806](https://github.com/windmill-labs/windmill/issues/2806)) ([b65657d](https://github.com/windmill-labs/windmill/commit/b65657d0f8db21f87a90bdcd6ce1122a8bb209ce))
|
||||
* no worker with tag warning ([#2803](https://github.com/windmill-labs/windmill/issues/2803)) ([e7141c1](https://github.com/windmill-labs/windmill/commit/e7141c147d9ff4728399ae0703ca636c776cb264))
|
||||
* sql types autocomplete ([#2810](https://github.com/windmill-labs/windmill/issues/2810)) ([a9b5d6b](https://github.com/windmill-labs/windmill/commit/a9b5d6bebb00c96cc329b4178816c73db5cbe3f0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* conditional component also for toggles ([d26730a](https://github.com/windmill-labs/windmill/commit/d26730ad3419df5c5d75328e7f441c701b2c0c68))
|
||||
* db schema autocomplete ([#2809](https://github.com/windmill-labs/windmill/issues/2809)) ([e250897](https://github.com/windmill-labs/windmill/commit/e2508972378f455b7af7984f2c33894dadd4f7bc))
|
||||
* deno chromium support when nsjail enabled ([#2815](https://github.com/windmill-labs/windmill/issues/2815)) ([2964569](https://github.com/windmill-labs/windmill/commit/296456996293d861c7cab2d10e01e3b6443e421c))
|
||||
* Fix test_complex_flow_restart ([#2813](https://github.com/windmill-labs/windmill/issues/2813)) ([b089449](https://github.com/windmill-labs/windmill/commit/b0894492f1e3c06880c0e1875c8c8886e31a165f))
|
||||
* improve sql default arg parsing + auto invite ([033000f](https://github.com/windmill-labs/windmill/commit/033000fed36d6172cf3b9a83ff449f5ba051fa08))
|
||||
* support interval in pg ([009a83d](https://github.com/windmill-labs/windmill/commit/009a83dd9bbc115b94978d2cb3113cbe10acd05d))
|
||||
|
||||
## [1.222.0](https://github.com/windmill-labs/windmill/compare/v1.221.0...v1.222.0) (2023-12-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* conditional fields on forms ([b10df30](https://github.com/windmill-labs/windmill/commit/b10df302455202940348cd20c2e733734fb7d027))
|
||||
* mysql named params ([#2805](https://github.com/windmill-labs/windmill/issues/2805)) ([c4a1054](https://github.com/windmill-labs/windmill/commit/c4a1054a5a4ab3fd700de842a1f46d5886632b11))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add saml feature flag ([#2802](https://github.com/windmill-labs/windmill/issues/2802)) ([da15a56](https://github.com/windmill-labs/windmill/commit/da15a56a07288d4164c88289dd559389735db719))
|
||||
* app reports screenshot refresh ([#2800](https://github.com/windmill-labs/windmill/issues/2800)) ([f7f7130](https://github.com/windmill-labs/windmill/commit/f7f71302f7d0fcac7468d506ebaceb55332ec1ed))
|
||||
* fix extraOptions for agGrid ([f7d7746](https://github.com/windmill-labs/windmill/commit/f7d7746327411da06cede4e0ff06222d318d01bc))
|
||||
* fix extraOptions for agGrid ([2f7fb9b](https://github.com/windmill-labs/windmill/commit/2f7fb9bfd490932a2f0bc01f02df678668487f8c))
|
||||
* **frontend:** Fix chartjs options merge ([#2804](https://github.com/windmill-labs/windmill/issues/2804)) ([e11b257](https://github.com/windmill-labs/windmill/commit/e11b257b2d761b2b26160d61f8e113dbe47e9efe))
|
||||
|
||||
## [1.221.0](https://github.com/windmill-labs/windmill/compare/v1.220.0...v1.221.0) (2023-12-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* limit pro plans ([#2794](https://github.com/windmill-labs/windmill/issues/2794)) ([c58190e](https://github.com/windmill-labs/windmill/commit/c58190ef788feaae9bac9848e8cfa59d10f51cab))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Allow run git for Deno ([#2789](https://github.com/windmill-labs/windmill/issues/2789)) ([8ad8d20](https://github.com/windmill-labs/windmill/commit/8ad8d20136f3151ca28784ba3513af562fcb5c9b))
|
||||
* Allow run git for Deno ([#2790](https://github.com/windmill-labs/windmill/issues/2790)) ([d822beb](https://github.com/windmill-labs/windmill/commit/d822beba63f08e4e1e1dac4aff56353276558f02))
|
||||
* app reports nits ([#2788](https://github.com/windmill-labs/windmill/issues/2788)) ([0b13f2d](https://github.com/windmill-labs/windmill/commit/0b13f2d782d5626d64b5efe39232ec8da268cd27))
|
||||
* app reports ts ([#2798](https://github.com/windmill-labs/windmill/issues/2798)) ([4f6a116](https://github.com/windmill-labs/windmill/commit/4f6a116e462d198cb81260749fdd8d57bc0ef03a))
|
||||
* Deno allow run git ([#2791](https://github.com/windmill-labs/windmill/issues/2791)) ([256ee64](https://github.com/windmill-labs/windmill/commit/256ee646b49bca091ee6e7a0fea37f7d98c9e378))
|
||||
* **frontend:** fix storage key typo ([#2796](https://github.com/windmill-labs/windmill/issues/2796)) ([e4da819](https://github.com/windmill-labs/windmill/commit/e4da819cda12e0e1f27cabc594fe5af5e2d9966c))
|
||||
* **frontend:** use correct id for selected for loop iteration ([#2787](https://github.com/windmill-labs/windmill/issues/2787)) ([90668fb](https://github.com/windmill-labs/windmill/commit/90668fb0e5b4ac9f09a0b1e57fcb10b4b4208d2e))
|
||||
* improve conditionalwrapper selection ([b5f5355](https://github.com/windmill-labs/windmill/commit/b5f53556a9afb3becbaaab034eeb8fdf4b1178a1))
|
||||
* linked variable already being a variable ([9c4f1e0](https://github.com/windmill-labs/windmill/commit/9c4f1e0333743b35c237b4d8fe49757c8ae122e7))
|
||||
* smtp doesn't require username/password ([abb50fa](https://github.com/windmill-labs/windmill/commit/abb50fac93a615d0ee18e9776c1fb84829fa2464))
|
||||
|
||||
## [1.220.0](https://github.com/windmill-labs/windmill/compare/v1.219.1...v1.220.0) (2023-12-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add workspace settings to sync scripts/flows/apps to git repo on deployment ([#2740](https://github.com/windmill-labs/windmill/issues/2740)) ([194ee83](https://github.com/windmill-labs/windmill/commit/194ee830cc8d8cc0ea69c877b428966f3f20bdb5))
|
||||
* app reports v2 ([#2778](https://github.com/windmill-labs/windmill/issues/2778)) ([f36a510](https://github.com/windmill-labs/windmill/commit/f36a510025d87fc20e15f66e041390cc701c65c8))
|
||||
* CLI now accepts message arg for sync push ([#2784](https://github.com/windmill-labs/windmill/issues/2784)) ([d9458e9](https://github.com/windmill-labs/windmill/commit/d9458e96ebc26160e9d5f909574bd1cf1d288d73))
|
||||
* Custom content type for script and flow results ([#2767](https://github.com/windmill-labs/windmill/issues/2767)) ([6c0f921](https://github.com/windmill-labs/windmill/commit/6c0f921b3ab3acac8890ceb1fa0565bf082de0d4))
|
||||
* **frontend:** add before and after icons for text input components ([#2746](https://github.com/windmill-labs/windmill/issues/2746)) ([a3f1111](https://github.com/windmill-labs/windmill/commit/a3f1111ca7715ffaa3a615786adee20e00870b30))
|
||||
* History commit message can be updated from the UI ([#2777](https://github.com/windmill-labs/windmill/issues/2777)) ([874b7a5](https://github.com/windmill-labs/windmill/commit/874b7a50f72a6505490a02c82786ea5f5c8261e0))
|
||||
* perpetual scripts ([bea0da3](https://github.com/windmill-labs/windmill/commit/bea0da3d6ff81ace956d6a1e7fb643e449927b05))
|
||||
* result preview in app editor ([#2761](https://github.com/windmill-labs/windmill/issues/2761)) ([51d648d](https://github.com/windmill-labs/windmill/commit/51d648d450c578580f6d9a9630ff91920060cc27))
|
||||
* save inline scripts of apps to workspace + search by path ([f50cef0](https://github.com/windmill-labs/windmill/commit/f50cef03670e50b8d7a6bb4719ae636f2714f22f))
|
||||
* Various minor improvements for S3 DX ([#2760](https://github.com/windmill-labs/windmill/issues/2760)) ([5cb3c34](https://github.com/windmill-labs/windmill/commit/5cb3c34e440be878687217fdd95d735f053c7839))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add access-control-allow-origin to static assets ([60cd14f](https://github.com/windmill-labs/windmill/commit/60cd14ff59a80f671838a5f9bfa21effc309cb54))
|
||||
* add run_flow_sync to python client + few nits ([042504f](https://github.com/windmill-labs/windmill/commit/042504f9052963c882ac346d2026c518c6d94f0f))
|
||||
* Array of S3 files now shows links to the S3 explorer ([#2783](https://github.com/windmill-labs/windmill/issues/2783)) ([20b0500](https://github.com/windmill-labs/windmill/commit/20b0500a2824be1e715d4301e05c6081a5575dbe))
|
||||
* deployment_metadata table app_version is a nullable BIGINT ([#2769](https://github.com/windmill-labs/windmill/issues/2769)) ([df86538](https://github.com/windmill-labs/windmill/commit/df865380bceb8663defb322a88f58f7c3f1a263c))
|
||||
* do not prorate for team plan checkouts mornings of firsts ([#2751](https://github.com/windmill-labs/windmill/issues/2751)) ([8fc0afc](https://github.com/windmill-labs/windmill/commit/8fc0afce714774647f86e1aca8eeec4b7d4c13fc))
|
||||
* fix early return sync on existing flows ([60c4860](https://github.com/windmill-labs/windmill/commit/60c486023310ef85d1cb171ebca792be8d7917e8))
|
||||
* **frontend:** Fix dark mode observer, removed code duplication ([#2770](https://github.com/windmill-labs/windmill/issues/2770)) ([a6d1e7c](https://github.com/windmill-labs/windmill/commit/a6d1e7c79bb16d2c02d53f9ede56451e064956e0))
|
||||
* **frontend:** fix fileinput drag and drop check ([#2781](https://github.com/windmill-labs/windmill/issues/2781)) ([0c3d4a6](https://github.com/windmill-labs/windmill/commit/0c3d4a64c4a8ad823646f9ce27de7e58ccc002e8))
|
||||
* **frontend:** fix resource search ([#2748](https://github.com/windmill-labs/windmill/issues/2748)) ([6eaec47](https://github.com/windmill-labs/windmill/commit/6eaec471629d6c60e6c3123372fa3569bc5471c7))
|
||||
* **frontend:** fix sign in with Google button ([#2756](https://github.com/windmill-labs/windmill/issues/2756)) ([8ca04e4](https://github.com/windmill-labs/windmill/commit/8ca04e49b061675f820e4aeaad6196403eabe135))
|
||||
* **frontend:** fix tutorials contols ([#2745](https://github.com/windmill-labs/windmill/issues/2745)) ([85805bd](https://github.com/windmill-labs/windmill/commit/85805bdf8265f4fe14a583b43b786257ffccc4dc))
|
||||
* **frontend:** infer args if the schema is not an object ([#2782](https://github.com/windmill-labs/windmill/issues/2782)) ([b67d005](https://github.com/windmill-labs/windmill/commit/b67d00540b7adfac3550299d50fc9a620b5a6e2e))
|
||||
* improve azure openai readme ([#2754](https://github.com/windmill-labs/windmill/issues/2754)) ([d47d4cc](https://github.com/windmill-labs/windmill/commit/d47d4ccf85747a14e0498be871ead08371392ff5))
|
||||
* show bg runnables currently running ([a122b6c](https://github.com/windmill-labs/windmill/commit/a122b6cab664e0b8b3d7223b9fd70569a2fe0991))
|
||||
* string or enum ([#2752](https://github.com/windmill-labs/windmill/issues/2752)) ([583dae6](https://github.com/windmill-labs/windmill/commit/583dae6a72ee6be70dee5a356e1b4172db9a7e2d))
|
||||
* string results with custom content types are not quoted ([#2768](https://github.com/windmill-labs/windmill/issues/2768)) ([1cad51b](https://github.com/windmill-labs/windmill/commit/1cad51b5941a3bd1892c935581373bb7375da45a))
|
||||
* strip prefix of s3 resource ([#2780](https://github.com/windmill-labs/windmill/issues/2780)) ([f3a0c81](https://github.com/windmill-labs/windmill/commit/f3a0c81639662a7da65af49e4032dfc573dd93eb))
|
||||
|
||||
## [1.219.1](https://github.com/windmill-labs/windmill/compare/v1.219.0...v1.219.1) (2023-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix editorContext in preview ([cb42f10](https://github.com/windmill-labs/windmill/commit/cb42f10dc605f00ba70fadc61069aba98af8ec88))
|
||||
* maps mapRegion update on move ([62de305](https://github.com/windmill-labs/windmill/commit/62de305908ba7e6ac75aa5e8ffba822522b08345))
|
||||
|
||||
## [1.219.0](https://github.com/windmill-labs/windmill/compare/v1.218.0...v1.219.0) (2023-12-01)
|
||||
|
||||
|
||||
|
||||
@@ -171,13 +171,13 @@ RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
targz='go1.21.0.linux-amd64.tar.gz'; \
|
||||
targz='go1.21.5.linux-amd64.tar.gz'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
targz='go1.21.0.linux-arm64.tar.gz'; \
|
||||
targz='go1.21.5.linux-arm64.tar.gz'; \
|
||||
;; \
|
||||
'armhf') \
|
||||
targz='go1.21.0.linux-armv6l.tar.gz'; \
|
||||
targz='go1.21.5.linux-armv6l.tar.gz'; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
|
||||
esac; \
|
||||
|
||||
@@ -367,7 +367,7 @@ it being synced automatically everyday.
|
||||
| WHITELIST_ENVS | None | List of envs variables, separated by a ',' that are whitelisted as being safe to passthrough the workers | Worker |
|
||||
| SAML_METADATA | None | SAML Metadata URL to enable SAML SSO (EE only) | Server |
|
||||
| SECRET_SALT | None | Secret Salt used for encryption and decryption of secrets. If defined, the secrets will not be decryptable unless the right salt is passed in, which is the case for the workers and the server | Server + Worker |
|
||||
| OPENAI_AZURE_BASE_PATH | None | Azure OpenAI API base path (no trailing slash) | Server |
|
||||
| OPENAI_AZURE_BASE_PATH | None | Azure OpenAI path to be used instead of the OpenAI path. All Windmill AI features will run on the specified deployed model. Format: `https://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}` | Server |
|
||||
| DISABLE_EMBEDDING | false | Disable local embedding search of hub scripts | Server |
|
||||
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | Worker |
|
||||
| DISABLE_SERVER | false | Disable the external API, operate as a worker only instance | Worker |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority from script where hash = $1 AND workspace_id = $2",
|
||||
"query": "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use from script where hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -37,7 +37,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
@@ -59,6 +58,11 @@
|
||||
"ordinal": 6,
|
||||
"name": "priority",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "delete_after_use",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -74,8 +78,9 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d48070ef889723f408ed6c1a97b5ab9f0e98340cd33c6e29824f6030f0089071"
|
||||
"hash": "0234079d04372b82ec8f2990916433bd8771c21d22a1db02eb1e388a8bfb28ff"
|
||||
}
|
||||
16
backend/.sqlx/query-0aff8b0f2cddc0dd696759f48368c7315ff39060be5430c59582e1e05a4b3d90.json
generated
Normal file
16
backend/.sqlx/query-0aff8b0f2cddc0dd696759f48368c7315ff39060be5430c59582e1e05a4b3d90.json
generated
Normal 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": "0aff8b0f2cddc0dd696759f48368c7315ff39060be5430c59582e1e05a4b3d90"
|
||||
}
|
||||
15
backend/.sqlx/query-0c0b5d5d1e6ab2fed7532f94b50be3210e3845b61551691bbef81c2b6fb01121.json
generated
Normal file
15
backend/.sqlx/query-0c0b5d5d1e6ab2fed7532f94b50be3210e3845b61551691bbef81c2b6fb01121.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1)\n WHERE id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0c0b5d5d1e6ab2fed7532f94b50be3210e3845b61551691bbef81c2b6fb01121"
|
||||
}
|
||||
@@ -87,6 +87,11 @@
|
||||
"ordinal": 16,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -111,6 +116,7 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
23
backend/.sqlx/query-1d27895aa42ccbb542479b19baefd62790205b529ab0d8af36f18c470e8bb838.json
generated
Normal file
23
backend/.sqlx/query-1d27895aa42ccbb542479b19baefd62790205b529ab0d8af36f18c470e8bb838.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT restart_unless_cancelled FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "restart_unless_cancelled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1d27895aa42ccbb542479b19baefd62790205b529ab0d8af36f18c470e8bb838"
|
||||
}
|
||||
@@ -45,7 +45,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
15
backend/.sqlx/query-26106be4d94c159cc8d9eb37a3b94927c5d3d43c18cdb5e98e979e761ed6ed0e.json
generated
Normal file
15
backend/.sqlx/query-26106be4d94c159cc8d9eb37a3b94927c5d3d43c18cdb5e98e979e761ed6ed0e.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "26106be4d94c159cc8d9eb37a3b94927c5d3d43c18cdb5e98e979e761ed6ed0e"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET leaf_jobs = JSONB_SET(coalesce(leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)\n WHERE COALESCE((SELECT root_job FROM queue WHERE id = $3), $3) = id\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2a3ebe1b0eae5b2164894321e138cc4dc0293788aeb98d05d95d18dfc708d6a6"
|
||||
}
|
||||
22
backend/.sqlx/query-322038aae939d738f47b5bddaec2a6342e25e815dcd8b9fd620ddb81063c5d76.json
generated
Normal file
22
backend/.sqlx/query-322038aae939d738f47b5bddaec2a6342e25e815dcd8b9fd620ddb81063c5d76.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_status->'cleanup_module' as cleanup_module\n FROM queue\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "cleanup_module",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "322038aae939d738f47b5bddaec2a6342e25e815dcd8b9fd620ddb81063c5d76"
|
||||
}
|
||||
29
backend/.sqlx/query-362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675.json
generated
Normal file
29
backend/.sqlx/query-362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET suspend = $1, suspend_until = now() + interval '14 day', running = true\n WHERE id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3c83e6adf21b7a8572529f9175cf6d2d30ea8c921ae2bf751b6825a3e453a012"
|
||||
}
|
||||
14
backend/.sqlx/query-42543450a5c1b988b258fb1e0c00cd0a53a0395bc60e35817ff149c4f27a19fb.json
generated
Normal file
14
backend/.sqlx/query-42543450a5c1b988b258fb1e0c00cd0a53a0395bc60e35817ff149c4f27a19fb.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job\n SET logs = '##DELETED##', args = '{}'::jsonb, result = '{}'::jsonb\n WHERE id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "42543450a5c1b988b258fb1e0c00cd0a53a0395bc60e35817ff149c4f27a19fb"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "47a74719faba8a6377ba157d25929a6f1e74afdffd642d5a1c7db57e745e0c01"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4de3ba6930b91c639f9f3939dab7b3ab35aa792484e4ccb8084c336a2ec60f45"
|
||||
}
|
||||
14
backend/.sqlx/query-51d253d3cba9e2a36c291f6f9cf7023834484e89d91f0267d9dcaaafe406c64c.json
generated
Normal file
14
backend/.sqlx/query-51d253d3cba9e2a36c291f6f9cf7023834484e89d91f0267d9dcaaafe406c64c.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET git_sync = NULL WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "51d253d3cba9e2a36c291f6f9cf7023834484e89d91f0267d9dcaaafe406c64c"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'], $3)\n WHERE id = $4\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "53ec897ac019abf3fc4499f593adadf85af465d66c1a23b0549d176edbabad79"
|
||||
}
|
||||
15
backend/.sqlx/query-53ff0e14c35a3e84585a699e55093546db5c5f0ad0c5f92f34aaf2cdd125d130.json
generated
Normal file
15
backend/.sqlx/query-53ff0e14c35a3e84585a699e55093546db5c5f0ad0c5f92f34aaf2cdd125d130.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET suspend = $1, suspend_until = now() + interval '14 day', running = true\n WHERE id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "53ff0e14c35a3e84585a699e55093546db5c5f0ad0c5f92f34aaf2cdd125d130"
|
||||
}
|
||||
@@ -87,6 +87,11 @@
|
||||
"ordinal": 16,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -111,6 +116,7 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
22
backend/.sqlx/query-5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04.json
generated
Normal file
22
backend/.sqlx/query-5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> $1 AND ping_at > now() - interval '1 minute')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04"
|
||||
}
|
||||
16
backend/.sqlx/query-61656548991bf6d44c839373cb3e29d3ca1170a2ac0d3dce0b5df0e8677a4874.json
generated
Normal file
16
backend/.sqlx/query-61656548991bf6d44c839373cb3e29d3ca1170a2ac0d3dce0b5df0e8677a4874.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "61656548991bf6d44c839373cb3e29d3ca1170a2ac0d3dce0b5df0e8677a4874"
|
||||
}
|
||||
@@ -40,7 +40,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -60,7 +61,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
17
backend/.sqlx/query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json
generated
Normal file
17
backend/.sqlx/query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a"
|
||||
}
|
||||
18
backend/.sqlx/query-6bb4fab9e74c421fb30cfd856a1a9b44c76f1cf1485ceba50aeede9daec94c01.json
generated
Normal file
18
backend/.sqlx/query-6bb4fab9e74c421fb30cfd856a1a9b44c76f1cf1485ceba50aeede9daec94c01.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6bb4fab9e74c421fb30cfd856a1a9b44c76f1cf1485ceba50aeede9daec94c01"
|
||||
}
|
||||
@@ -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) 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)",
|
||||
"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) 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)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -28,7 +28,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
@@ -62,10 +61,12 @@
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Int2"
|
||||
"Int2",
|
||||
"Bool",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "52ad1773a9f5a1b6be1bb16af408f051f27d3f21b615c468b85aa75b64d943db"
|
||||
"hash": "7db59e88a10150ab6a3e81f164343e6bb2be6149fb690e63b8a0c376f39e9924"
|
||||
}
|
||||
17
backend/.sqlx/query-7f9f1ce221835fc3ff6c864c362519fde3d08e496119e51d8ed4fe65792a85c1.json
generated
Normal file
17
backend/.sqlx/query-7f9f1ce221835fc3ff6c864c362519fde3d08e496119e51d8ed4fe65792a85c1.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL DO UPDATE SET callback_job_ids = $3, deployment_msg = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7f9f1ce221835fc3ff6c864c362519fde3d08e496119e51d8ed4fe65792a85c1"
|
||||
}
|
||||
16
backend/.sqlx/query-8929150cb9262623eb12c908cb96c4d7e8cac594900ab0956c098e1cfa75e2f0.json
generated
Normal file
16
backend/.sqlx/query-8929150cb9262623eb12c908cb96c4d7e8cac594900ab0956c098e1cfa75e2f0.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET leaf_jobs = JSONB_SET(coalesce(leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)\n WHERE COALESCE((SELECT root_job FROM queue WHERE id = $3), $3) = id",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8929150cb9262623eb12c908cb96c4d7e8cac594900ab0956c098e1cfa75e2f0"
|
||||
}
|
||||
14
backend/.sqlx/query-8a44ca0cfe1e154138cbbf5ebddebbba2e1149d2e329d1d89eecc1c008c93a31.json
generated
Normal file
14
backend/.sqlx/query-8a44ca0cfe1e154138cbbf5ebddebbba2e1149d2e329d1d89eecc1c008c93a31.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE completed_job\n SET logs = '##DELETED##', args = '{}'::jsonb, result = '{}'::jsonb\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8a44ca0cfe1e154138cbbf5ebddebbba2e1149d2e329d1d89eecc1c008c93a31"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'branchall', 'branch'], ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "int4",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9681c348429f5e647ab55a4a54933faa64018f8539a143641f10d14ddd3a09c4"
|
||||
}
|
||||
@@ -24,7 +24,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -40,7 +41,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1)\n WHERE id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a6145b0482c9e5da245059a80b1563cad20318fd2dd8aef33f9ca97de1826b8b"
|
||||
}
|
||||
23
backend/.sqlx/query-a94dbd1f7aab20682548471c5cc06c7a496edac36e3de537b00b76ad93c7556c.json
generated
Normal file
23
backend/.sqlx/query-a94dbd1f7aab20682548471c5cc06c7a496edac36e3de537b00b76ad93c7556c.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'branchall', 'branch'], ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "int4",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a94dbd1f7aab20682548471c5cc06c7a496edac36e3de537b00b76ad93c7556c"
|
||||
}
|
||||
@@ -30,7 +30,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -46,7 +47,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
22
backend/.sqlx/query-abb56dec024600d0d7a63212f63c3296196898003707f2cb3a890690173a51a5.json
generated
Normal file
22
backend/.sqlx/query-abb56dec024600d0d7a63212f63c3296196898003707f2cb3a890690173a51a5.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM app WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "abb56dec024600d0d7a63212f63c3296196898003707f2cb3a890690173a51a5"
|
||||
}
|
||||
17
backend/.sqlx/query-b64c7f3b483c74c51aecda65664c36d9686112182dbf35651e494da739b922f9.json
generated
Normal file
17
backend/.sqlx/query-b64c7f3b483c74c51aecda65664c36d9686112182dbf35651e494da739b922f9.json
generated
Normal 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": "b64c7f3b483c74c51aecda65664c36d9686112182dbf35651e494da739b922f9"
|
||||
}
|
||||
18
backend/.sqlx/query-b83e58972d8f8285eb604de94c3d363102f5f6705d6debb442a85126ed59d863.json
generated
Normal file
18
backend/.sqlx/query-b83e58972d8f8285eb604de94c3d363102f5f6705d6debb442a85126ed59d863.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b83e58972d8f8285eb604de94c3d363102f5f6705d6debb442a85126ed59d863"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['failure_module'], $1),\n ARRAY['step'], $2)\n WHERE id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c461bdf083d7aaacafb6c981556b218342626524ee0c92f083dbff2930b7de9e"
|
||||
}
|
||||
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
35
backend/.sqlx/query-d02c1cfefc7d5f87ca4555a092f5fbc777a91271b80d0e488a8cae79afd56d8f.json
generated
Normal file
35
backend/.sqlx/query-d02c1cfefc7d5f87ca4555a092f5fbc777a91271b80d0e488a8cae79afd56d8f.json
generated
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg\n FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version\n WHERE a.workspace_id = $1 AND a.path = $2\n ORDER BY created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "app_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "version_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d02c1cfefc7d5f87ca4555a092f5fbc777a91271b80d0e488a8cae79afd56d8f"
|
||||
}
|
||||
23
backend/.sqlx/query-d6c25421bb6513908697ebe74c159da0ef78b5252b6ac669a3c9e545d05c0d43.json
generated
Normal file
23
backend/.sqlx/query-d6c25421bb6513908697ebe74c159da0ef78b5252b6ac669a3c9e545d05c0d43.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "int4",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d6c25421bb6513908697ebe74c159da0ef78b5252b6ac669a3c9e545d05c0d43"
|
||||
}
|
||||
17
backend/.sqlx/query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json
generated
Normal file
17
backend/.sqlx/query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority from script where hash = $1 AND workspace_id = $2",
|
||||
"query": "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use from script where hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
@@ -64,6 +63,11 @@
|
||||
"ordinal": 7,
|
||||
"name": "priority",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "delete_after_use",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -80,8 +84,9 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "11ae5e1aade2bc9a78af2d956ece73766ae3330319619a68383b508a76f2d19c"
|
||||
"hash": "e541faa52c530c787d45c5ee70930687587c139606056bdc06cd860abeb0805b"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT success\n FROM completed_job\n WHERE id = ANY($1)\n ",
|
||||
"query": "SELECT success FROM completed_job WHERE id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -18,5 +18,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ed400dbf4a1fa597bc984240cfe9069369a8a265b127116187d78c0ba93ae076"
|
||||
"hash": "e58cf2e3deb9aa1e9f37313a33e5d44fcfdb40a4764e1dc34896bd85b3d007f9"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "int4",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e5a130478c8a581412f786b430c2c166c489faccbd0d9b448f8e7ad57559e605"
|
||||
}
|
||||
15
backend/.sqlx/query-ec4f70d9e6392ade016245de73e7a2831d430838307b086c96511f91e76a214a.json
generated
Normal file
15
backend/.sqlx/query-ec4f70d9e6392ade016245de73e7a2831d430838307b086c96511f91e76a214a.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET git_sync = $1 WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ec4f70d9e6392ade016245de73e7a2831d430838307b086c96511f91e76a214a"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority 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, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use 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": [
|
||||
{
|
||||
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
@@ -64,6 +63,11 @@
|
||||
"ordinal": 7,
|
||||
"name": "priority",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "delete_after_use",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -80,8 +84,9 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b6074eb9369de071f79165bf506fbc44f1688035e1928b4c3649d875187b1e1d"
|
||||
"hash": "f760667ffd6727443f023480627f62728bf9b6284d92e5fa9b97dc4e0b1c3a93"
|
||||
}
|
||||
@@ -39,7 +39,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -61,7 +62,8 @@
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies"
|
||||
"appdependencies",
|
||||
"deploymentcallback"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
445
backend/Cargo.lock
generated
445
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.219.0"
|
||||
version = "1.224.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -21,7 +21,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.219.0"
|
||||
version = "1.224.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -35,6 +35,7 @@ incremental = true
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
|
||||
saml = ["windmill-api/saml"]
|
||||
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
|
||||
flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
|
||||
loki = ["windmill-common/loki"]
|
||||
@@ -193,7 +194,7 @@ tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-
|
||||
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls"]}
|
||||
postgres-native-tls = "^0"
|
||||
native-tls = "^0"
|
||||
samael = { version = "0.0.12", features = ["xmlsec"] }
|
||||
samael = { version = "0.0.14", features = ["xmlsec"] }
|
||||
gcp_auth = "0.9.0"
|
||||
rust_decimal = { version = "^1", features = ["db-postgres"]}
|
||||
jsonwebtoken = "8.3.0"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE workspace_settings DROP COLUMN git_sync;
|
||||
|
||||
DROP TABLE IF EXISTS deployment_metadata;
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE JOB_KIND ADD VALUE IF NOT EXISTS 'deploymentcallback';
|
||||
ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS git_sync JSONB;
|
||||
|
||||
CREATE TABLE deployment_metadata(
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
|
||||
path VARCHAR(255) NOT NULL,
|
||||
script_hash BIGINT,
|
||||
app_version BIGSERIAL,
|
||||
callback_job_ids UUID[],
|
||||
deployment_msg TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_script ON deployment_metadata (workspace_id, script_hash) WHERE script_hash IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_flow ON deployment_metadata (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_app ON deployment_metadata (workspace_id, path, app_version) WHERE app_version IS NOT NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE script DROP COLUMN timeout;
|
||||
ALTER TABLE script DROP COLUMN delete_after_use;
|
||||
ALTER TABLE script DROP COLUMN restart_unless_cancelled;
|
||||
ALTER TABLE flow DROP COLUMN timeout;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE script ADD COLUMN timeout INTEGER;
|
||||
ALTER TABLE flow ADD COLUMN timeout INTEGER;
|
||||
ALTER TABLE script ADD COLUMN delete_after_use BOOLEAN;
|
||||
ALTER TABLE script ADD COLUMN restart_unless_cancelled BOOLEAN;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE deployment_metadata;
|
||||
|
||||
CREATE TABLE deployment_metadata(
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
|
||||
path VARCHAR(255) NOT NULL,
|
||||
script_hash BIGINT,
|
||||
app_version BIGINT,
|
||||
callback_job_ids UUID[],
|
||||
deployment_msg TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_script ON deployment_metadata (workspace_id, script_hash) WHERE script_hash IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_flow ON deployment_metadata (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_app ON deployment_metadata (workspace_id, path, app_version) WHERE app_version IS NOT NULL;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Add up migration script here
|
||||
DROP TABLE deployment_metadata;
|
||||
|
||||
CREATE TABLE deployment_metadata(
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
|
||||
path VARCHAR(255) NOT NULL,
|
||||
script_hash BIGINT,
|
||||
app_version BIGINT,
|
||||
callback_job_ids UUID[],
|
||||
deployment_msg TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_script ON deployment_metadata (workspace_id, script_hash) WHERE script_hash IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_flow ON deployment_metadata (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS deployment_metadata_app ON deployment_metadata (workspace_id, path, app_version) WHERE app_version IS NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
INSERT INTO global_settings VALUES ('custom_tags', '["chromium"]')
|
||||
ON CONFLICT (name) DO UPDATE SET value = jsonb_insert(global_settings.value, '{0}', '"chromium"', true)
|
||||
WHERE NOT global_settings.value @> '["chromium"]';
|
||||
|
||||
|
||||
@@ -61,24 +61,39 @@ lazy_static::lazy_static! {
|
||||
static ref RE_CODE_PGSQL: Regex = Regex::new(r#"(?m)\$(\d+)(?:::(\w+(?:\[\])?))?"#).unwrap();
|
||||
|
||||
// -- $1 name (type) = default
|
||||
static ref RE_ARG_MYSQL: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *[\r\n$]"#).unwrap();
|
||||
static ref RE_ARG_MYSQL: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
pub static ref RE_ARG_MYSQL_NAMED: Regex = Regex::new(r#"(?m)^-- :([a-z_][a-z0-9_]*) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
static ref RE_ARG_PGSQL: Regex = Regex::new(r#"(?m)^-- \$(\d+) (\w+)(?: ?\= ?(.+))? *[\r\n$]"#).unwrap();
|
||||
static ref RE_ARG_PGSQL: Regex = Regex::new(r#"(?m)^-- \$(\d+) (\w+)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
// -- @name (type) = default
|
||||
static ref RE_ARG_BIGQUERY: Regex = Regex::new(r#"(?m)^-- @(\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *[\r\n$]"#).unwrap();
|
||||
static ref RE_ARG_BIGQUERY: Regex = Regex::new(r#"(?m)^-- @(\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *[\r\n$]"#).unwrap();
|
||||
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
|
||||
static ref RE_ARG_MSSQL: Regex = Regex::new(r#"(?m)^-- @(?:P|p)\d+ (\w+) \((\w+)\)(?: ?\= ?(.+))? *[\r\n$]"#).unwrap();
|
||||
static ref RE_ARG_MSSQL: Regex = Regex::new(r#"(?m)^-- @(?:P|p)\d+ (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
}
|
||||
|
||||
fn parsed_default(parsed_typ: &Typ, default: String) -> Option<serde_json::Value> {
|
||||
match parsed_typ {
|
||||
_ if default.to_lowercase() == "null" => None,
|
||||
Typ::Int => default.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => default.parse::<f64>().ok().map(|x| json!(x)),
|
||||
Typ::Bool => default.parse::<bool>().ok().map(|x| json!(x)),
|
||||
Typ::Str(_) if default.len() >= 2 && default.starts_with("'") && default.ends_with("'") => {
|
||||
Some(json!(&default[1..default.len() - 1]))
|
||||
}
|
||||
_ => Some(json!(default)),
|
||||
}
|
||||
}
|
||||
fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args: Vec<Arg> = vec![];
|
||||
|
||||
for cap in RE_ARG_MYSQL.captures_iter(code) {
|
||||
let mut using_named_args = false;
|
||||
for cap in RE_ARG_MYSQL_NAMED.captures_iter(code) {
|
||||
using_named_args = true;
|
||||
let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap();
|
||||
let typ = cap
|
||||
.get(2)
|
||||
@@ -88,11 +103,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_mysql_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| match parsed_typ {
|
||||
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
|
||||
_ => Some(json!(x)),
|
||||
});
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
@@ -102,6 +113,30 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
});
|
||||
}
|
||||
|
||||
if !using_named_args {
|
||||
// backwards compatibility
|
||||
for cap in RE_ARG_MYSQL.captures_iter(code) {
|
||||
let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap();
|
||||
let typ = cap
|
||||
.get(2)
|
||||
.map(|x| x.as_str().to_string().to_lowercase())
|
||||
.unwrap();
|
||||
let default = cap.get(3).map(|x| x.as_str().to_string());
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_mysql_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
@@ -141,11 +176,8 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let default = cap.get(3).map(|x| x.as_str().to_string());
|
||||
let has_default = default.is_some();
|
||||
let oarg = args[(i.unwrap() - 1) as usize].clone();
|
||||
let parsed_default = default.and_then(|x| match oarg.typ {
|
||||
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
|
||||
_ => Some(json!(x)),
|
||||
});
|
||||
let parsed_default = default.and_then(|x| parsed_default(&oarg.typ, x));
|
||||
|
||||
args[(i.unwrap() - 1) as usize] =
|
||||
Arg { name, typ: oarg.typ, default: parsed_default, otyp: oarg.otyp, has_default };
|
||||
}
|
||||
@@ -166,11 +198,8 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_bigquery_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| match parsed_typ {
|
||||
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
|
||||
_ => Some(json!(x)),
|
||||
});
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
@@ -196,11 +225,8 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_snowflake_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| match parsed_typ {
|
||||
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
|
||||
_ => Some(json!(x)),
|
||||
});
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
@@ -226,11 +252,8 @@ fn parse_mssql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_mssql_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| match parsed_typ {
|
||||
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
|
||||
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
|
||||
_ => Some(json!(x)),
|
||||
});
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
@@ -251,6 +274,7 @@ pub fn parse_mysql_typ(typ: &str) -> Typ {
|
||||
"int" | "uint" | "integer" => Typ::Int,
|
||||
"bool" | "bit" => Typ::Bool,
|
||||
"double precision" | "float" | "real" | "dec" | "fixed" => Typ::Float,
|
||||
"date" | "datetime" | "timestamp" | "time" => Typ::Datetime,
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,6 +311,12 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
|
||||
} else {
|
||||
let literals = types
|
||||
.into_iter()
|
||||
.filter(|x| match ***x {
|
||||
TsType::TsKeywordType(TsKeywordType { kind, .. }) => {
|
||||
kind != TsKeywordTypeKind::TsStringKeyword
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
.map(|x| match &**x {
|
||||
TsType::TsLitType(TsLitType {
|
||||
lit: TsLit::Str(Str { value, .. }), ..
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"collaborators": [
|
||||
"Ruben Fiszel <ruben@windmill.dev>"
|
||||
],
|
||||
"version": "1.202.1",
|
||||
"version": "1.222.0",
|
||||
"files": [
|
||||
"windmill_parser_wasm_bg.wasm",
|
||||
"windmill_parser_wasm.js",
|
||||
|
||||
@@ -97,15 +97,6 @@ function getInt32Memory0() {
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
@@ -124,6 +115,15 @@ function getFloat64Memory0() {
|
||||
return cachedFloat64Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let cachedBigInt64Memory0 = null;
|
||||
|
||||
function getBigInt64Memory0() {
|
||||
@@ -518,6 +518,10 @@ function __wbg_get_imports() {
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_eval_1081105c41705556 = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
@@ -526,14 +530,6 @@ function __wbg_get_imports() {
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
imports.wbg.__wbg_eval_f742bccbdcf34b02 = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function(arg0) {
|
||||
const v = getObject(arg0);
|
||||
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||
@@ -551,6 +547,10 @@ function __wbg_get_imports() {
|
||||
const ret = getObject(arg0) === getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) {
|
||||
const ret = BigInt.asUintN(64, arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'number' ? obj : undefined;
|
||||
@@ -566,19 +566,19 @@ function __wbg_get_imports() {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) {
|
||||
const ret = BigInt.asUintN(64, arg0);
|
||||
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) == getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_get_4a9aa5157afeb382 = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_get_f01601b5a68d10e3 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_length_cace2e0b3ddc0502 = function(arg0) {
|
||||
imports.wbg.__wbg_length_1009b1af0c481d7b = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
@@ -586,39 +586,39 @@ function __wbg_get_imports() {
|
||||
const ret = typeof(getObject(arg0)) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_next_15da6a3df9290720 = function(arg0) {
|
||||
imports.wbg.__wbg_next_9b877f231f476d01 = function(arg0) {
|
||||
const ret = getObject(arg0).next;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_next_1989a20442400aaa = function() { return handleError(function (arg0) {
|
||||
imports.wbg.__wbg_next_6529ee0cca8d57ed = function() { return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).next();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_done_bc26bf4ada718266 = function(arg0) {
|
||||
imports.wbg.__wbg_done_5fe336b092d60cf2 = function(arg0) {
|
||||
const ret = getObject(arg0).done;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_0570714ff7d75f35 = function(arg0) {
|
||||
imports.wbg.__wbg_value_0c248a78fdc8e19f = function(arg0) {
|
||||
const ret = getObject(arg0).value;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function() {
|
||||
imports.wbg.__wbg_iterator_db7ca081358d4fb2 = function() {
|
||||
const ret = Symbol.iterator;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_2aff440840bb6202 = function() { return handleError(function (arg0, arg1) {
|
||||
imports.wbg.__wbg_get_7b48513de5dc5ea4 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_669127b9d730c650 = function() { return handleError(function (arg0, arg1) {
|
||||
imports.wbg.__wbg_call_90c26b09837aba1c = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_isArray_38525be7442aa21e = function(arg0) {
|
||||
imports.wbg.__wbg_isArray_74fb723e24f76012 = function(arg0) {
|
||||
const ret = Array.isArray(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function(arg0) {
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_e7d53d51371448e2 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof ArrayBuffer;
|
||||
@@ -628,30 +628,30 @@ function __wbg_get_imports() {
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function(arg0) {
|
||||
imports.wbg.__wbg_isSafeInteger_f93fde0dca9820f8 = function(arg0) {
|
||||
const ret = Number.isSafeInteger(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_entries_6d727b73ee02b7ce = function(arg0) {
|
||||
imports.wbg.__wbg_entries_9e2e2aa45aa5094a = function(arg0) {
|
||||
const ret = Object.entries(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_buffer_344d9b41efe96da7 = function(arg0) {
|
||||
imports.wbg.__wbg_buffer_a448f833075b71ba = function(arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_d8a000788389a31e = function(arg0) {
|
||||
imports.wbg.__wbg_new_8f67e318f15d7254 = function(arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_dcfd613a3420f908 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_set_2357bf09366ee480 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbg_length_a5587d6cd79ab197 = function(arg0) {
|
||||
imports.wbg.__wbg_length_1d25fa9e4ac21ce7 = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function(arg0) {
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_bced6f43aed8c1aa = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof Uint8Array;
|
||||
|
||||
Binary file not shown.
@@ -218,7 +218,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// migration code to avoid break
|
||||
windmill_api::migrate_db(&db).await?;
|
||||
|
||||
let last_mig_version = sqlx::query_scalar!(
|
||||
let new_last_mig_version = sqlx::query_scalar!(
|
||||
"select version from _sqlx_migrations order by version desc limit 1;"
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
@@ -226,12 +226,19 @@ async fn main() -> anyhow::Result<()> {
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
tracing::info!(
|
||||
"Completed potential migration of the db. Last migration version: {last_mig_version:?}",
|
||||
);
|
||||
if last_mig_version != new_last_mig_version {
|
||||
tracing::info!(
|
||||
"Completed migration of the db. New migration version: {}",
|
||||
new_last_mig_version.unwrap_or(-1)
|
||||
);
|
||||
} else {
|
||||
tracing::info!("No migration, db was up-to-date");
|
||||
}
|
||||
}
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
let shutdown_signal = windmill_common::shutdown_signal(tx.clone(), rx.resubscribe());
|
||||
let (killpill_tx, killpill_rx) = tokio::sync::broadcast::channel::<()>(2);
|
||||
let (killpill_phase2_tx, killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2);
|
||||
let shutdown_signal =
|
||||
windmill_common::shutdown_signal(killpill_tx.clone(), killpill_rx.resubscribe());
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
tracing::info!(
|
||||
@@ -275,7 +282,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
default_base_internal_url.clone()
|
||||
};
|
||||
|
||||
initial_load(&db, tx.clone(), worker_mode, server_mode).await;
|
||||
initial_load(&db, killpill_tx.clone(), worker_mode, server_mode).await;
|
||||
|
||||
monitor_db(&db, &base_internal_url, rsmq.clone(), server_mode).await;
|
||||
|
||||
@@ -298,7 +305,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
db.clone(),
|
||||
rsmq2,
|
||||
addr,
|
||||
rx.resubscribe(),
|
||||
killpill_phase2_rx.resubscribe(),
|
||||
base_internal_tx,
|
||||
server_mode,
|
||||
)
|
||||
@@ -318,25 +325,29 @@ Windmill Community Edition {GIT_VERSION}
|
||||
if worker_mode {
|
||||
run_workers(
|
||||
db.clone(),
|
||||
rx.resubscribe(),
|
||||
tx.clone(),
|
||||
killpill_rx.resubscribe(),
|
||||
killpill_tx.clone(),
|
||||
num_workers,
|
||||
base_internal_url.clone(),
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("All workers exited.");
|
||||
tx.send(())?; // signal server to shutdown
|
||||
killpill_tx.send(())?;
|
||||
} else {
|
||||
killpill_rx.resubscribe().recv().await?;
|
||||
}
|
||||
tracing::info!("Starting phase 2 of shutdown");
|
||||
killpill_phase2_tx.send(())?;
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
|
||||
let monitor_f = async {
|
||||
let db = db.clone();
|
||||
let tx = tx.clone();
|
||||
let tx = killpill_tx.clone();
|
||||
let rsmq = rsmq.clone();
|
||||
|
||||
let mut rx = rx.resubscribe();
|
||||
let mut rx = killpill_rx.resubscribe();
|
||||
let base_internal_url = base_internal_url.to_string();
|
||||
let h = tokio::spawn(async move {
|
||||
let mut listener = retry_listen_pg(&db).await;
|
||||
@@ -472,8 +483,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!("Metrics are only available in the EE, ignoring...");
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
windmill_common::serve_metrics(*METRICS_ADDR, rx.resubscribe(), num_workers > 0)
|
||||
.await;
|
||||
windmill_common::serve_metrics(
|
||||
*METRICS_ADDR,
|
||||
killpill_phase2_rx.resubscribe(),
|
||||
num_workers > 0,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
@@ -481,7 +496,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
let instance_name = rd_string(8);
|
||||
schedule_stats(instance_name, mode, &db, &HTTP_CLIENT).await;
|
||||
|
||||
futures::try_join!(shutdown_signal, server_f, metrics_f, workers_f, monitor_f)?;
|
||||
futures::try_join!(shutdown_signal, workers_f, monitor_f, server_f, metrics_f)?;
|
||||
} else {
|
||||
tracing::info!("Nothing to do, exiting.");
|
||||
}
|
||||
|
||||
@@ -1084,6 +1084,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None
|
||||
},
|
||||
FlowModule {
|
||||
id: "b".to_string(),
|
||||
@@ -1119,6 +1120,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
}],
|
||||
},
|
||||
stop_after_if: Default::default(),
|
||||
@@ -1130,6 +1132,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
],
|
||||
same_worker: false,
|
||||
@@ -1230,6 +1233,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
FlowModule {
|
||||
id: "b".to_string(),
|
||||
@@ -1276,6 +1280,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
FlowModule {
|
||||
id: "e".to_string(),
|
||||
@@ -1308,6 +1313,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1320,26 +1326,26 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None
|
||||
},
|
||||
FlowModule {
|
||||
id: "c".to_string(),
|
||||
value: FlowModuleValue::RawScript {
|
||||
input_transforms: [
|
||||
|
||||
(
|
||||
"loops".to_string(),
|
||||
InputTransform::Javascript { expr: "results.b".to_string() },
|
||||
),
|
||||
(
|
||||
"path".to_string(),
|
||||
InputTransform::Static { value: json!("outer.txt") },
|
||||
),
|
||||
(
|
||||
"path2".to_string(),
|
||||
InputTransform::Static { value: json!("inner.txt") },
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
(
|
||||
"loops".to_string(),
|
||||
InputTransform::Javascript { expr: "results.b".to_string() },
|
||||
),
|
||||
(
|
||||
"path".to_string(),
|
||||
InputTransform::Static { value: json!("outer.txt") },
|
||||
),
|
||||
(
|
||||
"path2".to_string(),
|
||||
InputTransform::Static { value: json!("inner.txt") },
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
language: ScriptLang::Deno,
|
||||
content: r#"export async function main(path: string, loops: string[], path2: string) {
|
||||
return await Deno.readTextFile(`./shared/${path}`) + "," + loops + "," + await Deno.readTextFile(`./shared/${path2}`);
|
||||
@@ -1360,6 +1366,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
],
|
||||
same_worker: true,
|
||||
@@ -2579,8 +2586,10 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
|
||||
ws_error_handler_muted: None,
|
||||
priority: None,
|
||||
dedicated_worker: None,
|
||||
timeout: None,
|
||||
},
|
||||
draft_only: None,
|
||||
deployment_message: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -2802,10 +2811,9 @@ async fn test_complex_flow_restart(db: Pool<Postgres>) {
|
||||
}),
|
||||
}).run_until_complete(&db, port).await;
|
||||
|
||||
assert_eq!(
|
||||
first_run_result.json_result().unwrap(),
|
||||
restarted_flow_result.json_result().unwrap()
|
||||
);
|
||||
let first_run_result_int = serde_json::from_value::<i32>(first_run_result.json_result().unwrap()).expect("first_run_result was not an int");
|
||||
let restarted_flow_result_int = serde_json::from_value::<i32>(restarted_flow_result.json_result().unwrap()).expect("restarted_flow_result was not an int");
|
||||
assert_eq!(first_run_result_int, restarted_flow_result_int);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
@@ -3129,6 +3137,10 @@ async fn run_deployed_relative_imports(db: &Pool<Postgres>, script_content: Stri
|
||||
schema: std::collections::HashMap::new(),
|
||||
ws_error_handler_muted: Some(false),
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
timeout: None,
|
||||
restart_unless_cancelled: None,
|
||||
deployment_message: None,
|
||||
},
|
||||
).await.unwrap();
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ name = "windmill_api"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-queue/enterprise", "async-stripe", "samael", "windmill-audit/enterprise"]
|
||||
enterprise = ["windmill-queue/enterprise", "async-stripe", "windmill-audit/enterprise"]
|
||||
saml = ["samael"]
|
||||
benchmark = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.219.0
|
||||
version: 1.224.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -1192,6 +1192,8 @@ paths:
|
||||
type: boolean
|
||||
large_file_storage:
|
||||
$ref: "#/components/schemas/LargeFileStorage"
|
||||
git_sync:
|
||||
$ref: "#/components/schemas/WorkspaceGitSync"
|
||||
required:
|
||||
- code_completion_enabled
|
||||
|
||||
@@ -1513,6 +1515,32 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/edit_git_sync_config:
|
||||
post:
|
||||
summary: edit workspace git sync settings
|
||||
operationId: editWorkspaceGitSyncConfig
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: Workspace Git sync settings
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
git_sync_settings:
|
||||
$ref: "#/components/schemas/WorkspaceGitSync"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/get_large_file_storage_config:
|
||||
get:
|
||||
summary: get large file storage config
|
||||
@@ -2197,8 +2225,7 @@ paths:
|
||||
|
||||
/w/{workspace}/resources/get_value_interpolated/{path}:
|
||||
get:
|
||||
summary:
|
||||
get resource interpolated (variables and resources are fully unrolled)
|
||||
summary: get resource interpolated (variables and resources are fully unrolled)
|
||||
operationId: getResourceValueInterpolated
|
||||
tags:
|
||||
- resource
|
||||
@@ -2886,8 +2913,7 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
- name: first_parent_hash
|
||||
description:
|
||||
mask to filter scripts whom first direct parent has exact hash
|
||||
description: mask to filter scripts whom first direct parent has exact hash
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -3082,8 +3108,7 @@ paths:
|
||||
|
||||
/workers/custom_tags:
|
||||
get:
|
||||
summary:
|
||||
get all instance custom tags (tags are used to dispatch jobs to
|
||||
summary: get all instance custom tags (tags are used to dispatch jobs to
|
||||
different worker groups)
|
||||
operationId: getCustomTags
|
||||
tags:
|
||||
@@ -3134,8 +3159,7 @@ paths:
|
||||
|
||||
/w/{workspace}/scripts/delete/h/{hash}:
|
||||
post:
|
||||
summary:
|
||||
delete script by hash (erase content but keep hash, require admin)
|
||||
summary: delete script by hash (erase content but keep hash, require admin)
|
||||
operationId: deleteScriptByHash
|
||||
tags:
|
||||
- script
|
||||
@@ -3201,6 +3225,53 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewScriptWithDraft"
|
||||
|
||||
/w/{workspace}/scripts/history/p/{path}:
|
||||
get:
|
||||
summary: get history of a script by path
|
||||
operationId: getScriptHistoryByPath
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: script history
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ScriptHistory"
|
||||
|
||||
/w/{workspace}/scripts/history_update/h/{hash}/p/{path}:
|
||||
post:
|
||||
summary: update history of a script
|
||||
operationId: updateScriptHistory
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptHash"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
requestBody:
|
||||
description: Script deployment message
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deployment_msg:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: success
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/scripts/raw/p/{path}:
|
||||
get:
|
||||
summary: raw script by path
|
||||
@@ -3327,16 +3398,14 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3718,6 +3787,8 @@ paths:
|
||||
properties:
|
||||
draft_only:
|
||||
type: boolean
|
||||
deployment_message:
|
||||
type: string
|
||||
responses:
|
||||
"201":
|
||||
description: flow created
|
||||
@@ -3741,7 +3812,12 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OpenFlowWPath"
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/OpenFlowWPath"
|
||||
- type: object
|
||||
properties:
|
||||
deployment_message:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
"200":
|
||||
@@ -3979,6 +4055,8 @@ paths:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
draft_only:
|
||||
type: boolean
|
||||
deployment_message:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -4043,6 +4121,53 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppWithLastVersionWDraft"
|
||||
|
||||
/w/{workspace}/apps/history/p/{path}:
|
||||
get:
|
||||
summary: get app history by path
|
||||
operationId: getAppHistoryByPath
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: app history
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AppHistory"
|
||||
|
||||
/w/{workspace}/apps/history_update/a/{id}/v/{version}:
|
||||
post:
|
||||
summary: update app history
|
||||
operationId: updateAppHistory
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/PathId"
|
||||
- $ref: "#/components/parameters/PathVersion"
|
||||
requestBody:
|
||||
description: App deployment message
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deployment_msg:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: success
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps_u/public_app/{path}:
|
||||
get:
|
||||
summary: get public app by secret
|
||||
@@ -4233,6 +4358,8 @@ paths:
|
||||
value: {}
|
||||
policy:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
deployment_message:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: app updated
|
||||
@@ -4309,8 +4436,7 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -4318,8 +4444,7 @@ paths:
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the flow owner (default false)
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -4371,8 +4496,7 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -4380,8 +4504,7 @@ paths:
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the flow owner (default false)
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -4419,8 +4542,7 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -4428,8 +4550,7 @@ paths:
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -4460,8 +4581,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -4494,8 +4614,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -4710,8 +4829,7 @@ paths:
|
||||
- job
|
||||
responses:
|
||||
"200":
|
||||
description:
|
||||
the timestamp of the db that can be used to compute the drift
|
||||
description: the timestamp of the db that can be used to compute the drift
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -4947,8 +5065,7 @@ paths:
|
||||
|
||||
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
|
||||
get:
|
||||
summary:
|
||||
get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getResumeUrls
|
||||
tags:
|
||||
- job
|
||||
@@ -5499,8 +5616,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description:
|
||||
only list the groups the user is member of (default false)
|
||||
description: only list the groups the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -5688,8 +5804,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description:
|
||||
only list the folders the user is member of (default false)
|
||||
description: only list the folders the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -5913,6 +6028,26 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkerPing"
|
||||
|
||||
/workers/exists_worker_with_tag:
|
||||
get:
|
||||
summary: exists worker with tag
|
||||
operationId: existsWorkerWithTag
|
||||
tags:
|
||||
- worker
|
||||
parameters:
|
||||
- name: tag
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: whether a worker with the tag exists
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/configs/list_worker_groups:
|
||||
get:
|
||||
summary: list worker groups
|
||||
@@ -6345,6 +6480,39 @@ paths:
|
||||
properties:
|
||||
connection_settings_str:
|
||||
type: string
|
||||
/w/{workspace}/job_helpers/v2/duckdb_connection_settings:
|
||||
post:
|
||||
summary:
|
||||
Converts an S3 resource to the set of instructions necessary to connect
|
||||
DuckDB to an S3 bucket
|
||||
operationId: duckdbConnectionSettingsV2
|
||||
tags:
|
||||
- helpers
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
s3_resource_path:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Connection settings
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
connection_settings_str:
|
||||
type: string
|
||||
required:
|
||||
- connection_settings_str
|
||||
|
||||
/w/{workspace}/job_helpers/polars_connection_settings:
|
||||
post:
|
||||
summary:
|
||||
@@ -6390,6 +6558,74 @@ paths:
|
||||
- use_ssl
|
||||
- cache_regions
|
||||
- client_kwargs
|
||||
/w/{workspace}/job_helpers/v2/polars_connection_settings:
|
||||
post:
|
||||
summary:
|
||||
Converts an S3 resource to the set of arguments necessary to connect
|
||||
Polars to an S3 bucket
|
||||
operationId: polarsConnectionSettingsV2
|
||||
tags:
|
||||
- helpers
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
s3_resource_path:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Connection settings
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
s3fs_args:
|
||||
type: object
|
||||
properties:
|
||||
endpoint_url:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
secret:
|
||||
type: string
|
||||
use_ssl:
|
||||
type: boolean
|
||||
cache_regions:
|
||||
type: boolean
|
||||
client_kwargs:
|
||||
$ref: "#/components/schemas/PolarsClientKwargs"
|
||||
required:
|
||||
- endpoint_url
|
||||
- use_ssl
|
||||
- cache_regions
|
||||
- client_kwargs
|
||||
polars_cloud_options:
|
||||
type: object
|
||||
properties:
|
||||
aws_endpoint_url:
|
||||
type: string
|
||||
aws_access_key_id:
|
||||
type: string
|
||||
aws_secret_access_key:
|
||||
type: string
|
||||
aws_region:
|
||||
type: string
|
||||
aws_allow_http:
|
||||
type: boolean
|
||||
required:
|
||||
- aws_endpoint_url
|
||||
- aws_region
|
||||
- aws_allow_http
|
||||
required:
|
||||
- s3fs_args
|
||||
- cloud_options
|
||||
|
||||
/w/{workspace}/job_helpers/test_connection:
|
||||
get:
|
||||
@@ -6486,6 +6722,10 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: csv_has_header
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: read_bytes_from
|
||||
in: query
|
||||
schema:
|
||||
@@ -6580,6 +6820,12 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
PathVersion:
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
Name:
|
||||
name: name
|
||||
in: path
|
||||
@@ -6594,8 +6840,7 @@ components:
|
||||
type: integer
|
||||
PerPage:
|
||||
name: per_page
|
||||
description:
|
||||
number of items to return for a given page (default 30, max 100)
|
||||
description: number of items to return for a given page (default 30, max 100)
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -6729,8 +6974,7 @@ components:
|
||||
type: boolean
|
||||
ArgsFilter:
|
||||
name: args
|
||||
description:
|
||||
filter on jobs containing those args as a json subset (@> in postgres)
|
||||
description: filter on jobs containing those args as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -6742,8 +6986,7 @@ components:
|
||||
type: string
|
||||
ResultFilter:
|
||||
name: result
|
||||
description:
|
||||
filter on jobs containing those result as a json subset (@> in postgres)
|
||||
description: filter on jobs containing those result as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -6912,6 +7155,12 @@ components:
|
||||
type: boolean
|
||||
priority:
|
||||
type: integer
|
||||
restart_unless_cancelled:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: integer
|
||||
delete_after_use:
|
||||
type: boolean
|
||||
required:
|
||||
- hash
|
||||
- path
|
||||
@@ -6990,6 +7239,14 @@ components:
|
||||
type: boolean
|
||||
priority:
|
||||
type: integer
|
||||
restart_unless_cancelled:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: integer
|
||||
delete_after_use:
|
||||
type: boolean
|
||||
deployment_message:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- summary
|
||||
@@ -7009,6 +7266,16 @@ components:
|
||||
required:
|
||||
- hash
|
||||
|
||||
ScriptHistory:
|
||||
type: object
|
||||
properties:
|
||||
script_hash:
|
||||
type: string
|
||||
deployment_msg:
|
||||
type: string
|
||||
required:
|
||||
- script_hash
|
||||
|
||||
ScriptArgs:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
@@ -7125,6 +7392,7 @@ components:
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"deploymentcallback",
|
||||
]
|
||||
schedule_path:
|
||||
type: string
|
||||
@@ -7233,6 +7501,7 @@ components:
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"deploymentcallback",
|
||||
]
|
||||
schedule_path:
|
||||
type: string
|
||||
@@ -8257,6 +8526,8 @@ components:
|
||||
type: integer
|
||||
dedicated_worker:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: number
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
@@ -8279,6 +8550,8 @@ components:
|
||||
type: integer
|
||||
dedicated_worker:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: number
|
||||
required:
|
||||
- path
|
||||
|
||||
@@ -8442,6 +8715,16 @@ components:
|
||||
type: boolean
|
||||
draft: {}
|
||||
|
||||
AppHistory:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: integer
|
||||
deployment_msg:
|
||||
type: string
|
||||
required:
|
||||
- version
|
||||
|
||||
SlackToken:
|
||||
type: object
|
||||
properties:
|
||||
@@ -8562,3 +8845,14 @@ components:
|
||||
- endPoint
|
||||
- useSSL
|
||||
- pathStyle
|
||||
|
||||
WorkspaceGitSync:
|
||||
type: object
|
||||
properties:
|
||||
script_path:
|
||||
type: string
|
||||
git_repo_resource_path:
|
||||
type: string
|
||||
required:
|
||||
- script_path
|
||||
- git_repo_resource_path
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::collections::HashMap;
|
||||
*/
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
deployment_metadata_helpers,
|
||||
users::{require_owner_of_path, OptAuthed},
|
||||
variables::build_crypt,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
@@ -54,6 +55,8 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/update/*path", post(update_app))
|
||||
.route("/delete/*path", delete(delete_app))
|
||||
.route("/create", post(create_app))
|
||||
.route("/history/p/*path", get(get_app_history))
|
||||
.route("/history_update/a/:id/v/:version", post(update_app_history))
|
||||
}
|
||||
|
||||
pub fn unauthed_service() -> Router {
|
||||
@@ -124,6 +127,19 @@ pub struct AppWithLastVersionAndDraft {
|
||||
pub draft_only: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AppHistory {
|
||||
pub app_id: i64,
|
||||
pub version: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AppHistoryUpdate {
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
pub type StaticFields = HashMap<String, Box<RawValue>>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -153,6 +169,7 @@ pub struct CreateApp {
|
||||
pub value: serde_json::Value,
|
||||
pub policy: Policy,
|
||||
pub draft_only: Option<bool>,
|
||||
pub deployment_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -161,6 +178,7 @@ pub struct EditApp {
|
||||
pub summary: Option<String>,
|
||||
pub value: Option<serde_json::Value>,
|
||||
pub policy: Option<Policy>,
|
||||
pub deployment_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -312,6 +330,64 @@ async fn get_app_w_draft(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
async fn get_app_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<Vec<AppHistory>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let query_result = sqlx::query!(
|
||||
"SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg
|
||||
FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version
|
||||
WHERE a.workspace_id = $1 AND a.path = $2
|
||||
ORDER BY created_at DESC",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
).fetch_all(&mut *tx).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let result: Vec<AppHistory> = query_result
|
||||
.into_iter()
|
||||
.map(|row| AppHistory {
|
||||
app_id: row.app_id,
|
||||
version: row.version_id,
|
||||
deployment_msg: row.deployment_msg,
|
||||
})
|
||||
.collect();
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
async fn update_app_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, app_id, app_version)): Path<(String, i64, i64)>,
|
||||
Json(app_history_update): Json<AppHistoryUpdate>,
|
||||
) -> Result<()> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let app_path = sqlx::query_scalar!("SELECT path FROM app WHERE id = $1", app_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if app_path.is_none() {
|
||||
tx.commit().await?;
|
||||
return Err(Error::NotFound(
|
||||
format!("App with ID {app_id} not found").to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
w_id,
|
||||
app_path.unwrap(),
|
||||
app_version,
|
||||
app_history_update.deployment_msg,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn get_app_by_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -512,8 +588,8 @@ async fn create_app(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, tx) = push(
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -538,7 +614,27 @@ async fn create_app(
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
tx.commit().await?;
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::App { path: app.path.clone(), version: v_id },
|
||||
app.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateApp { workspace: w_id, path: app.path.clone() },
|
||||
@@ -753,11 +849,10 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
|
||||
PushIsolationLevel::Transaction(tx);
|
||||
if let Some(v_id) = v_id {
|
||||
let tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
|
||||
PushIsolationLevel::Transaction(tx);
|
||||
|
||||
let (dependency_job_uuid, tx) = push(
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -782,10 +877,28 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
tx.commit().await?;
|
||||
} else {
|
||||
tx.commit().await?;
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::App { path: npath.clone(), version: v_id },
|
||||
ns.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
|
||||
176
backend/windmill-api/src/deployment_metadata_helpers.rs
Normal file
176
backend/windmill-api/src/deployment_metadata_helpers.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::db::DB;
|
||||
use crate::workspaces::{WorkspaceGitRepo, WorkspaceSettings};
|
||||
|
||||
use serde_json::json;
|
||||
use windmill_common::error::{Error, Result};
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::scripts::ScriptHash;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_queue::PushIsolationLevel;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DeployedObject {
|
||||
Script { hash: ScriptHash, path: String },
|
||||
Flow { path: String },
|
||||
App { path: String, version: i64 },
|
||||
}
|
||||
|
||||
impl DeployedObject {
|
||||
pub fn get_path(&self) -> &str {
|
||||
match self {
|
||||
DeployedObject::Script { path, .. } => path,
|
||||
DeployedObject::Flow { path } => path,
|
||||
DeployedObject::App { path, .. } => path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
mut tx: PushIsolationLevel<'c, R>,
|
||||
authed: &ApiAuthed,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
obj: DeployedObject,
|
||||
deployment_message: Option<String>,
|
||||
) -> Result<PushIsolationLevel<'c, R>> {
|
||||
let skip_git_sync = if obj.get_path().starts_with("u/") {
|
||||
tracing::debug!(
|
||||
"Ignoring {} from git sync as it's in a private user folder",
|
||||
obj.get_path()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let workspace_git_repo_setting = sqlx::query_as::<_, WorkspaceSettings>(
|
||||
"SELECT * FROM workspace_settings WHERE workspace_id = $1",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
if workspace_git_repo_setting.is_none() {
|
||||
return Err(Error::InternalErr(
|
||||
"No workspace settings found for workspace ID".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let workspace_git_repo = workspace_git_repo_setting
|
||||
.unwrap()
|
||||
.git_sync
|
||||
.map(|conf| serde_json::from_value::<WorkspaceGitRepo>(conf).ok())
|
||||
.flatten();
|
||||
|
||||
let (git_sync_job_uuid, mut new_tx) = if !skip_git_sync && workspace_git_repo.is_some() {
|
||||
let workspace_git_repo = workspace_git_repo.unwrap();
|
||||
|
||||
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
|
||||
args.insert(
|
||||
"repo_url_resource_path".to_string(),
|
||||
json!(workspace_git_repo
|
||||
.git_repo_resource_path
|
||||
.strip_prefix("$res:")),
|
||||
);
|
||||
|
||||
let default_commit_msg: String;
|
||||
match obj.clone() {
|
||||
DeployedObject::Script { path, .. } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("Script '{}' deployed", path);
|
||||
}
|
||||
DeployedObject::Flow { path } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("Flow '{}' deployed", path);
|
||||
}
|
||||
DeployedObject::App { path, .. } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("App '{}' deployed", path);
|
||||
}
|
||||
}
|
||||
args.insert(
|
||||
"commit_msg".to_string(),
|
||||
json!(deployment_message.clone().unwrap_or(default_commit_msg)),
|
||||
);
|
||||
|
||||
let (job_uuid, new_tx) = windmill_queue::push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::DeploymentCallback { path: workspace_git_repo.script_path.clone() },
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
(Some(job_uuid), new_tx)
|
||||
} else {
|
||||
let new_tx = match tx {
|
||||
PushIsolationLevel::Isolated(user_db, authed, rsmq) => {
|
||||
(rsmq, user_db.begin(&authed).await?).into()
|
||||
}
|
||||
PushIsolationLevel::IsolatedRoot(db, rsmq) => (rsmq, db.begin().await?).into(),
|
||||
PushIsolationLevel::Transaction(tx) => tx,
|
||||
};
|
||||
(None, new_tx)
|
||||
};
|
||||
|
||||
// We're not persisting the default commit msg as it's pretty useless. We will persist the ones manually set by users
|
||||
let job_uuids = if git_sync_job_uuid.is_some() {
|
||||
vec![git_sync_job_uuid.unwrap()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
if deployment_message.is_some() || job_uuids.len() > 0 {
|
||||
// if the git sync job hasn't been triggered, and there is not custom deployment message, there's not point adding an entry to the table
|
||||
match obj.clone() {
|
||||
DeployedObject::Script { path, hash, .. } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, hash.0, &job_uuids, deployment_message,
|
||||
)
|
||||
},
|
||||
DeployedObject::Flow { path } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL DO UPDATE SET callback_job_ids = $3, deployment_msg = $4",
|
||||
w_id, path, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
DeployedObject::App { path, version } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, version, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
}.execute(&mut new_tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
return Ok(tx);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::deployment_metadata_helpers;
|
||||
use crate::{
|
||||
db::DB,
|
||||
schedule::clear_schedule,
|
||||
@@ -354,8 +355,8 @@ async fn create_flow(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, mut tx) = push(
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, mut new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -389,9 +390,28 @@ async fn create_flow(
|
||||
nf.path,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.execute(&mut new_tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
|
||||
nf.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
@@ -565,9 +585,9 @@ async fn update_flow(
|
||||
},
|
||||
);
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
|
||||
let (dependency_job_uuid, mut tx) = push(
|
||||
let (dependency_job_uuid, mut new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -600,17 +620,37 @@ async fn update_flow(
|
||||
nf.path,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.execute(&mut new_tx)
|
||||
.await?;
|
||||
if let Some(old_dep_job) = old_dep_job {
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET canceled = true WHERE id = $1",
|
||||
old_dep_job
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.execute(&mut new_tx)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
|
||||
nf.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nf.path.to_string())
|
||||
}
|
||||
|
||||
@@ -823,6 +863,7 @@ mod tests {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
FlowModule {
|
||||
id: "b".to_string(),
|
||||
@@ -848,6 +889,7 @@ mod tests {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
FlowModule {
|
||||
id: "c".to_string(),
|
||||
@@ -870,6 +912,7 @@ mod tests {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
},
|
||||
],
|
||||
failure_module: Some(FlowModule {
|
||||
@@ -891,6 +934,7 @@ mod tests {
|
||||
mock: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
}),
|
||||
same_worker: false,
|
||||
concurrent_limit: None,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::{
|
||||
db::DB, resources::transform_json_value, users::Tokened, workspaces::LargeFileStorage,
|
||||
db::DB, resources::get_resource_value_interpolated_internal, users::Tokened,
|
||||
workspaces::LargeFileStorage,
|
||||
};
|
||||
use aws_sdk_s3::config::{BehaviorVersion, Credentials, Region};
|
||||
use axum::{
|
||||
@@ -39,10 +40,22 @@ pub fn workspaced_service() -> Router {
|
||||
"/duckdb_connection_settings",
|
||||
post(duckdb_connection_settings).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/v2/duckdb_connection_settings",
|
||||
post(duckdb_connection_settings_v2).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/polars_connection_settings",
|
||||
post(polars_connection_settings).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/v2/polars_connection_settings",
|
||||
post(polars_connection_settings_v2).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/blah",
|
||||
post(polars_connection_settings_v2).layer(cors.clone()),
|
||||
)
|
||||
.route("/test_connection", get(test_connection).layer(cors.clone()))
|
||||
.route(
|
||||
"/list_stored_files",
|
||||
@@ -113,13 +126,83 @@ async fn duckdb_connection_settings(
|
||||
return Ok(Json(response));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DuckdbConnectionSettingsQueryV2 {
|
||||
s3_resource_path: Option<String>,
|
||||
}
|
||||
|
||||
async fn duckdb_connection_settings_v2(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Tokened { token }: Tokened,
|
||||
Path(w_id): Path<String>,
|
||||
Json(query): Json<DuckdbConnectionSettingsQueryV2>,
|
||||
) -> error::JsonResult<DuckdbConnectionSettingsResponse> {
|
||||
let s3_resource_opt = match query.s3_resource_path {
|
||||
Some(s3_resource_path) => {
|
||||
get_s3_resource(
|
||||
&authed,
|
||||
&user_db,
|
||||
&db,
|
||||
&token,
|
||||
&w_id,
|
||||
s3_resource_path.as_str(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
None => get_workspace_s3_resource(&authed, &user_db, &db, &token, &w_id).await?,
|
||||
};
|
||||
let s3_resource = s3_resource_opt.ok_or(error::Error::NotFound(
|
||||
"No datasets storage resource defined at the workspace level".to_string(),
|
||||
))?;
|
||||
return duckdb_connection_settings(
|
||||
Path(w_id),
|
||||
Json(DuckdbConnectionSettingsQuery { s3_resource }),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PolarsConnectionSettingsQuery {
|
||||
s3_resource: S3Resource,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PolarsConnectionSettings {
|
||||
pub region_name: String,
|
||||
}
|
||||
|
||||
async fn polars_connection_settings(
|
||||
Path(_w_id): Path<String>,
|
||||
Json(query): Json<PolarsConnectionSettingsQuery>,
|
||||
) -> error::JsonResult<S3fsArgs> {
|
||||
let s3_resource = query.s3_resource;
|
||||
|
||||
let response = S3fsArgs {
|
||||
endpoint_url: render_endpoint(&s3_resource),
|
||||
key: s3_resource.access_key,
|
||||
secret: s3_resource.secret_key,
|
||||
use_ssl: s3_resource.use_ssl,
|
||||
cache_regions: false,
|
||||
client_kwargs: PolarsConnectionSettings { region_name: s3_resource.region },
|
||||
};
|
||||
return Ok(Json(response));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PolarsConnectionSettingsQueryV2 {
|
||||
s3_resource_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PolarsConnectionSettingsResponse {
|
||||
s3fs_args: S3fsArgs,
|
||||
polars_cloud_options: PolarsCloudOptions,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct S3fsArgs {
|
||||
endpoint_url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
key: Option<String>,
|
||||
@@ -131,25 +214,57 @@ struct PolarsConnectionSettingsResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PolarsConnectionSettings {
|
||||
pub region_name: String,
|
||||
struct PolarsCloudOptions {
|
||||
aws_endpoint_url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
aws_access_key_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
aws_secret_access_key: Option<String>,
|
||||
aws_region: String,
|
||||
aws_allow_http: bool,
|
||||
}
|
||||
|
||||
async fn polars_connection_settings(
|
||||
Path(_w_id): Path<String>,
|
||||
Json(query): Json<PolarsConnectionSettingsQuery>,
|
||||
async fn polars_connection_settings_v2(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Tokened { token }: Tokened,
|
||||
Path(w_id): Path<String>,
|
||||
Json(query): Json<PolarsConnectionSettingsQueryV2>,
|
||||
) -> error::JsonResult<PolarsConnectionSettingsResponse> {
|
||||
let s3_resource = query.s3_resource;
|
||||
|
||||
let response = PolarsConnectionSettingsResponse {
|
||||
endpoint_url: render_endpoint(&s3_resource),
|
||||
key: s3_resource.access_key,
|
||||
secret: s3_resource.secret_key,
|
||||
use_ssl: s3_resource.use_ssl,
|
||||
cache_regions: false,
|
||||
client_kwargs: PolarsConnectionSettings { region_name: s3_resource.region },
|
||||
let s3_resource_opt = match query.s3_resource_path {
|
||||
Some(s3_resource_path) => {
|
||||
get_s3_resource(
|
||||
&authed,
|
||||
&user_db,
|
||||
&db,
|
||||
&token,
|
||||
&w_id,
|
||||
s3_resource_path.as_str(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
None => get_workspace_s3_resource(&authed, &user_db, &db, &token, &w_id).await?,
|
||||
};
|
||||
let s3_resource = s3_resource_opt.ok_or(error::Error::NotFound(
|
||||
"No datasets storage resource defined at the workspace level".to_string(),
|
||||
))?;
|
||||
let s3fs = polars_connection_settings(
|
||||
Path(w_id),
|
||||
Json(PolarsConnectionSettingsQuery { s3_resource: s3_resource.clone() }),
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
let response = PolarsConnectionSettingsResponse {
|
||||
s3fs_args: s3fs,
|
||||
polars_cloud_options: PolarsCloudOptions {
|
||||
aws_endpoint_url: render_endpoint(&s3_resource),
|
||||
aws_access_key_id: s3_resource.access_key,
|
||||
aws_secret_access_key: s3_resource.secret_key,
|
||||
aws_region: s3_resource.region,
|
||||
aws_allow_http: !s3_resource.use_ssl,
|
||||
},
|
||||
};
|
||||
|
||||
return Ok(Json(response));
|
||||
}
|
||||
|
||||
@@ -284,8 +399,9 @@ struct LoadFilePreviewQuery {
|
||||
pub file_size_in_bytes: Option<i64>,
|
||||
pub file_mime_type: Option<String>,
|
||||
|
||||
// For CSV files, the separator needs to be specify
|
||||
pub csv_separator: Option<String>,
|
||||
// For CSV files, the separator needs to be specified
|
||||
pub csv_separator: Option<String>, // defaults to ','
|
||||
pub csv_has_header: Option<bool>, // defaults to true
|
||||
|
||||
// Specify the content length to be read. Both will be taken into account when reading files, except for:
|
||||
// - CSVs: only the length will be taken into account, a CSV file larger than this will be truncated.
|
||||
@@ -402,17 +518,17 @@ async fn load_file_preview(
|
||||
} else {
|
||||
query.read_bytes_length
|
||||
};
|
||||
|
||||
let content_type: WindmillContentType;
|
||||
let content_preview = match s3_object_mime_type.as_deref() {
|
||||
Some("text/csv") => {
|
||||
content_type = WindmillContentType::Csv;
|
||||
read_s3_csv_object_head(
|
||||
csv_file_preview_with_fallback(
|
||||
&s3_client,
|
||||
&s3_bucket,
|
||||
&file_key,
|
||||
file_chunk_length,
|
||||
query.csv_separator,
|
||||
query.csv_has_header,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -432,18 +548,36 @@ async fn load_file_preview(
|
||||
.await
|
||||
}
|
||||
mt_opt => {
|
||||
let lowercased_file_key = file_key.to_lowercase();
|
||||
// sometimes S3 doesn't infer the content type on upload. Guess it from the file extension
|
||||
if file_key.to_lowercase().ends_with(".parquet") {
|
||||
if lowercased_file_key.ends_with(".parquet") {
|
||||
content_type = WindmillContentType::Parquet;
|
||||
read_s3_parquet_object_head(&s3_resource, &file_key).await
|
||||
} else if file_key.to_lowercase().ends_with(".csv") {
|
||||
} else if lowercased_file_key.ends_with(".csv") {
|
||||
content_type = WindmillContentType::Csv;
|
||||
read_s3_csv_object_head(
|
||||
csv_file_preview_with_fallback(
|
||||
&s3_client,
|
||||
&s3_bucket,
|
||||
&file_key,
|
||||
file_chunk_length,
|
||||
query.csv_separator,
|
||||
query.csv_has_header,
|
||||
)
|
||||
.await
|
||||
} else if lowercased_file_key.ends_with(".json")
|
||||
|| lowercased_file_key.ends_with(".yaml")
|
||||
|| lowercased_file_key.ends_with(".yml")
|
||||
|| lowercased_file_key.ends_with(".xml")
|
||||
|| lowercased_file_key.ends_with(".txt")
|
||||
|| lowercased_file_key.ends_with(".log")
|
||||
{
|
||||
content_type = WindmillContentType::RawText;
|
||||
read_s3_text_object_head(
|
||||
&s3_client,
|
||||
&s3_bucket,
|
||||
&file_key,
|
||||
query.read_bytes_from,
|
||||
file_chunk_length,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@@ -514,20 +648,37 @@ async fn get_workspace_s3_resource<'c>(
|
||||
LargeFileStorage::S3Storage(s3_lfs) => s3_lfs,
|
||||
};
|
||||
|
||||
let resource_path_json_value = serde_json::to_value(s3_lfs.s3_resource_path)
|
||||
.map_err(|err| error::Error::InternalErr(err.to_string()))?;
|
||||
let interpolated_value = transform_json_value(
|
||||
let stripped_resource_path = match s3_lfs.s3_resource_path.strip_prefix("$res:") {
|
||||
Some(stripped) => stripped,
|
||||
None => s3_lfs.s3_resource_path.as_str(),
|
||||
};
|
||||
return get_s3_resource(authed, user_db, db, token, w_id, stripped_resource_path).await;
|
||||
}
|
||||
|
||||
async fn get_s3_resource<'c>(
|
||||
authed: &ApiAuthed,
|
||||
user_db: &UserDB,
|
||||
db: &DB,
|
||||
token: &str,
|
||||
w_id: &str,
|
||||
s3_resource_path: &str,
|
||||
) -> error::Result<Option<S3Resource>> {
|
||||
let s3_resource_value_raw = get_resource_value_interpolated_internal(
|
||||
authed,
|
||||
user_db,
|
||||
db,
|
||||
&w_id,
|
||||
resource_path_json_value,
|
||||
&Option::None,
|
||||
w_id,
|
||||
s3_resource_path,
|
||||
None,
|
||||
token,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let s3_resource = serde_json::from_value::<S3Resource>(interpolated_value)
|
||||
if s3_resource_value_raw.is_none() {
|
||||
return Err(error::Error::NotFound("Resource not found".to_string()));
|
||||
}
|
||||
|
||||
let s3_resource = serde_json::from_value::<S3Resource>(s3_resource_value_raw.unwrap())
|
||||
.map_err(|err| error::Error::InternalErr(err.to_string()))?;
|
||||
return Ok(Some(s3_resource));
|
||||
}
|
||||
@@ -682,23 +833,45 @@ async fn read_s3_parquet_object_head(
|
||||
return polars_df_result;
|
||||
}
|
||||
|
||||
async fn csv_file_preview_with_fallback(
|
||||
s3_client: &aws_sdk_s3::Client,
|
||||
s3_bucket: &str,
|
||||
file_key: &str,
|
||||
length: i64,
|
||||
separator: Option<String>,
|
||||
has_header: Option<bool>,
|
||||
) -> error::Result<String> {
|
||||
match read_s3_csv_object_head(
|
||||
&s3_client, &s3_bucket, &file_key, length, separator, has_header,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(csv_preview) => Ok(csv_preview),
|
||||
Err(_) => {
|
||||
// fallback to default text file preview is the CSV could not be parsed. It's a text file after all
|
||||
let raw_text =
|
||||
read_s3_text_object_head(&s3_client, &s3_bucket, &file_key, 0, length).await?;
|
||||
return Ok(raw_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_s3_csv_object_head(
|
||||
s3_client: &aws_sdk_s3::Client,
|
||||
s3_bucket: &str,
|
||||
file_key: &str,
|
||||
length: i64,
|
||||
separator: Option<String>,
|
||||
has_header: Option<bool>,
|
||||
) -> error::Result<String> {
|
||||
let separator_final = if let Some(separator_char) = separator {
|
||||
if separator_char.len() != 1 {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Separator must be a single character".to_string(),
|
||||
));
|
||||
}
|
||||
separator_char.as_bytes()[0]
|
||||
} else {
|
||||
",".as_bytes()[0] // polars uses the comma as default, doing the same here
|
||||
};
|
||||
let separator_final = match separator {
|
||||
Some(separator_char) if separator_char == "\\t" => Ok("\t".as_bytes()[0]),
|
||||
Some(separator_char) if separator_char.len() != 1 => Err(error::Error::BadRequest(
|
||||
"Separator must be a single character".to_string(),
|
||||
)),
|
||||
Some(separator_char) => Ok(separator_char.as_bytes()[0]),
|
||||
None => Ok(",".as_bytes()[0]), // polars uses the comma as default, doing the same here
|
||||
}?;
|
||||
|
||||
let s3_object = s3_client
|
||||
.get_object()
|
||||
@@ -723,6 +896,7 @@ async fn read_s3_csv_object_head(
|
||||
let csv_df = CsvReader::new(cursor)
|
||||
.with_n_rows(Some(10)) // for now read only first 10 lines
|
||||
.with_separator(separator_final)
|
||||
.has_header(has_header.unwrap_or(true))
|
||||
.finish()
|
||||
.map_err(|err| error::Error::InternalErr(err.to_string()))?;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use axum::http::HeaderValue;
|
||||
use serde_json::value::RawValue;
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::flow_status::RestartedFrom;
|
||||
@@ -328,9 +329,10 @@ pub async fn get_path_tag_limits_cache_for_hash(
|
||||
ScriptLang,
|
||||
Option<bool>,
|
||||
Option<i16>,
|
||||
Option<bool>,
|
||||
)> {
|
||||
let script = sqlx::query!(
|
||||
"select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority from script where hash = $1 AND workspace_id = $2",
|
||||
"select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use from script where hash = $1 AND workspace_id = $2",
|
||||
hash,
|
||||
w_id
|
||||
)
|
||||
@@ -350,6 +352,7 @@ pub async fn get_path_tag_limits_cache_for_hash(
|
||||
script.language,
|
||||
script.dedicated_worker,
|
||||
script.priority,
|
||||
script.delete_after_use,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1811,7 +1814,8 @@ pub async fn run_job_by_path(
|
||||
|
||||
check_scopes(&authed, || format!("run:script/{script_path}"))?;
|
||||
|
||||
let (job_payload, tag) = script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
let (job_payload, tag, _delete_after_use) =
|
||||
script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
let scheduled_for = run_query.get_scheduled_for(&db).await?;
|
||||
|
||||
check_tag_available_for_workspace(&w_id, &tag).await?;
|
||||
@@ -1874,15 +1878,16 @@ impl Drop for Guard {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WindmillStatusCode {
|
||||
pub struct WindmillCompositeResult {
|
||||
windmill_status_code: Option<u16>,
|
||||
windmill_content_type: Option<String>,
|
||||
result: Option<Box<RawValue>>,
|
||||
}
|
||||
async fn run_wait_result<T>(
|
||||
db: &DB,
|
||||
uuid: Uuid,
|
||||
Path((w_id, _)): Path<(String, T)>,
|
||||
node_id: Option<String>,
|
||||
node_id_for_empty_return: Option<String>,
|
||||
) -> error::Result<Response> {
|
||||
let mut result;
|
||||
let timeout = SERVER_CONFIG.read().await.timeout_wait_result.clone();
|
||||
@@ -1898,10 +1903,16 @@ async fn run_wait_result<T>(
|
||||
let mut accumulated_delay = 0 as u64;
|
||||
|
||||
loop {
|
||||
if let Some(node_id) = node_id.as_ref() {
|
||||
result = get_result_by_id_from_running_flow(&db, &w_id, &uuid, node_id, None)
|
||||
.await
|
||||
.ok();
|
||||
if let Some(node_id_for_empty_return) = node_id_for_empty_return.as_ref() {
|
||||
result = get_result_by_id_from_running_flow(
|
||||
&db,
|
||||
&w_id,
|
||||
&uuid,
|
||||
node_id_for_empty_return,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
} else {
|
||||
let row =
|
||||
sqlx::query("SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2")
|
||||
@@ -1934,13 +1945,53 @@ async fn run_wait_result<T>(
|
||||
if let Some(result) = result {
|
||||
g.done = true;
|
||||
|
||||
let status_code = serde_json::from_str::<WindmillStatusCode>(result.get());
|
||||
match status_code {
|
||||
Ok(WindmillStatusCode { windmill_status_code: Some(status_code), result }) => {
|
||||
Err(Error::CustomStatusCode(
|
||||
StatusCode::from_u16(status_code).unwrap_or_else(|_| StatusCode::IM_A_TEAPOT),
|
||||
result,
|
||||
))
|
||||
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
|
||||
match composite_result {
|
||||
Ok(WindmillCompositeResult {
|
||||
windmill_status_code,
|
||||
windmill_content_type,
|
||||
result: result_value,
|
||||
}) => {
|
||||
if windmill_content_type.is_none() && windmill_status_code.is_none() {
|
||||
return Ok(Json(result).into_response());
|
||||
}
|
||||
|
||||
let status_code_or_default = windmill_status_code
|
||||
.map(|val| match StatusCode::from_u16(val) {
|
||||
Ok(sc) => Ok(sc),
|
||||
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
|
||||
})
|
||||
.unwrap_or(if result_value.is_some() {
|
||||
Ok(StatusCode::OK)
|
||||
} else {
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
})?;
|
||||
|
||||
if windmill_content_type.is_some() {
|
||||
let serialized_json_result = result_value
|
||||
.map(|val| val.get().to_owned())
|
||||
.unwrap_or_else(String::new);
|
||||
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
|
||||
// it falls back to the original serialized JSON if it doesn't work.
|
||||
let serialized_result =
|
||||
serde_json::from_str::<String>(serialized_json_result.as_str())
|
||||
.ok()
|
||||
.unwrap_or(serialized_json_result);
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
[(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(windmill_content_type.unwrap().as_str()).unwrap(),
|
||||
)],
|
||||
serialized_result,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
Json(result_value), // default to JSON result if no content type is provided
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
_ => Ok(Json(result).into_response()),
|
||||
}
|
||||
@@ -1949,6 +2000,18 @@ async fn run_wait_result<T>(
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_job_metadata_after_use(db: &DB, job_uuid: Uuid) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE completed_job
|
||||
SET logs = '##DELETED##', args = '{}'::jsonb, result = '{}'::jsonb
|
||||
WHERE id = $1",
|
||||
job_uuid,
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_queue_too_long(db: &DB, queue_limit: Option<i64>) -> error::Result<()> {
|
||||
if let Some(limit) = queue_limit {
|
||||
let count = sqlx::query_scalar!(
|
||||
@@ -2025,7 +2088,8 @@ pub async fn run_wait_result_job_by_path_get(
|
||||
let script_path = script_path.to_path();
|
||||
check_scopes(&authed, || format!("run:script/{script_path}"))?;
|
||||
|
||||
let (job_payload, tag) = script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
let (job_payload, tag, delete_after_use) =
|
||||
script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
check_tag_available_for_workspace(&w_id, &tag).await?;
|
||||
let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq);
|
||||
|
||||
@@ -2055,7 +2119,11 @@ pub async fn run_wait_result_job_by_path_get(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(&db, uuid, Path((w_id, script_path)), None).await
|
||||
let wait_result = run_wait_result(&db, uuid, Path((w_id, script_path)), None).await;
|
||||
if delete_after_use.unwrap_or(false) {
|
||||
delete_job_metadata_after_use(&db, uuid).await?;
|
||||
}
|
||||
return wait_result;
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_flow_by_path_get(
|
||||
@@ -2137,7 +2205,8 @@ async fn run_wait_result_script_by_path_internal(
|
||||
let script_path = script_path.to_path();
|
||||
check_scopes(&authed, || format!("run:script/{script_path}"))?;
|
||||
|
||||
let (job_payload, tag) = script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
let (job_payload, tag, delete_after_use) =
|
||||
script_path_to_payload(script_path, &db, &w_id).await?;
|
||||
|
||||
check_tag_available_for_workspace(&w_id, &tag).await?;
|
||||
let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq);
|
||||
@@ -2168,7 +2237,11 @@ async fn run_wait_result_script_by_path_internal(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(&db, uuid, Path((w_id, script_path)), None).await
|
||||
let wait_result = run_wait_result(&db, uuid, Path((w_id, script_path)), None).await;
|
||||
if delete_after_use.unwrap_or(false) {
|
||||
delete_job_metadata_after_use(&db, uuid).await?;
|
||||
}
|
||||
return wait_result;
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_script_by_hash(
|
||||
@@ -2195,6 +2268,7 @@ pub async fn run_wait_result_script_by_hash(
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
delete_after_use,
|
||||
) = get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
|
||||
check_scopes(&authed, || format!("run:script/{path}"))?;
|
||||
|
||||
@@ -2236,7 +2310,11 @@ pub async fn run_wait_result_script_by_hash(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(&db, uuid, Path((w_id, script_hash)), None).await
|
||||
let wait_result = run_wait_result(&db, uuid, Path((w_id, script_hash)), None).await;
|
||||
if delete_after_use.unwrap_or(false) {
|
||||
delete_job_metadata_after_use(&db, uuid).await?;
|
||||
}
|
||||
return wait_result;
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_flow_by_path(
|
||||
@@ -2417,6 +2495,7 @@ async fn add_batch_jobs(
|
||||
language,
|
||||
dedicated_worker,
|
||||
_priority,
|
||||
_delete_after_use,
|
||||
) = get_latest_deployed_hash_for_path(&db, &w_id, &path).await?;
|
||||
(
|
||||
Some(script_hash),
|
||||
@@ -2608,6 +2687,7 @@ pub async fn run_job_by_hash(
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
_delete_after_use, // not taken into account in async endpoints
|
||||
) = get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
|
||||
check_scopes(&authed, || format!("run:script/{path}"))?;
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ use tower_http::{
|
||||
trace::TraceLayer,
|
||||
};
|
||||
use windmill_common::db::UserDB;
|
||||
#[cfg(feature = "saml")]
|
||||
use windmill_common::ee::{get_license_plan, LicensePlan};
|
||||
use windmill_common::utils::rd_string;
|
||||
use windmill_common::worker::ALL_TAGS;
|
||||
use windmill_common::BASE_URL;
|
||||
@@ -47,6 +49,7 @@ mod audit;
|
||||
mod capture;
|
||||
mod configs;
|
||||
mod db;
|
||||
mod deployment_metadata_helpers;
|
||||
mod drafts;
|
||||
pub mod ee;
|
||||
pub mod embeddings;
|
||||
@@ -157,10 +160,13 @@ pub async fn run_server(
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let sp_extension: (ServiceProviderExt, SamlSsoLogin) = saml::build_sp_extension().await?;
|
||||
#[cfg(feature = "saml")]
|
||||
let sp_extension: (ServiceProviderExt, SamlSsoLogin) = match get_license_plan().await {
|
||||
LicensePlan::Enterprise => saml::build_sp_extension().await?,
|
||||
LicensePlan::Pro => (ServiceProviderExt(None), SamlSsoLogin(None)),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
#[cfg(not(feature = "saml"))]
|
||||
let sp_extension = (ServiceProviderExt(), SamlSsoLogin(None));
|
||||
|
||||
let embeddings_db = if server_mode {
|
||||
|
||||
@@ -873,6 +873,7 @@ async fn slack_command(
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
_delete_after_use,
|
||||
) = windmill_common::get_latest_deployed_hash_for_path(
|
||||
&db,
|
||||
&settings.workspace_id,
|
||||
|
||||
@@ -399,42 +399,54 @@ async fn get_resource_value_interpolated(
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Query(job_info): Query<JobInfo>,
|
||||
) -> JsonResult<Option<serde_json::Value>> {
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
return get_resource_value_interpolated_internal(
|
||||
&authed,
|
||||
&user_db,
|
||||
&db,
|
||||
w_id.as_str(),
|
||||
path.to_path(),
|
||||
job_info.job_id,
|
||||
token.as_str(),
|
||||
)
|
||||
.await
|
||||
.map(|success| Json(success));
|
||||
}
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
|
||||
pub async fn get_resource_value_interpolated_internal(
|
||||
authed: &ApiAuthed,
|
||||
user_db: &UserDB,
|
||||
db: &DB,
|
||||
workspace: &str,
|
||||
path: &str,
|
||||
job_id: Option<Uuid>,
|
||||
token: &str,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let mut tx = user_db.clone().begin(authed).await?;
|
||||
|
||||
let value_o = sqlx::query_scalar!(
|
||||
"SELECT value from resource WHERE path = $1 AND workspace_id = $2",
|
||||
path.to_owned(),
|
||||
&w_id
|
||||
path,
|
||||
workspace
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
if value_o.is_none() {
|
||||
explain_resource_perm_error(&path, &w_id, &db).await?;
|
||||
explain_resource_perm_error(path, workspace, db).await?;
|
||||
}
|
||||
|
||||
let value = not_found_if_none(value_o, "Resource", path)?;
|
||||
if let Some(value) = value {
|
||||
Ok(Json(Some(
|
||||
transform_json_value(
|
||||
&authed,
|
||||
&user_db,
|
||||
&db,
|
||||
&w_id,
|
||||
value,
|
||||
&job_info.job_id,
|
||||
&token,
|
||||
)
|
||||
.await?,
|
||||
)))
|
||||
Ok(Some(
|
||||
transform_json_value(authed, user_db, db, workspace, value, &job_id, token).await?,
|
||||
))
|
||||
} else {
|
||||
Ok(Json(None))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
|
||||
#[async_recursion]
|
||||
pub async fn transform_json_value<'c>(
|
||||
authed: &ApiAuthed,
|
||||
|
||||
@@ -7,42 +7,45 @@
|
||||
*/
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use axum::response::Redirect;
|
||||
use axum::{routing::post, Router};
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use axum::{Extension, Form};
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use samael::metadata::{ContactPerson, ContactType, EntityDescriptor};
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use samael::service_provider::{ServiceProvider, ServiceProviderBuilder};
|
||||
|
||||
use serde::Deserialize;
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use tower_cookies::Cookies;
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use windmill_common::error::{Error, Result};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use crate::db::DB;
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use crate::users::login_externally;
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
use crate::BASE_URL;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
#[derive(Clone)]
|
||||
pub struct ServiceProviderExt(pub Option<ServiceProvider>);
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
#[cfg(not(feature = "saml"))]
|
||||
pub struct ServiceProviderExt();
|
||||
|
||||
#[cfg(feature = "saml")]
|
||||
use windmill_common::ee::{get_license_plan, LicensePlan};
|
||||
|
||||
pub struct SamlSsoLogin(pub Option<String>);
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
pub async fn build_sp_extension() -> anyhow::Result<(ServiceProviderExt, SamlSsoLogin)> {
|
||||
if let Some(url_metadata) = std::env::var("SAML_METADATA").ok() {
|
||||
//todo restrict for non ee
|
||||
@@ -90,13 +93,18 @@ pub struct SamlForm {
|
||||
pub SAMLResponse: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "saml")]
|
||||
pub async fn acs(
|
||||
Extension(db): Extension<DB>,
|
||||
cookies: Cookies,
|
||||
Extension(se): Extension<Arc<ServiceProviderExt>>,
|
||||
Form(s): Form<SamlForm>,
|
||||
) -> Result<Redirect> {
|
||||
if matches!(get_license_plan().await, LicensePlan::Pro) {
|
||||
return Err(Error::BadRequest(
|
||||
"SAML not available in the pro plan".to_string(),
|
||||
));
|
||||
};
|
||||
if let Some(sp_m) = &se.0 {
|
||||
let sp = sp_m.clone();
|
||||
if let Some(encoded_resp) = s.SAMLResponse {
|
||||
@@ -121,7 +129,7 @@ pub async fn acs(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
#[cfg(not(feature = "saml"))]
|
||||
pub async fn acs() -> String {
|
||||
"SAML available only in enterprise version".to_string()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
deployment_metadata_helpers,
|
||||
schedule::clear_schedule,
|
||||
users::{maybe_refresh_folders, require_owner_of_path, AuthCache},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
@@ -39,7 +40,7 @@ use windmill_common::{
|
||||
schedule::Schedule,
|
||||
scripts::{
|
||||
to_i64, HubScript, ListScriptQuery, ListableScript, NewScript, Schema, Script, ScriptHash,
|
||||
ScriptKind, ScriptLang,
|
||||
ScriptHistory, ScriptHistoryUpdate, ScriptKind, ScriptLang,
|
||||
},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{
|
||||
@@ -81,6 +82,10 @@ pub struct ScriptWDraft {
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -120,6 +125,11 @@ pub fn workspaced_service() -> Router {
|
||||
"/toggle_workspace_error_handler/p/*path",
|
||||
post(toggle_workspace_error_handler),
|
||||
)
|
||||
.route("/history/p/*path", get(get_script_history))
|
||||
.route(
|
||||
"/history_update/h/:hash/p/*path",
|
||||
post(update_script_history),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -310,6 +320,7 @@ async fn create_script(
|
||||
));
|
||||
}
|
||||
|
||||
let script_path = ns.path.clone();
|
||||
let hash = ScriptHash(hash_script(&ns));
|
||||
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
|
||||
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
|
||||
@@ -463,8 +474,8 @@ async fn create_script(
|
||||
"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) \
|
||||
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)",
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use) \
|
||||
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)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
ns.path,
|
||||
@@ -488,6 +499,8 @@ async fn create_script(
|
||||
ns.dedicated_worker,
|
||||
ns.ws_error_handler_muted.unwrap_or(false),
|
||||
ns.priority,
|
||||
ns.restart_unless_cancelled,
|
||||
ns.delete_after_use,
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
@@ -636,6 +649,16 @@ async fn create_script(
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
}
|
||||
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Script { hash: hash, path: script_path },
|
||||
ns.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
@@ -696,7 +719,7 @@ async fn get_script_by_path_w_draft(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let script_o = sqlx::query_as::<_, ScriptWDraft>(
|
||||
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority FROM script LEFT JOIN draft ON
|
||||
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use FROM script LEFT JOIN draft ON
|
||||
script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script'
|
||||
WHERE script.path = $1 AND script.workspace_id = $2 \
|
||||
AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
|
||||
@@ -712,6 +735,54 @@ async fn get_script_by_path_w_draft(
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn get_script_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<Vec<ScriptHistory>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let query_result = sqlx::query!(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by created_at DESC",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let result: Vec<ScriptHistory> = query_result
|
||||
.into_iter()
|
||||
.map(|row| ScriptHistory {
|
||||
script_hash: ScriptHash(row.hash),
|
||||
deployment_msg: row.deployment_msg,
|
||||
})
|
||||
.collect();
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
async fn update_script_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, script_hash, script_path)): Path<(String, ScriptHash, StripPath)>,
|
||||
Json(script_history_update): Json<ScriptHistoryUpdate>,
|
||||
) -> Result<()> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
w_id,
|
||||
script_path.to_path(),
|
||||
script_hash.0,
|
||||
script_history_update.deployment_msg,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn list_paths(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -56,9 +56,16 @@ pub async fn test_email(
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let smtp = test_email.smtp;
|
||||
let to = test_email.to;
|
||||
let client = SmtpClientBuilder::new(smtp.host, smtp.port)
|
||||
.implicit_tls(smtp.tls_implicit)
|
||||
.credentials((smtp.username, smtp.password));
|
||||
let client = SmtpClientBuilder::new(smtp.host, smtp.port).implicit_tls(smtp.tls_implicit);
|
||||
let client = if let (Some(username), Some(password)) = (smtp.username, smtp.password) {
|
||||
if !username.is_empty() {
|
||||
client.credentials((username, password))
|
||||
} else {
|
||||
client
|
||||
}
|
||||
} else {
|
||||
client
|
||||
};
|
||||
let message = MessageBuilder::new()
|
||||
.from(("Windmill", smtp.from.as_str()))
|
||||
.to(to.clone())
|
||||
|
||||
@@ -47,7 +47,9 @@ fn serve_path(path: &str) -> Response<BoxBody> {
|
||||
Some(content) => {
|
||||
let body = body::boxed(body::Full::from(content.data));
|
||||
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
||||
let mut res = Response::builder().header(header::CONTENT_TYPE, mime.as_ref());
|
||||
let mut res = Response::builder()
|
||||
.header(header::CONTENT_TYPE, mime.as_ref())
|
||||
.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
if mime.as_ref() == mime::APPLICATION_JAVASCRIPT {
|
||||
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
||||
} else if (mime.type_(), mime.subtype()) == (mime::TEXT, mime::CSS) {
|
||||
|
||||
@@ -42,6 +42,8 @@ use time::OffsetDateTime;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use tracing::{Instrument, Span};
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee::{get_license_plan, LicensePlan};
|
||||
use windmill_common::oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH;
|
||||
use windmill_common::users::truncate_token;
|
||||
use windmill_common::worker::{CLOUD_HOSTED, SERVER_CONFIG};
|
||||
@@ -1602,7 +1604,6 @@ async fn create_user(
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
_check_nb_of_user(&db).await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -1629,6 +1630,8 @@ async fn create_user(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx = add_to_demo_if_exists(tx, &email).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
invite_user_to_all_auto_invite_worspaces(&db, &nu.email).await?;
|
||||
@@ -1663,9 +1666,16 @@ pub fn send_email_if_possible(subject: &str, content: &str, to: &str) {
|
||||
|
||||
pub async fn send_email_if_possible_intern(subject: &str, content: &str, to: &str) -> Result<()> {
|
||||
if let Some(smtp) = SERVER_CONFIG.read().await.smtp.clone() {
|
||||
let client = SmtpClientBuilder::new(smtp.host, smtp.port)
|
||||
.implicit_tls(smtp.tls_implicit)
|
||||
.credentials((smtp.username, smtp.password));
|
||||
let client = SmtpClientBuilder::new(smtp.host, smtp.port).implicit_tls(smtp.tls_implicit);
|
||||
let client = if let (Some(username), Some(password)) = (smtp.username, smtp.password) {
|
||||
if !username.is_empty() {
|
||||
client.credentials((username, password))
|
||||
} else {
|
||||
client
|
||||
}
|
||||
} else {
|
||||
client
|
||||
};
|
||||
let message = MessageBuilder::new()
|
||||
.from(("Windmill", smtp.from.as_str()))
|
||||
.to(to)
|
||||
@@ -2335,6 +2345,10 @@ pub struct LoginUserInfo {
|
||||
}
|
||||
|
||||
async fn _check_nb_of_user(db: &DB) -> Result<()> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
if matches!(get_license_plan().await, LicensePlan::Enterprise) {
|
||||
return Ok(());
|
||||
}
|
||||
let nb_users_sso =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",)
|
||||
.fetch_one(db)
|
||||
@@ -2421,7 +2435,6 @@ pub async fn login_externally(
|
||||
name = user.clone().unwrap().displayName;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
_check_nb_of_user(&db).await?;
|
||||
|
||||
sqlx::query(&format!(
|
||||
@@ -2449,26 +2462,34 @@ pub async fn login_externally(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let demo_exists =
|
||||
sqlx::query_scalar!("SELECT EXISTS(SELECT 1 FROM workspace WHERE id = 'demo')")
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if demo_exists {
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO workspace_invite
|
||||
(workspace_id, email, is_admin)
|
||||
VALUES ('demo', $1, false)
|
||||
ON CONFLICT DO NOTHING",
|
||||
&email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
{
|
||||
tracing::error!("error inserting invite: {:#?}", e);
|
||||
}
|
||||
}
|
||||
tx = add_to_demo_if_exists(tx, email).await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_to_demo_if_exists<'c>(
|
||||
mut tx: sqlx::Transaction<'c, sqlx::Postgres>,
|
||||
email: &String,
|
||||
) -> Result<sqlx::Transaction<'c, sqlx::Postgres>> {
|
||||
let demo_exists =
|
||||
sqlx::query_scalar!("SELECT EXISTS(SELECT 1 FROM workspace WHERE id = 'demo')")
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if demo_exists {
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO workspace_invite
|
||||
(workspace_id, email, is_admin)
|
||||
VALUES ('demo', $1, false)
|
||||
ON CONFLICT DO NOTHING",
|
||||
&email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
{
|
||||
tracing::error!("error inserting invite: {:#?}", e);
|
||||
}
|
||||
}
|
||||
return Ok(tx);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use crate::db::ApiAuthed;
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_worker_pings))
|
||||
.route("/exists_worker_with_tag", get(exists_worker_with_tag))
|
||||
.route("/custom_tags", get(get_custom_tags))
|
||||
}
|
||||
|
||||
@@ -68,6 +69,27 @@ async fn list_worker_pings(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TagQuery {
|
||||
tag: String,
|
||||
}
|
||||
|
||||
async fn exists_worker_with_tag(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Query(tag_query): Query<TagQuery>,
|
||||
) -> JsonResult<bool> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let row = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> $1 AND ping_at > now() - interval '1 minute')",
|
||||
&[tag_query.tag]
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(row.exists.unwrap_or(false)))
|
||||
}
|
||||
|
||||
async fn get_custom_tags() -> Json<Vec<String>> {
|
||||
Json(ALL_TAGS.read().await.clone().into())
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use magic_crypt::MagicCryptTrait;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use stripe::CustomerId;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use chrono::{TimeZone, Datelike};
|
||||
use chrono::{TimeZone, Datelike, Timelike};
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::db::UserDB;
|
||||
@@ -84,6 +84,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_copilot_info", get(get_copilot_info) )
|
||||
.route("/edit_error_handler", post(edit_error_handler))
|
||||
.route("/edit_large_file_storage_config", post(edit_large_file_storage_config))
|
||||
.route("/edit_git_sync_config", post(edit_git_sync_config))
|
||||
.route("/leave", post(leave_workspace));
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -151,6 +152,7 @@ pub struct WorkspaceSettings {
|
||||
pub error_handler_extra_args: Option<serde_json::Value>,
|
||||
pub error_handler_muted_on_cancel: Option<bool>,
|
||||
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitRepo
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -424,22 +426,25 @@ async fn stripe_checkout(
|
||||
_ => params.customer_email = Some(&authed.email),
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
params.subscription_data = Some(stripe::CreateCheckoutSessionSubscriptionData {
|
||||
metadata: {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
map.insert("workspace_id".to_string(), w_id.clone());
|
||||
Some(map)
|
||||
},
|
||||
billing_cycle_anchor: Some({
|
||||
// first of the next month (and possibly next year) at noon UTC
|
||||
let now = Utc::now();
|
||||
let date = if now.month() == 12 {
|
||||
Utc.with_ymd_and_hms(now.year() + 1, 1, 1, 12, 0, 0).single().unwrap()
|
||||
} else {
|
||||
Utc.with_ymd_and_hms(now.year(), now.month() + 1, 1, 12, 0, 0).single().unwrap()
|
||||
};
|
||||
date.timestamp()
|
||||
}),
|
||||
billing_cycle_anchor: if now.day() == 1 && now.hour() < 12 {
|
||||
// no need to prorate so close to the billing cycle renew date
|
||||
None
|
||||
} else {
|
||||
// first of the next month (and possibly next year) at noon UTC
|
||||
let date = if now.month() == 12 {
|
||||
Utc.with_ymd_and_hms(now.year() + 1, 1, 1, 12, 0, 0).single().unwrap()
|
||||
} else {
|
||||
Utc.with_ymd_and_hms(now.year(), now.month() + 1, 1, 12, 0, 0).single().unwrap()
|
||||
};
|
||||
Some(date.timestamp())
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -751,9 +756,9 @@ async fn edit_auto_invite(
|
||||
|
||||
let domain = if ea.invite_all.is_some_and(|x| x) {
|
||||
if *CLOUD_HOSTED {
|
||||
return Err(Error::BadRequest(
|
||||
"invite_all is only available locally".to_string(),
|
||||
));
|
||||
return Err(Error::BadRequest(
|
||||
"invite_all is only available locally".to_string(),
|
||||
));
|
||||
} else {
|
||||
"*"
|
||||
}
|
||||
@@ -976,7 +981,72 @@ async fn edit_large_file_storage_config(
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit copilot config for workspace {}", &w_id))
|
||||
Ok(format!("Edit large file storage config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditGitSyncConfig {
|
||||
git_sync_settings: Option<WorkspaceGitRepo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkspaceGitRepo {
|
||||
pub script_path: String,
|
||||
pub git_repo_resource_path: String,
|
||||
}
|
||||
|
||||
async fn edit_git_sync_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, .. }: ApiAuthed,
|
||||
Json(new_config): Json<EditGitSyncConfig>,
|
||||
) -> Result<String> {
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
"Git sync is only available on Windmill Enterprise Edition".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let args_for_audit = format!("{:?}", new_config.git_sync_settings);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
"workspaces.edit_git_sync_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("git_sync_settings", args_for_audit.as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(git_sync_settings) = new_config.git_sync_settings {
|
||||
let serialized_config = serde_json::to_value::<WorkspaceGitRepo>(git_sync_settings)
|
||||
.map_err(|err| Error::InternalErr(err.to_string()))?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET git_sync = $1 WHERE workspace_id = $2",
|
||||
serialized_config,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET git_sync = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit git sync config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
async fn edit_error_handler(
|
||||
@@ -1111,7 +1181,7 @@ async fn _check_nb_of_workspaces(db: &DB) -> Result<()> {
|
||||
.await?;
|
||||
if nb_workspaces.unwrap_or(0) >= 2 {
|
||||
return Err(Error::BadRequest(
|
||||
"You have reached the maximum number of workspaces (2 outside of default worskapce 'admins') without an enterprise license. Archive/delete another workspace to create a new one"
|
||||
"You have reached the maximum number of workspaces (2 outside of default workspace 'admins') without an enterprise license. Archive/delete another workspace to create a new one"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
@@ -1678,6 +1748,12 @@ struct ScriptMetadata {
|
||||
priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn is_none_or_false(val: &Option<bool>) -> bool {
|
||||
@@ -1883,6 +1959,9 @@ async fn tarball_workspace(
|
||||
ws_error_handler_muted: script.ws_error_handler_muted,
|
||||
priority: script.priority,
|
||||
tag: script.tag,
|
||||
timeout: script.timeout,
|
||||
delete_after_use: script.delete_after_use,
|
||||
restart_unless_cancelled: script.restart_unless_cancelled,
|
||||
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
|
||||
@@ -15,6 +15,9 @@ use windmill_common::{
|
||||
utils::Pagination,
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee::{get_license_plan, LicensePlan};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::SqlBuilder;
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
@@ -44,23 +47,32 @@ pub struct AuditLog {
|
||||
pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>(
|
||||
db: E,
|
||||
username: &str,
|
||||
_operation: &str,
|
||||
mut _operation: &str,
|
||||
action_kind: ActionKind,
|
||||
w_id: &str,
|
||||
_resource: Option<&str>,
|
||||
mut _resource: Option<&str>,
|
||||
_parameters: Option<HashMap<&str, &str>>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
let p_json: serde_json::Value = serde_json::to_value(&_parameters).unwrap();
|
||||
let p_json = match get_license_plan().await {
|
||||
LicensePlan::Enterprise => serde_json::to_value(&_parameters).unwrap(),
|
||||
LicensePlan::Pro => serde_json::json!({"redacted": "-"}),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let p_json: serde_json::Value = serde_json::json!({"redacted": "-"});
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let _resource: Option<&str> = Some("EE only");
|
||||
#[cfg(feature = "enterprise")]
|
||||
if matches!(get_license_plan().await, LicensePlan::Pro) {
|
||||
_resource = Some("EE only");
|
||||
_operation = "redacted";
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let _operation: &str = "redacted";
|
||||
{
|
||||
_resource = Some("EE only");
|
||||
_operation = "redacted";
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
operation = _operation,
|
||||
|
||||
@@ -6,3 +6,18 @@ lazy_static::lazy_static! {
|
||||
pub static ref LICENSE_KEY_ID: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
pub static ref LICENSE_KEY: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
}
|
||||
|
||||
pub enum LicensePlan {
|
||||
Pro,
|
||||
Enterprise,
|
||||
}
|
||||
|
||||
pub async fn get_license_plan() -> LicensePlan {
|
||||
let id = LICENSE_KEY_ID.read().await.clone();
|
||||
|
||||
if id.ends_with("_pro") {
|
||||
LicensePlan::Pro
|
||||
} else {
|
||||
LicensePlan::Enterprise
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ use axum::{
|
||||
};
|
||||
|
||||
use hyper::StatusCode;
|
||||
use serde_json::value::RawValue;
|
||||
#[cfg(feature = "sqlx")]
|
||||
use sqlx::migrate::MigrateError;
|
||||
use thiserror::Error;
|
||||
@@ -65,8 +64,6 @@ pub enum Error {
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
#[error("Error: {0:#?}")]
|
||||
JsonErr(serde_json::Value),
|
||||
#[error("Custom Status Code: {0:#?}")]
|
||||
CustomStatusCode(StatusCode, Option<Box<RawValue>>),
|
||||
#[error("{0}")]
|
||||
OpenAIError(String),
|
||||
}
|
||||
@@ -85,41 +82,30 @@ pub fn to_anyhow<T: 'static + std::error::Error + Send + Sync>(e: T) -> anyhow::
|
||||
#[cfg(feature = "axum")]
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response<BoxBody> {
|
||||
match self {
|
||||
Self::CustomStatusCode(code, result) => {
|
||||
let mut res = Json(result).into_response();
|
||||
let status_mut = res.status_mut();
|
||||
*status_mut = code;
|
||||
res
|
||||
let e = &self;
|
||||
let body = body::boxed(body::Full::from(e.to_string()));
|
||||
|
||||
let status = match self {
|
||||
Self::NotFound(_) => axum::http::StatusCode::NOT_FOUND,
|
||||
Self::NotAuthorized(_) => axum::http::StatusCode::UNAUTHORIZED,
|
||||
Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN,
|
||||
Self::SqlErr(_) | Self::BadRequest(_) | Self::OpenAIError(_) => {
|
||||
axum::http::StatusCode::BAD_REQUEST
|
||||
}
|
||||
_ => {
|
||||
let e = &self;
|
||||
let body = body::boxed(body::Full::from(e.to_string()));
|
||||
_ => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
let status = match self {
|
||||
Self::NotFound(_) => axum::http::StatusCode::NOT_FOUND,
|
||||
Self::NotAuthorized(_) => axum::http::StatusCode::UNAUTHORIZED,
|
||||
Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN,
|
||||
Self::CustomStatusCode(code, _) => code,
|
||||
Self::SqlErr(_) | Self::BadRequest(_) | Self::OpenAIError(_) => {
|
||||
axum::http::StatusCode::BAD_REQUEST
|
||||
}
|
||||
_ => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
if matches!(status, axum::http::StatusCode::NOT_FOUND) {
|
||||
tracing::warn!(not_found = e.to_string());
|
||||
} else {
|
||||
tracing::error!(error = e.to_string());
|
||||
};
|
||||
|
||||
if matches!(status, axum::http::StatusCode::NOT_FOUND) {
|
||||
tracing::warn!(not_found = e.to_string());
|
||||
} else {
|
||||
tracing::error!(error = e.to_string());
|
||||
};
|
||||
|
||||
axum::response::Response::builder()
|
||||
.header("Content-Type", "text/plain")
|
||||
.status(status)
|
||||
.body(body)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
axum::response::Response::builder()
|
||||
.header("Content-Type", "text/plain")
|
||||
.status(status)
|
||||
.body(body)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ pub struct FlowStatus {
|
||||
pub modules: Vec<FlowStatusModule>,
|
||||
pub failure_module: FlowStatusModuleWParent,
|
||||
#[serde(default)]
|
||||
pub cleanup_module: FlowCleanupModule,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "is_retry_default")]
|
||||
pub retry: RetryStatus,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -96,6 +98,13 @@ pub struct FlowStatusModuleWParent {
|
||||
pub module_status: FlowStatusModule,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct FlowCleanupModule {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub flow_jobs_to_clean: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum FlowStatusModule {
|
||||
@@ -218,6 +227,7 @@ impl FlowStatus {
|
||||
.unwrap_or_else(|| "failure".to_string()),
|
||||
},
|
||||
},
|
||||
cleanup_module: FlowCleanupModule { flow_jobs_to_clean: vec![] },
|
||||
retry: RetryStatus { fail_count: 0, failed_jobs: vec![] },
|
||||
restarted_from: None,
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ pub struct Flow {
|
||||
pub tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -74,6 +76,8 @@ pub struct NewFlow {
|
||||
pub tag: Option<String>,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub timeout: Option<i32>,
|
||||
pub deployment_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
@@ -216,6 +220,8 @@ pub struct FlowModule {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
// Priority at the flow step level
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
}
|
||||
|
||||
impl FlowModule {
|
||||
@@ -365,6 +371,7 @@ pub fn add_virtual_items_if_necessary(modules: &mut Vec<FlowModule>) {
|
||||
cache_ttl: None,
|
||||
timeout: None,
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum JobKind {
|
||||
FlowDependencies,
|
||||
AppDependencies,
|
||||
Noop,
|
||||
DeploymentCallback,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Serialize, Clone)]
|
||||
@@ -307,6 +308,9 @@ pub enum JobPayload {
|
||||
path: Option<String>,
|
||||
restarted_from: Option<RestartedFrom>,
|
||||
},
|
||||
DeploymentCallback {
|
||||
path: String,
|
||||
},
|
||||
Identity,
|
||||
Noop,
|
||||
}
|
||||
@@ -331,9 +335,13 @@ pub async fn script_path_to_payload(
|
||||
script_path: &str,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> error::Result<(JobPayload, Option<Tag>)> {
|
||||
let (job_payload, tag) = if script_path.starts_with("hub/") {
|
||||
(JobPayload::ScriptHub { path: script_path.to_owned() }, None)
|
||||
) -> error::Result<(JobPayload, Option<Tag>, Option<bool>)> {
|
||||
let (job_payload, tag, delete_after_use) = if script_path.starts_with("hub/") {
|
||||
(
|
||||
JobPayload::ScriptHub { path: script_path.to_owned() },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let (
|
||||
script_hash,
|
||||
@@ -344,6 +352,7 @@ pub async fn script_path_to_payload(
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
delete_after_use,
|
||||
) = get_latest_deployed_hash_for_path(db, w_id, script_path).await?;
|
||||
(
|
||||
JobPayload::ScriptHash {
|
||||
@@ -357,9 +366,10 @@ pub async fn script_path_to_payload(
|
||||
priority,
|
||||
},
|
||||
tag,
|
||||
delete_after_use,
|
||||
)
|
||||
};
|
||||
Ok((job_payload, tag))
|
||||
Ok((job_payload, tag, delete_after_use))
|
||||
}
|
||||
|
||||
pub async fn script_hash_to_tag_and_limits<'c>(
|
||||
@@ -374,9 +384,10 @@ pub async fn script_hash_to_tag_and_limits<'c>(
|
||||
ScriptLang,
|
||||
Option<bool>,
|
||||
Option<i16>,
|
||||
Option<bool>,
|
||||
)> {
|
||||
let script = sqlx::query!(
|
||||
"select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority from script where hash = $1 AND workspace_id = $2",
|
||||
"select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use from script where hash = $1 AND workspace_id = $2",
|
||||
script_hash.0,
|
||||
w_id
|
||||
)
|
||||
@@ -395,6 +406,7 @@ pub async fn script_hash_to_tag_and_limits<'c>(
|
||||
script.language,
|
||||
script.dedicated_worker,
|
||||
script.priority,
|
||||
script.delete_after_use,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -403,7 +415,7 @@ pub async fn get_payload_tag_from_prefixed_path(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> Result<(JobPayload, Option<String>), Error> {
|
||||
let (payload, tag) = if path.starts_with("script/") {
|
||||
let (payload, tag, _) = if path.starts_with("script/") {
|
||||
script_path_to_payload(path.strip_prefix("script/").unwrap(), &db, w_id).await?
|
||||
} else if path.starts_with("flow/") {
|
||||
let path = path.strip_prefix("flow/").unwrap().to_string();
|
||||
@@ -417,7 +429,7 @@ pub async fn get_payload_tag_from_prefixed_path(
|
||||
let (tag, dedicated_worker) = r
|
||||
.map(|x| (x.tag, x.dedicated_worker))
|
||||
.unwrap_or_else(|| (None, None));
|
||||
(JobPayload::Flow { path, dedicated_worker }, tag)
|
||||
(JobPayload::Flow { path, dedicated_worker }, tag, None)
|
||||
} else {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"path must start with script/ or flow/ (got {})",
|
||||
|
||||
@@ -228,9 +228,10 @@ pub async fn get_latest_deployed_hash_for_path(
|
||||
ScriptLang,
|
||||
Option<bool>,
|
||||
Option<i16>,
|
||||
Option<bool>,
|
||||
)> {
|
||||
let r_o = sqlx::query!(
|
||||
"select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority from script where path = $1 AND workspace_id = $2 AND
|
||||
"select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use from script where path = $1 AND workspace_id = $2 AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND
|
||||
deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
|
||||
script_path,
|
||||
@@ -250,6 +251,7 @@ pub async fn get_latest_deployed_hash_for_path(
|
||||
script.language,
|
||||
script.dedicated_worker,
|
||||
script.priority,
|
||||
script.delete_after_use,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,12 @@ pub struct Script {
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cache_ttl: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -198,6 +204,18 @@ pub struct ListableScript {
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ScriptHistory {
|
||||
pub script_hash: ScriptHash,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ScriptHistoryUpdate {
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx)]
|
||||
@@ -234,6 +252,10 @@ pub struct NewScript {
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
pub priority: Option<i16>,
|
||||
pub timeout: Option<i32>,
|
||||
pub delete_after_use: Option<bool>,
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
pub deployment_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::{error, DB};
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
pub struct Smtp {
|
||||
pub host: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub port: u16,
|
||||
pub from: String,
|
||||
pub tls_implicit: bool,
|
||||
@@ -33,7 +33,7 @@ pub async fn load_server_config(db: &DB) -> error::Result<ServerConfig> {
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let config_smtp = if let (Some(host), Some(username), Some(password)) =
|
||||
let config_smtp = if let (Some(host), username, password) =
|
||||
(config.smtp_host, config.smtp_username, config.smtp_password)
|
||||
{
|
||||
Some(Smtp {
|
||||
@@ -50,7 +50,7 @@ pub async fn load_server_config(db: &DB) -> error::Result<ServerConfig> {
|
||||
None
|
||||
};
|
||||
let smtp = config_smtp.or(
|
||||
if let (Some(host), Some(username), Some(password)) = (
|
||||
if let (Some(host), username, password) = (
|
||||
std::env::var("SMTP_HOST").ok(),
|
||||
std::env::var("SMTP_USERNAME").ok(),
|
||||
std::env::var("SMTP_PASSWORD").ok(),
|
||||
|
||||
@@ -89,7 +89,7 @@ pub async fn reload_custom_tags_setting(db: &DB) -> error::Result<()> {
|
||||
let custom_tags = process_custom_tags(tags);
|
||||
|
||||
tracing::info!(
|
||||
"Loaded setting custom tags, common: {:?}, per-workspace: {:?}",
|
||||
"Loaded setting custom_tags, common: {:?}, per-workspace: {:?}",
|
||||
custom_tags.0,
|
||||
custom_tags.1,
|
||||
);
|
||||
|
||||
@@ -45,8 +45,8 @@ use windmill_common::{
|
||||
db::{Authed, UserDB},
|
||||
error::{self, Error},
|
||||
flow_status::{
|
||||
BranchAllStatus, FlowStatus, FlowStatusModule, FlowStatusModuleWParent, Iterator,
|
||||
JobResult, RestartedFrom, RetryStatus, MAX_RETRY_ATTEMPTS, MAX_RETRY_INTERVAL,
|
||||
BranchAllStatus, FlowCleanupModule, FlowStatus, FlowStatusModule, FlowStatusModuleWParent,
|
||||
Iterator, JobResult, RestartedFrom, RetryStatus, MAX_RETRY_ATTEMPTS, MAX_RETRY_INTERVAL,
|
||||
},
|
||||
flows::{add_virtual_items_if_necessary, FlowModuleValue, FlowValue},
|
||||
jobs::{
|
||||
@@ -559,6 +559,63 @@ pub async fn add_completed_job<
|
||||
}
|
||||
}
|
||||
|
||||
if !queued_job.is_flow_step && queued_job.job_kind == JobKind::Script && canceled_by.is_none() {
|
||||
if let Some(hash) = queued_job.script_hash {
|
||||
let p = sqlx::query_scalar!(
|
||||
"SELECT restart_unless_cancelled FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
hash.0,
|
||||
&queued_job.workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(false);
|
||||
|
||||
if p {
|
||||
let tx = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq);
|
||||
|
||||
let (_uuid, tx) = push(
|
||||
db,
|
||||
tx,
|
||||
&queued_job.workspace_id,
|
||||
JobPayload::ScriptHash {
|
||||
hash,
|
||||
path: queued_job.script_path().to_string(),
|
||||
concurrent_limit: queued_job.concurrent_limit,
|
||||
concurrency_time_window_s: queued_job.concurrency_time_window_s,
|
||||
cache_ttl: queued_job.cache_ttl,
|
||||
dedicated_worker: None,
|
||||
language: queued_job
|
||||
.language
|
||||
.clone()
|
||||
.unwrap_or_else(|| ScriptLang::Deno),
|
||||
priority: queued_job.priority,
|
||||
},
|
||||
queued_job.args.clone(),
|
||||
&queued_job.created_by,
|
||||
&queued_job.email,
|
||||
queued_job.permissioned_as.clone(),
|
||||
None,
|
||||
queued_job.schedule_path.clone(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
queued_job.visible_to_owner,
|
||||
Some(queued_job.tag.clone()),
|
||||
queued_job.timeout,
|
||||
None,
|
||||
queued_job.priority,
|
||||
)
|
||||
.await?;
|
||||
if let Err(e) = tx.commit().await {
|
||||
tracing::error!("Could not restart job {}: {}", queued_job.id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// tracing::error!("4 {:?}", start.elapsed());
|
||||
|
||||
Ok(queued_job.id)
|
||||
@@ -2267,7 +2324,7 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
priority,
|
||||
),
|
||||
JobPayload::ScriptHub { path } => {
|
||||
if path == "hub/7771/slack" {
|
||||
if path == "hub/7771/slack" || path == "hub/7836/slack" {
|
||||
permissioned_as = SUPERADMIN_NOTIFICATION_EMAIL.to_string();
|
||||
email = SUPERADMIN_NOTIFICATION_EMAIL;
|
||||
}
|
||||
@@ -2392,6 +2449,7 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
id: "failure".to_string(),
|
||||
},
|
||||
},
|
||||
cleanup_module: FlowCleanupModule { flow_jobs_to_clean: vec![] },
|
||||
// retry status is reset
|
||||
retry: RetryStatus { fail_count: 0, failed_jobs: vec![] },
|
||||
// TODO: for now, flows with approval conditions aren't supported for restart
|
||||
@@ -2477,6 +2535,7 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
id: "failure".to_string(),
|
||||
},
|
||||
},
|
||||
cleanup_module: FlowCleanupModule { flow_jobs_to_clean: vec![] },
|
||||
// retry status is reset
|
||||
retry: RetryStatus { fail_count: 0, failed_jobs: vec![] },
|
||||
// TODO: for now, flows with approval conditions aren't supported for restart
|
||||
@@ -2502,6 +2561,20 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
priority,
|
||||
)
|
||||
}
|
||||
JobPayload::DeploymentCallback { path } => (
|
||||
None,
|
||||
Some(path),
|
||||
None,
|
||||
JobKind::DeploymentCallback,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
JobPayload::Identity => (
|
||||
None,
|
||||
None,
|
||||
@@ -2600,7 +2673,11 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
|| job_kind == JobKind::Identity
|
||||
{
|
||||
"flow".to_string()
|
||||
} else if job_kind == JobKind::Dependencies || job_kind == JobKind::FlowDependencies {
|
||||
} else if job_kind == JobKind::Dependencies
|
||||
|| job_kind == JobKind::FlowDependencies
|
||||
|| job_kind == JobKind::DeploymentCallback
|
||||
{
|
||||
// using the dependency tag for deployment callback for now. We can create a separate tag when we need
|
||||
"dependency".to_string()
|
||||
} else {
|
||||
"deno".to_string()
|
||||
@@ -2712,6 +2789,7 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
JobKind::Noop => "jobs.run.noop",
|
||||
JobKind::FlowDependencies => "jobs.run.flow_dependencies",
|
||||
JobKind::AppDependencies => "jobs.run.app_dependencies",
|
||||
JobKind::DeploymentCallback => "jobs.run.deployment_callback",
|
||||
};
|
||||
|
||||
audit_log(
|
||||
|
||||
@@ -279,15 +279,20 @@ run().catch(async (e) => {{
|
||||
args.push("--lock-write");
|
||||
}
|
||||
}
|
||||
let allow_read = format!(
|
||||
"--allow-read=./,/tmp/windmill/cache/deno/,{}",
|
||||
DENO_PATH.as_str()
|
||||
);
|
||||
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
|
||||
for flag in deno_flags {
|
||||
args.push(flag);
|
||||
}
|
||||
} else if !*DISABLE_NSJAIL {
|
||||
args.push("--allow-net");
|
||||
args.push("--allow-read=./,/tmp/windmill/cache/deno/");
|
||||
args.push(allow_read.as_str());
|
||||
args.push("--allow-write=./");
|
||||
args.push("--allow-env");
|
||||
args.push("--allow-run=git,/usr/bin/chromium");
|
||||
} else {
|
||||
args.push("-A");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use base64::Engine;
|
||||
use mysql_async::{
|
||||
consts::ColumnType, prelude::*, FromValueError, OptsBuilder, Params, Row, SslOpts,
|
||||
@@ -9,7 +11,7 @@ use windmill_common::{
|
||||
error::{to_anyhow, Error},
|
||||
jobs::QueuedJob,
|
||||
};
|
||||
use windmill_parser_sql::parse_mysql_sig;
|
||||
use windmill_parser_sql::{parse_mysql_sig, RE_ARG_MYSQL_NAMED};
|
||||
|
||||
use crate::{common::build_args_map, AuthedClientBackgroundTask};
|
||||
|
||||
@@ -63,14 +65,19 @@ pub async fn do_mysql(
|
||||
let pool = mysql_async::Pool::new(opts);
|
||||
let mut conn = pool.get_conn().await.map_err(to_anyhow)?;
|
||||
|
||||
let mut statement_values: Vec<mysql_async::Value> = vec![];
|
||||
|
||||
let sig = parse_mysql_sig(&query)
|
||||
.map_err(|x| Error::ExecutionErr(x.to_string()))?
|
||||
.args;
|
||||
|
||||
let using_named_params = RE_ARG_MYSQL_NAMED.captures_iter(&query).count() > 0;
|
||||
|
||||
let mut statement_values: Params = match using_named_params {
|
||||
true => Params::Named(HashMap::new()),
|
||||
false => Params::Positional(vec![]),
|
||||
};
|
||||
for arg in &sig {
|
||||
let arg_t = arg.otyp.clone().unwrap_or_else(|| "text".to_string());
|
||||
let arg_n = arg.clone().name;
|
||||
let mysql_v = match job
|
||||
.args
|
||||
.as_ref()
|
||||
@@ -83,6 +90,14 @@ pub async fn do_mysql(
|
||||
{
|
||||
Value::Null => mysql_async::Value::NULL,
|
||||
Value::Bool(b) => mysql_async::Value::Int(if b { 1 } else { 0 }),
|
||||
Value::String(s)
|
||||
if arg_t == "timestamp"
|
||||
|| arg_t == "datetime"
|
||||
|| arg_t == "date"
|
||||
|| arg_t == "time" =>
|
||||
{
|
||||
string_date_to_mysql_date(&s)
|
||||
}
|
||||
Value::String(s) => mysql_async::Value::Bytes(s.as_bytes().to_vec()),
|
||||
Value::Number(n)
|
||||
if n.is_i64() && (arg_t == "int" || arg_t == "integer" || arg_t == "smallint") =>
|
||||
@@ -103,10 +118,23 @@ pub async fn do_mysql(
|
||||
)))
|
||||
}
|
||||
};
|
||||
statement_values.push(mysql_v);
|
||||
match &mut statement_values {
|
||||
Params::Positional(v) => v.push(mysql_v),
|
||||
Params::Named(m) => {
|
||||
m.insert(arg_n.into_bytes(), mysql_v);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let rows: Vec<Row> = conn
|
||||
.exec(query, Params::Positional(statement_values))
|
||||
.exec(
|
||||
query,
|
||||
match statement_values {
|
||||
Params::Positional(v) => Params::Positional(v),
|
||||
Params::Named(m) => Params::Named(m),
|
||||
_ => Params::Empty,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
let rows = rows
|
||||
@@ -122,6 +150,26 @@ pub async fn do_mysql(
|
||||
return Ok(windmill_common::worker::to_raw_value(&json!(rows)));
|
||||
}
|
||||
|
||||
fn string_date_to_mysql_date(s: &str) -> mysql_async::Value {
|
||||
// 2023-12-01T16:18:00.000Z
|
||||
let re = regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d+)Z").unwrap();
|
||||
let caps = re.captures(s);
|
||||
|
||||
if let Some(caps) = caps {
|
||||
mysql_async::Value::Date(
|
||||
caps.get(1).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(2).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(3).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(4).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(5).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(6).unwrap().as_str().parse().unwrap_or_default(),
|
||||
caps.get(7).unwrap().as_str().parse().unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
mysql_async::Value::Date(0, 0, 0, 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_row_to_value(row: Row) -> serde_json::Value {
|
||||
let mut map = serde_json::Map::new();
|
||||
|
||||
@@ -150,8 +198,10 @@ fn convert_mysql_value_to_json(v: mysql_async::Value, c: ColumnType) -> serde_js
|
||||
mysql_async::Value::UInt(n) => json!(n),
|
||||
mysql_async::Value::Float(n) => json!(n),
|
||||
mysql_async::Value::Double(n) => json!(n),
|
||||
d @ mysql_async::Value::Date(_, _, _, _, _, _, _) => json!(d.as_sql(true)),
|
||||
t @ mysql_async::Value::Time(_, _, _, _, _, _) => json!(t.as_sql(true)),
|
||||
d @ mysql_async::Value::Date(_, _, _, _, _, _, _) => {
|
||||
json!(d.as_sql(true).trim_matches('\''))
|
||||
}
|
||||
t @ mysql_async::Value::Time(_, _, _, _, _, _) => json!(t.as_sql(true).trim_matches('\'')),
|
||||
_ => match c {
|
||||
ColumnType::MYSQL_TYPE_FLOAT | ColumnType::MYSQL_TYPE_DOUBLE => {
|
||||
conversion_error(f64::from_value_opt(v))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user