mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-20 16:02:28 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f46459844 | ||
|
|
f82dfa7e97 | ||
|
|
a605d79f21 |
@@ -16,7 +16,6 @@ sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/
|
||||
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i '' -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i '' -e "/^ModuleVersion =/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
# sed -i '' -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
|
||||
sed -i '' -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
sed -i '' -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
|
||||
@@ -12,12 +12,10 @@ sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/be
|
||||
sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/backend/windmill-api/openapi.yaml
|
||||
sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/openflow.openapi.yaml
|
||||
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescript-client/package.json
|
||||
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescript-client/jsr.json
|
||||
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/package.json
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^ModuleVersion =/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
name: Publish powershell-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish_gallery:
|
||||
runs-on: ubicloud-standard-8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: . ./powershell-client/publish.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
|
||||
@@ -1,16 +0,0 @@
|
||||
name: Publish typescript-client on JSR
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: ${{ secrets.JSR_OIDC_ID_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: cd typescript-client && ./publish.jsr.sh
|
||||
+380
-652
File diff suppressed because it is too large
Load Diff
+6
-14
@@ -12,14 +12,12 @@ RUN apt-get -y update \
|
||||
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version ^0.1
|
||||
RUN cargo install sccache --version ^0.8
|
||||
ENV RUSTC_WRAPPER=sccache SCCACHE_DIR=/backend/sccache
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef
|
||||
|
||||
WORKDIR /windmill
|
||||
|
||||
ENV SQLX_OFFLINE=true
|
||||
# ENV CARGO_INCREMENTAL=1
|
||||
ENV CARGO_INCREMENTAL=1
|
||||
|
||||
FROM node:20-alpine as frontend
|
||||
|
||||
@@ -48,9 +46,7 @@ FROM rust_base AS planner
|
||||
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY ./backend ./
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef prepare --recipe-path recipe.json
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM rust_base AS builder
|
||||
ARG features=""
|
||||
@@ -59,9 +55,7 @@ COPY --from=planner /windmill/recipe.json recipe.json
|
||||
|
||||
RUN apt-get update && apt-get install -y libxml2-dev libxmlsec1-dev clang libclang-dev cmake
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json
|
||||
|
||||
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY ./backend ./
|
||||
@@ -70,9 +64,7 @@ COPY --from=frontend /frontend /frontend
|
||||
COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml
|
||||
COPY .git/ .git/
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
|
||||
|
||||
|
||||
FROM ${DEBIAN_IMAGE} as downloader
|
||||
@@ -171,7 +163,7 @@ COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
|
||||
|
||||
COPY --from=downloader --chmod=755 /deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.1.7 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.1.0 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
@@ -13,7 +13,7 @@ any snippets of code that require a positive license check to be activated.
|
||||
Those snippets and files are under a proprietary and commercial license. Private
|
||||
and public forks MUST not include any of the above proprietary and commercial
|
||||
code. Windmill Labs, Inc. provide tools to clean the codebase from those
|
||||
snippets upon demand. The files under python-client/ deno-client/ go-client/ powershell-client/ are
|
||||
snippets upon demand. The files under python-client/ deno-client/ go-client/ are
|
||||
Apache 2.0 Licensed.
|
||||
|
||||
The openapi files, including the OpenFlow spec is Apache 2.0 Licensed.
|
||||
|
||||
@@ -352,44 +352,6 @@ you to have it being synced automatically everyday.
|
||||
| TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker |
|
||||
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
|
||||
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
|
||||
| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server |
|
||||
|
||||
## Run a local dev setup
|
||||
### only Frontend
|
||||
This will use the backend of <https://app.windmill.dev> but your own frontend
|
||||
with hot-code reloading.
|
||||
1. Install [caddy](https://caddyserver.com)
|
||||
2. Go to `frontend/`:
|
||||
1. `npm install`, `npm run generate-backend-client` then `npm run dev`
|
||||
2. In another shell `sudo caddy run --config CaddyfileRemote`
|
||||
3. Et voilà, windmill should be available at `http://localhost/`
|
||||
### Backend + Frontend
|
||||
See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all
|
||||
running options.
|
||||
1. Create a Postgres Database for Windmill and create an admin role inside your
|
||||
Postgres setup.
|
||||
The easiest way to get a working db is to run
|
||||
```
|
||||
cargo install sqlx-cli
|
||||
env DATABASE_URL=<YOUR_DATABASE_URL> sqlx migrate run
|
||||
```
|
||||
This will also avoid compile time issue with sqlx's `query!` macro
|
||||
2. Install [nsjail](https://github.com/google/nsjail) and have it accessible in
|
||||
your PATH
|
||||
3. Install deno and python3, have the bins at `/usr/bin/deno` and
|
||||
`/usr/local/bin/python3`
|
||||
4. Install [caddy](https://caddyserver.com)
|
||||
5. Install the [lld linker](https://lld.llvm.org/)
|
||||
6. Go to `frontend/`:
|
||||
1. `npm install`, `npm run generate-backend-client` then `npm run dev`
|
||||
2. You might need to set some extra heap space for the node runtime `export NODE_OPTIONS="--max-old-space-size=4096"`
|
||||
3. In another shell `npm run build` otherwise the backend will not find the `frontend/build` folder and will not compile.
|
||||
4. In another shell `sudo caddy run --config Caddyfile`
|
||||
7. Go to `backend/`:
|
||||
`env DATABASE_URL=<DATABASE_URL_TO_YOUR_WINDMILL_DB> RUST_LOG=info cargo run`
|
||||
8. Et voilà, windmill should be available at `http://localhost/`
|
||||
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)",
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -66,12 +66,10 @@
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8e7ff45c5378c3a3406ba94dc0653afa5d28c072203617c598caefaaf1bafcfb"
|
||||
"hash": "020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, running, is_flow_step FROM queue WHERE scheduled_for < now() AND workspace_id = $1 AND schedule_path IS NULL",
|
||||
"query": "UPDATE queue SET canceled = true, canceled_by = $2, scheduled_for = now(), suspend = 0 WHERE scheduled_for < now() AND workspace_id = $1 AND schedule_path IS NULL RETURNING id, running, is_flow_step",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -21,7 +21,8 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@@ -30,5 +31,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "caeb49629b8673c1f1c84a6e40c3e2d2c3bc3fdbde530a0a6b6fd68a22b867c3"
|
||||
"hash": "18699cb0eca25b6bde05d81571dfdea8cafd0043634f61b0f652a93767c9c30a"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2",
|
||||
"query": "DELETE FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,5 +11,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0d7ba88a9810e434aa00fd63bbf416cbe222f2c67ccc8aa92e651c2bea4c2d7b"
|
||||
"hash": "26e4ec75366d1e46a98710f29066b40e66a802f98eeabbb3ae5bebe3aeb6b3f8"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1 FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "4613382f7b031a2b667f86d9af995065a99a63c407fd58aa293fb269e032121d"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "61e9662fe42506131222412ab3de48cf6485dea10aa3a2f97c0fd6322a0cb17f"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DROP INDEX CONCURRENTLY IF EXISTS labeled_jobs_on_jobs",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6f12be65a4fe3eb39292164363f557de9cef7017dcfbcd40370b849a288c52e3"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7142222bee1f60bc56752e377b96ac6e4e981ea219c173146088faf1ad9f3822"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM folder WHERE name = $1 AND workspace_id = $2 RETURNING 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "748904c35cdbb6c7b8a8e0024b341278bf2bb727f2fe0427847565fb9c774abc"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS FALSE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS FALSE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -18,5 +18,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "94ff696b4d3904e3823ef637fa8f1f0d0bdac01040c81b31514326417eb58cee"
|
||||
"hash": "7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "CREATE INDEX CONCURRENTLY labeled_jobs_on_jobs ON completed_job USING GIN ((result -> 'wm_labels')) WHERE result ? 'wm_label';",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "89e72f997e27b9298bd923e7f2b546c5ff061bd9ed63798024306456ca148aec"
|
||||
}
|
||||
+3
-9
@@ -1,16 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false\n GROUP BY script_path), $3) as min_started_at, now() AS now",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false\n GROUP BY script_path), $3)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "min_started_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "now",
|
||||
"name": "coalesce",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
@@ -22,9 +17,8 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "3901cce744c9b246b661c817e068bdb3b1ab504ff8070fcccf6c909ad75f1f6d"
|
||||
"hash": "abc7c72dfe9b01cde6f5b206300ed33e3d15b16bce2510160166a66fb7598e61"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM queue WHERE schedule_path = $1 AND running = false AND workspace_id = $2 AND is_flow_step = false",
|
||||
"query": "DELETE FROM queue WHERE schedule_path = $1 AND running = false AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,5 +11,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8d4235984f27d8b939ffd5c660d5b57dc38dd3b2643361ed3b7cdcd1534d2e21"
|
||||
"hash": "ade89de6e8527c543b182229f1febeb2513ad58b03ab526df148582264fb3a44"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT args FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT args FROM input WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "args",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bbd5f968d7b62a55a7ebf7b98cfd411678ce544fdb9472d8d223235fb2818aaf"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM schedule WHERE path = $1 AND workspace_id = $2 RETURNING 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d1ded8b38e50eb01fa5e5e122dae48ec21856a0041f4aeb244349ab7648d306f"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET canceled = true, canceled_by = $1, scheduled_for = now(), suspend = 0 WHERE id = $2 RETURNING 1 as one",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "one",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d4fb94ee8198592c24e85d29078feb5220ab367e339510ee0e83bb7b5abfd184"
|
||||
}
|
||||
Generated
+518
-458
File diff suppressed because it is too large
Load Diff
+4
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.324.0"
|
||||
version = "1.306.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -24,7 +24,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.324.0"
|
||||
version = "1.306.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -50,7 +50,6 @@ parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/p
|
||||
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus"]
|
||||
flow_testing = ["windmill-worker/flow_testing"]
|
||||
openidconnect = ["windmill-api/openidconnect"]
|
||||
cloud = ["windmill-queue/cloud"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -109,7 +108,7 @@ windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
|
||||
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
|
||||
axum = { version = "^0.7", features = ["multipart"] }
|
||||
axum = { version = "^0.7" }
|
||||
headers = "^0"
|
||||
hyper = { version = "^1", features = ["full"] }
|
||||
tokio = { version = "^1", features = ["full", "tracing"] }
|
||||
@@ -225,7 +224,7 @@ tokenizers = "0.14.1"
|
||||
candle-core = "0.3.0"
|
||||
candle-transformers = "0.3.0"
|
||||
candle-nn = "0.3.0"
|
||||
tiberius = { git = "https://github.com/prisma/tiberius", rev = "8f66a699dfa041e7b5f736c7e94f92c945453c9e", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
|
||||
tiberius = { version = "0.12.2", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"] }
|
||||
pin-project = "1"
|
||||
indexmap = { version = "2.2.5", features = ["serde"]}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
644b6f49f087790a728a7a0a82525997b1742738
|
||||
66d9cbb158ab9a5869a45ba253bf57f2cbb5ecb6
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,228 +0,0 @@
|
||||
|
||||
DO
|
||||
$do$
|
||||
DECLARE
|
||||
i text;
|
||||
arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow', 'app', 'raw_app'];
|
||||
BEGIN
|
||||
FOREACH i IN ARRAY arr
|
||||
LOOP
|
||||
EXECUTE FORMAT(
|
||||
$$
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user ON %1$I;
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user_delete ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user ON %1$I;
|
||||
DROP POLICY IF EXISTS see_member ON %1$I;
|
||||
DROP POLICY IF EXISTS see_own ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_delete ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_delete ON %1$I;
|
||||
|
||||
-- New policies for select, insert, update
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user_select ON %1$I;
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user_insert ON %1$I;
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user_update ON %1$I;
|
||||
|
||||
DROP POLICY IF EXISTS see_own ON %1$I;
|
||||
DROP POLICY IF EXISTS see_member ON %1$I;
|
||||
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_select ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_insert ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_update ON %1$I;
|
||||
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_select ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_insert ON %1$I;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_update ON %1$I;
|
||||
|
||||
|
||||
-- Folder permissions split into select, insert, and update
|
||||
CREATE POLICY see_folder_extra_perms_user_select ON %1$I FOR SELECT TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_insert ON %1$I FOR INSERT TO windmill_user
|
||||
WITH CHECK (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_update ON %1$I FOR UPDATE TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_delete ON %1$I FOR UPDATE TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_own ON %1$I FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'u' AND SPLIT_PART(%1$I.path, '/', 2) = current_setting('session.user'));
|
||||
|
||||
|
||||
CREATE POLICY see_member ON %1$I FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'g' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
|
||||
|
||||
|
||||
|
||||
CREATE POLICY see_extra_perms_user_select ON %1$I FOR SELECT TO windmill_user
|
||||
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_insert ON %1$I FOR INSERT TO windmill_user
|
||||
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
CREATE POLICY see_extra_perms_user_update ON %1$I FOR UPDATE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
CREATE POLICY see_extra_perms_user_delete ON %1$I FOR DELETE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
|
||||
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_select ON %1$I FOR SELECT TO windmill_user
|
||||
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_insert ON %1$I FOR INSERT TO windmill_user
|
||||
WITH CHECK (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_update ON %1$I FOR UPDATE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_delete ON %1$I FOR DELETE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
|
||||
$$,
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END
|
||||
$do$;
|
||||
|
||||
DROP POLICY IF EXISTS see_extra_perms_user ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_select ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_insert ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_update ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_user_delete ON folder;
|
||||
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_select ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_insert ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_update ON folder;
|
||||
DROP POLICY IF EXISTS see_extra_perms_groups_delete ON folder;
|
||||
|
||||
|
||||
-- Existing CREATE POLICY statements updated to reflect policy splitting for 'folder' table
|
||||
CREATE POLICY see_extra_perms_user_select ON folder FOR SELECT TO windmill_user
|
||||
USING (extra_perms ? CONCAT('u/', current_setting('session.user')) OR CONCAT('u/', current_setting('session.user')) = ANY(owners));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_insert ON folder FOR INSERT TO windmill_user
|
||||
WITH CHECK ((CONCAT('u/', current_setting('session.user')) = ANY(owners)));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_update ON folder FOR UPDATE TO windmill_user
|
||||
USING ((CONCAT('u/', current_setting('session.user')) = ANY(owners)));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_delete ON folder FOR DELETE TO windmill_user
|
||||
USING ((CONCAT('u/', current_setting('session.user')) = ANY(owners)));
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_select ON folder FOR SELECT TO windmill_user
|
||||
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[] OR EXISTS (
|
||||
SELECT o FROM unnest(owners) AS o
|
||||
WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_insert ON folder FOR INSERT TO windmill_user
|
||||
WITH CHECK (EXISTS (
|
||||
SELECT o FROM unnest(owners) AS o
|
||||
WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_update ON folder FOR UPDATE TO windmill_user
|
||||
USING (EXISTS (
|
||||
SELECT o FROM unnest(owners) AS o
|
||||
WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_delete ON folder FOR DELETE TO windmill_user
|
||||
USING (EXISTS (
|
||||
SELECT o FROM unnest(owners) AS o
|
||||
WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
|
||||
|
||||
-- DO
|
||||
-- $do$
|
||||
-- DECLARE
|
||||
-- i text;
|
||||
-- arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow', 'app', 'raw_app'];
|
||||
-- BEGIN
|
||||
-- FOREACH i IN ARRAY arr
|
||||
-- LOOP
|
||||
-- EXECUTE FORMAT(
|
||||
-- $$
|
||||
|
||||
|
||||
-- CREATE POLICY see_folder_extra_perms_user ON %1$I FOR ALL TO windmill_user
|
||||
-- USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]))
|
||||
-- WITH CHECK (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
-- CREATE POLICY see_folder_extra_perms_user_delete ON %1$I AS RESTRICTIVE FOR DELETE TO windmill_user
|
||||
-- USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
-- CREATE POLICY see_own ON %1$I FOR ALL TO windmill_user
|
||||
-- USING (SPLIT_PART(%1$I.path, '/', 1) = 'u' AND SPLIT_PART(%1$I.path, '/', 2) = current_setting('session.user'));
|
||||
|
||||
-- CREATE POLICY see_member ON %1$I FOR ALL TO windmill_user
|
||||
-- USING (SPLIT_PART(%1$I.path, '/', 1) = 'g' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
|
||||
|
||||
|
||||
-- CREATE POLICY see_extra_perms_user ON %1$I FOR ALL TO windmill_user
|
||||
-- USING (extra_perms ? CONCAT('u/', current_setting('session.user')))
|
||||
-- WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
-- CREATE POLICY see_extra_perms_user_delete ON %1$I FOR DELETE TO windmill_user
|
||||
-- USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
-- CREATE POLICY see_extra_perms_groups ON %1$I FOR ALL TO windmill_user
|
||||
-- USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
-- WITH CHECK (exists(
|
||||
-- SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
-- WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
-- AND value::boolean));
|
||||
|
||||
-- CREATE POLICY see_extra_perms_groups_delete ON %1$I FOR DELETE TO windmill_user
|
||||
-- USING (exists(
|
||||
-- SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
-- WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
-- AND value::boolean));
|
||||
-- $$,
|
||||
-- i
|
||||
-- );
|
||||
-- END LOOP;
|
||||
-- END
|
||||
-- $do$;
|
||||
|
||||
|
||||
-- DROP POLICY see_extra_perms_user ON folder;
|
||||
-- DROP POLICY see_extra_perms_groups ON folder;
|
||||
|
||||
-- CREATE POLICY see_extra_perms_user ON folder FOR ALL to windmill_user
|
||||
-- USING (extra_perms ? CONCAT('u/', current_setting('session.user')) or (CONCAT('u/', current_setting('session.user')) = ANY(owners)))
|
||||
-- WITH CHECK ((CONCAT('u/', current_setting('session.user')) = ANY(owners)));
|
||||
|
||||
-- DROP POLICY IF EXISTS see_extra_perms_user_delete ON folder;
|
||||
-- CREATE POLICY see_extra_perms_user_delete ON folder AS RESTRICTIVE FOR DELETE to windmill_user
|
||||
-- USING ((CONCAT('u/', current_setting('session.user')) = ANY(owners)));
|
||||
|
||||
-- CREATE POLICY see_extra_perms_groups ON folder FOR ALL to windmill_user
|
||||
-- USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[] or (exists(
|
||||
-- SELECT o FROM unnest(owners) as o
|
||||
-- WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]))))
|
||||
-- WITH CHECK (exists(
|
||||
-- SELECT o FROM unnest(owners) as o
|
||||
-- WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
|
||||
-- DROP POLICY IF EXISTS see_extra_perms_groups_delete ON folder;
|
||||
-- CREATE POLICY see_extra_perms_groups_delete ON folder AS RESTRICTIVE FOR DELETE to windmill_user
|
||||
-- USING (exists(
|
||||
-- SELECT o FROM unnest(owners) as o
|
||||
-- WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])));
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
DO
|
||||
$do$
|
||||
DECLARE
|
||||
i text;
|
||||
arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow', 'app', 'raw_app'];
|
||||
BEGIN
|
||||
FOREACH i IN ARRAY arr
|
||||
LOOP
|
||||
EXECUTE FORMAT(
|
||||
$$
|
||||
DROP POLICY IF EXISTS see_folder_extra_perms_user_delete ON %1$I;
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_delete ON %1$I FOR DELETE TO windmill_user
|
||||
USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
$$,
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END
|
||||
$do$;-- Add up migration script here
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE script ADD COLUMN no_main_func BOOLEAN;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Add down migration script here
|
||||
DROP INDEX IF EXISTS labeled_jobs_on_completed_jobs;
|
||||
@@ -1 +0,0 @@
|
||||
-- Add up migration script here
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1 +0,0 @@
|
||||
-- Add up migration script here
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE script DROP COLUMN codebase;
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Add up migration script here
|
||||
|
||||
ALTER TABLE script ADD COLUMN codebase VARCHAR(255);
|
||||
@@ -141,16 +141,5 @@
|
||||
"scopes": [
|
||||
"com.intuit.quickbooks.accounting"
|
||||
]
|
||||
},
|
||||
"visma": {
|
||||
"auth_url": "https://connect.visma.com/connect/authorize",
|
||||
"token_url": "https://connect.visma.com/connect/token",
|
||||
"scopes": [
|
||||
"offline_access",
|
||||
"vismanet_erp_interactive_api:create",
|
||||
"vismanet_erp_interactive_api:delete",
|
||||
"vismanet_erp_interactive_api:read",
|
||||
"vismanet_erp_interactive_api:update"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,6 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
|
||||
"mysql" => "mysql-connector-python",
|
||||
"tenable" => "pytenable",
|
||||
"ns1" => "ns1-python",
|
||||
"pymsql" => "PyMySQL",
|
||||
"haystack" => "haystack-ai",
|
||||
"github" => "PyGithub",
|
||||
};
|
||||
|
||||
fn replace_import(x: String) -> String {
|
||||
@@ -64,7 +61,7 @@ fn replace_import(x: String) -> String {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap();
|
||||
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)$").unwrap();
|
||||
}
|
||||
|
||||
fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<String> {
|
||||
|
||||
@@ -292,30 +292,22 @@ pub fn parse_pg_typ(typ: &str) -> Typ {
|
||||
Typ::List(Box::new(base_typ))
|
||||
} else {
|
||||
match typ {
|
||||
"varchar" | "character varying" => Typ::Str(None),
|
||||
"varchar" => Typ::Str(None),
|
||||
"text" => Typ::Str(None),
|
||||
"int" | "integer" | "int4" => Typ::Int,
|
||||
"int" => Typ::Int,
|
||||
"bigint" => Typ::Int,
|
||||
"bool" | "boolean" => Typ::Bool,
|
||||
"char" | "character" => Typ::Str(None),
|
||||
"json" | "jsonb" => Typ::Object(vec![]),
|
||||
"smallint" | "int2" => Typ::Int,
|
||||
"smallserial" | "serial2" => Typ::Int,
|
||||
"serial" | "serial4" => Typ::Int,
|
||||
"bigserial" | "serial8" => Typ::Int,
|
||||
"real" | "float4" => Typ::Float,
|
||||
"double" | "double precision" | "float8" => Typ::Float,
|
||||
"numeric" | "decimal" => Typ::Float,
|
||||
"bool" => Typ::Bool,
|
||||
"char" => Typ::Str(None),
|
||||
"smallint" => Typ::Int,
|
||||
"smallserial" => Typ::Int,
|
||||
"serial" => Typ::Int,
|
||||
"bigserial" => Typ::Int,
|
||||
"real" => Typ::Float,
|
||||
"double precision" => Typ::Float,
|
||||
"numeric" => Typ::Float,
|
||||
"decimal" => Typ::Float,
|
||||
"oid" => Typ::Int,
|
||||
"date"
|
||||
| "time"
|
||||
| "timetz"
|
||||
| "time with time zone"
|
||||
| "time without time zone"
|
||||
| "timestamp"
|
||||
| "timestamptz"
|
||||
| "timestamp with time zone"
|
||||
| "timestamp without time zone" => Typ::Datetime,
|
||||
"date" | "time" | "timestamp" | "timestamptz" => Typ::Datetime,
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"collaborators": [
|
||||
"Ruben Fiszel <ruben@windmill.dev>"
|
||||
],
|
||||
"version": "1.318.0",
|
||||
"version": "1.286.2",
|
||||
"files": [
|
||||
"windmill_parser_wasm_bg.wasm",
|
||||
"windmill_parser_wasm.js",
|
||||
|
||||
@@ -561,6 +561,10 @@ async function __wbg_load(module, imports) {
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_eval_89aaea39f7e976e8 = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
@@ -612,10 +616,6 @@ function __wbg_get_imports() {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_eval_33c4985197d1feaf = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) == getObject(arg1);
|
||||
return ret;
|
||||
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/src/ee.rs
|
||||
@@ -0,0 +1,16 @@
|
||||
use anyhow::anyhow;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::error::{Error, Result};
|
||||
|
||||
pub async fn set_license_key(_license_key: String) -> anyhow::Result<()> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License cannot be set in Windmill CE"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn verify_license_key() -> Result<()> {
|
||||
// Implementation is not open source
|
||||
Err(Error::InternalErr(
|
||||
"License always invalid in Windmill CE".to_string(),
|
||||
))
|
||||
}
|
||||
+9
-15
@@ -18,11 +18,11 @@ use tokio::fs::DirBuilder;
|
||||
use windmill_api::HTTP_CLIENT;
|
||||
use windmill_common::{
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
|
||||
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, ENV_SETTINGS,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING, PIP_INDEX_URL_SETTING,
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CUSTOM_TAGS_SETTING,
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
|
||||
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING,
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
|
||||
NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING, PIP_INDEX_URL_SETTING,
|
||||
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
|
||||
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
|
||||
},
|
||||
@@ -47,11 +47,10 @@ use windmill_worker::{
|
||||
use crate::monitor::{
|
||||
initial_load, load_keep_job_dir, load_require_preexisting_user, load_tag_per_workspace_enabled,
|
||||
monitor_db, monitor_pool, reload_base_url_setting, reload_bunfig_install_scopes_setting,
|
||||
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
|
||||
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_license_key,
|
||||
reload_npm_config_registry_setting, reload_pip_index_url_setting,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_server_config,
|
||||
reload_worker_config,
|
||||
reload_extra_pip_index_url_setting, reload_hub_base_url_setting,
|
||||
reload_job_default_timeout_setting, reload_license_key, reload_npm_config_registry_setting,
|
||||
reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting,
|
||||
reload_server_config, reload_worker_config,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -504,11 +503,6 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!(error = %e, "Could not reload hub base url setting");
|
||||
}
|
||||
},
|
||||
CRITICAL_ERROR_CHANNELS_SETTING => {
|
||||
if let Err(e) = reload_critical_error_channels_setting(&db).await {
|
||||
tracing::error!(error = %e, "Could not reload critical error emails setting");
|
||||
}
|
||||
},
|
||||
a @_ => {
|
||||
tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a);
|
||||
}
|
||||
|
||||
+7
-38
@@ -22,17 +22,15 @@ use windmill_api::{
|
||||
DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT, SAML_METADATA, SCIM_TOKEN,
|
||||
};
|
||||
use windmill_common::{
|
||||
ee::CriticalErrorChannel,
|
||||
error,
|
||||
flow_status::FlowStatusModule,
|
||||
global_settings::{
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
|
||||
DEFAULT_TAGS_PER_WORKSPACE_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
|
||||
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING, PIP_INDEX_URL_SETTING,
|
||||
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
|
||||
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
|
||||
},
|
||||
jobs::QueuedJob,
|
||||
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
|
||||
@@ -42,8 +40,7 @@ use windmill_common::{
|
||||
load_worker_config, reload_custom_tags_setting, DEFAULT_TAGS_PER_WORKSPACE, SERVER_CONFIG,
|
||||
WORKER_CONFIG,
|
||||
},
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED,
|
||||
BASE_URL, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
|
||||
};
|
||||
use windmill_queue::cancel_job;
|
||||
use windmill_worker::{
|
||||
@@ -146,10 +143,6 @@ pub async fn initial_load(
|
||||
tracing::error!("Error reloading hub base url: {:?}", e)
|
||||
}
|
||||
|
||||
if let Err(e) = reload_critical_error_channels_setting(&db).await {
|
||||
tracing::error!("Could not reload critical error emails setting: {:?}", e);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
if !_is_agent {
|
||||
reload_s3_cache_setting(&db).await;
|
||||
@@ -1081,27 +1074,3 @@ pub async fn reload_hub_base_url_setting(db: &DB, server_mode: bool) -> error::R
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reload_critical_error_channels_setting(db: &DB) -> error::Result<()> {
|
||||
let critical_error_channels =
|
||||
load_value_from_global_settings(db, CRITICAL_ERROR_CHANNELS_SETTING).await?;
|
||||
|
||||
let critical_error_channels = if let Some(q) = critical_error_channels {
|
||||
if let Ok(v) = serde_json::from_value::<Vec<CriticalErrorChannel>>(q.clone()) {
|
||||
v
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Could not parse critical_error_emails setting as an array of channels, found: {:#?}",
|
||||
&q
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut l = CRITICAL_ERROR_CHANNELS.write().await;
|
||||
*l = critical_error_channels;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1026,7 +1026,7 @@ async fn listen_for_uuid_on(
|
||||
|
||||
async fn completed_job(uuid: Uuid, db: &Pool<Postgres>) -> CompletedJob {
|
||||
|
||||
sqlx::query_as::<_, CompletedJob>("SELECT *, result->'wm_labels' as labels FROM completed_job WHERE id = $1").bind(uuid)
|
||||
sqlx::query_as::<_, CompletedJob>("SELECT * FROM completed_job WHERE id = $1").bind(uuid)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -1663,7 +1663,6 @@ func main(derp string) (string, error) {
|
||||
.to_owned();
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
lock: None,
|
||||
@@ -1695,7 +1694,6 @@ echo "hello $msg"
|
||||
.to_owned();
|
||||
|
||||
let job = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
lock: None,
|
||||
@@ -1724,7 +1722,6 @@ def main():
|
||||
.to_owned();
|
||||
|
||||
let job = JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
@@ -1759,7 +1756,6 @@ def main():
|
||||
.to_owned();
|
||||
|
||||
let job = JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
@@ -1793,7 +1789,6 @@ def main():
|
||||
.to_owned();
|
||||
|
||||
let job = JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
@@ -3169,8 +3164,6 @@ async fn run_deployed_relative_imports(db: &Pool<Postgres>, script_content: Stri
|
||||
deployment_message: None,
|
||||
concurrency_key: None,
|
||||
visible_to_runner_only: None,
|
||||
no_main_func: None,
|
||||
codebase: None
|
||||
},
|
||||
).await.unwrap();
|
||||
|
||||
@@ -3218,7 +3211,6 @@ async fn run_preview_relative_imports(db: &Pool<Postgres>, script_content: Strin
|
||||
let db2 = db.clone();
|
||||
in_test_worker(&db, async move {
|
||||
let job = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content: script_content,
|
||||
path: Some("f/system/test_import".to_string()),
|
||||
language,
|
||||
|
||||
@@ -72,6 +72,7 @@ async_zip.workspace = true
|
||||
rsmq_async.workspace = true
|
||||
regex.workspace = true
|
||||
bytes.workspace = true
|
||||
mail-send.workspace = true
|
||||
samael = { workspace = true, optional = true }
|
||||
async-recursion.workspace = true
|
||||
rsa.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.324.0
|
||||
version: 1.306.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -3401,13 +3401,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: hide_without_main
|
||||
description: |
|
||||
(default false)
|
||||
hide the scripts without an exported main function
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_template
|
||||
description: |
|
||||
(default regardless)
|
||||
@@ -4907,8 +4900,6 @@ paths:
|
||||
- language
|
||||
force_viewer_static_fields:
|
||||
type: object
|
||||
force_viewer_one_of_fields:
|
||||
type: object
|
||||
required:
|
||||
- args
|
||||
- component
|
||||
@@ -5250,11 +5241,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_not_schedule
|
||||
description: is not a scheduled job
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All queued jobs
|
||||
@@ -5340,7 +5326,6 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/OrderDesc"
|
||||
- $ref: "#/components/parameters/CreatedBy"
|
||||
- $ref: "#/components/parameters/Label"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
@@ -5370,11 +5355,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_not_schedule
|
||||
description: is not a scheduled job
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All completed jobs
|
||||
@@ -5394,7 +5374,6 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/CreatedBy"
|
||||
- $ref: "#/components/parameters/Label"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
@@ -5437,11 +5416,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_not_schedule
|
||||
description: is not a scheduled job
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All jobs
|
||||
@@ -5560,26 +5534,6 @@ paths:
|
||||
flow_status:
|
||||
$ref: "#/components/schemas/WorkflowStatusRecord"
|
||||
|
||||
/w/{workspace}/jobs_u/get_log_file/{path}:
|
||||
get:
|
||||
summary: get log file from object store
|
||||
operationId: getLogFileFromStore
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: path
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: job log
|
||||
content:
|
||||
text/plain:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs_u/get_flow_debug_info/{id}:
|
||||
get:
|
||||
summary: get flow debug info
|
||||
@@ -7271,26 +7225,6 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/Input"
|
||||
|
||||
/w/{workspace}/inputs/{jobOrInputId}/args:
|
||||
get:
|
||||
summary: Get args from history or saved input
|
||||
operationId: getArgsFromHistoryOrSavedInput
|
||||
tags:
|
||||
- input
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: jobOrInputId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: args
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/inputs/list:
|
||||
get:
|
||||
summary: List saved Inputs for a Runnable
|
||||
@@ -8071,12 +8005,6 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
Label:
|
||||
name: label
|
||||
description: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
ParentJob:
|
||||
name: parent_job
|
||||
description:
|
||||
@@ -8408,11 +8336,6 @@ components:
|
||||
type: boolean
|
||||
visible_to_runner_only:
|
||||
type: boolean
|
||||
no_main_func:
|
||||
type: boolean
|
||||
codebase:
|
||||
type: string
|
||||
|
||||
required:
|
||||
- hash
|
||||
- path
|
||||
@@ -8428,8 +8351,6 @@ components:
|
||||
- language
|
||||
- kind
|
||||
- starred
|
||||
- no_main_func
|
||||
|
||||
|
||||
NewScript:
|
||||
type: object
|
||||
@@ -8503,10 +8424,6 @@ components:
|
||||
type: string
|
||||
visible_to_runner_only:
|
||||
type: boolean
|
||||
no_main_func:
|
||||
type: boolean
|
||||
codebase:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- summary
|
||||
@@ -8808,10 +8725,6 @@ components:
|
||||
type: string
|
||||
priority:
|
||||
type: integer
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- created_by
|
||||
@@ -8829,21 +8742,15 @@ components:
|
||||
- tag
|
||||
|
||||
Job:
|
||||
oneOf:
|
||||
- allOf:
|
||||
allOf:
|
||||
- oneOf:
|
||||
- $ref: "#/components/schemas/CompletedJob"
|
||||
- type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [CompletedJob]
|
||||
- allOf:
|
||||
- $ref: "#/components/schemas/QueuedJob"
|
||||
- type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [QueuedJob]
|
||||
- type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [CompletedJob, QueuedJob]
|
||||
discriminator:
|
||||
propertyName: type
|
||||
|
||||
@@ -9813,11 +9720,6 @@ components:
|
||||
- $ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
|
||||
- $ref: "#/components/schemas/FlowMetadata"
|
||||
|
||||
ExtraPerms:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
||||
FlowMetadata:
|
||||
type: object
|
||||
properties:
|
||||
@@ -9833,7 +9735,9 @@ components:
|
||||
archived:
|
||||
type: boolean
|
||||
extra_perms:
|
||||
$ref: "#/components/schemas/ExtraPerms"
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
starred:
|
||||
type: boolean
|
||||
draft_only:
|
||||
@@ -9916,10 +9820,6 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
triggerables_v2:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
execution_mode:
|
||||
type: string
|
||||
enum: [viewer, publisher, anonymous]
|
||||
@@ -10011,8 +9911,7 @@ components:
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
value:
|
||||
type: object
|
||||
value: {}
|
||||
policy:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
execution_mode:
|
||||
|
||||
@@ -143,7 +143,6 @@ pub struct AppHistoryUpdate {
|
||||
}
|
||||
|
||||
pub type StaticFields = HashMap<String, Box<RawValue>>;
|
||||
pub type OneOfFields = HashMap<String, Vec<Box<RawValue>>>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -153,12 +152,6 @@ pub enum ExecutionMode {
|
||||
Viewer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PolicyTriggerableInputs {
|
||||
static_inputs: StaticFields,
|
||||
one_of_inputs: OneOfFields,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Policy {
|
||||
pub on_behalf_of: Option<String>,
|
||||
@@ -167,10 +160,7 @@ pub struct Policy {
|
||||
// - script/<path>
|
||||
// - flow/<path>
|
||||
// - rawscript/<sha256>
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub triggerables: Option<HashMap<String, StaticFields>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub triggerables_v2: Option<HashMap<String, PolicyTriggerableInputs>>,
|
||||
pub triggerables: HashMap<String, StaticFields>,
|
||||
pub execution_mode: ExecutionMode,
|
||||
}
|
||||
|
||||
@@ -944,7 +934,6 @@ pub struct ExecuteApp {
|
||||
pub raw_code: Option<RawCode>,
|
||||
// if set, the app is executed as viewer with the given static fields
|
||||
pub force_viewer_static_fields: Option<StaticFields>,
|
||||
pub force_viewer_one_of_fields: Option<OneOfFields>,
|
||||
}
|
||||
|
||||
fn digest(code: &str) -> String {
|
||||
@@ -977,56 +966,39 @@ async fn execute_component(
|
||||
|
||||
let path = path.to_path();
|
||||
|
||||
let policy = match payload.clone() {
|
||||
ExecuteApp {
|
||||
force_viewer_static_fields: Some(static_fields),
|
||||
force_viewer_one_of_fields: Some(one_of_fields),
|
||||
..
|
||||
} => {
|
||||
let mut hm = HashMap::new();
|
||||
let policy = if let Some(static_fields) = payload.clone().force_viewer_static_fields {
|
||||
let mut hm = HashMap::new();
|
||||
|
||||
if let Some(path) = payload.path.clone() {
|
||||
hm.insert(
|
||||
format!("{}:{path}", payload.component),
|
||||
PolicyTriggerableInputs {
|
||||
static_inputs: static_fields,
|
||||
one_of_inputs: one_of_fields,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
hm.insert(
|
||||
format!(
|
||||
"{}:{}",
|
||||
payload.component,
|
||||
digest(payload.raw_code.clone().unwrap().content.as_str())
|
||||
),
|
||||
PolicyTriggerableInputs {
|
||||
static_inputs: static_fields,
|
||||
one_of_inputs: one_of_fields,
|
||||
},
|
||||
);
|
||||
}
|
||||
Policy {
|
||||
execution_mode: ExecutionMode::Viewer,
|
||||
triggerables: None,
|
||||
triggerables_v2: Some(hm),
|
||||
on_behalf_of: None,
|
||||
on_behalf_of_email: None,
|
||||
}
|
||||
if let Some(path) = payload.path.clone() {
|
||||
hm.insert(format!("{}:{path}", payload.component), static_fields);
|
||||
} else {
|
||||
hm.insert(
|
||||
format!(
|
||||
"{}:{}",
|
||||
payload.component,
|
||||
digest(payload.raw_code.clone().unwrap().content.as_str())
|
||||
),
|
||||
static_fields,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
let policy_o = sqlx::query_scalar!(
|
||||
"SELECT policy from app WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let policy = not_found_if_none(policy_o, "App", path)?;
|
||||
|
||||
serde_json::from_value::<Policy>(policy).map_err(to_anyhow)?
|
||||
Policy {
|
||||
execution_mode: ExecutionMode::Viewer,
|
||||
triggerables: hm,
|
||||
on_behalf_of: None,
|
||||
on_behalf_of_email: None,
|
||||
}
|
||||
} else {
|
||||
let policy_o = sqlx::query_scalar!(
|
||||
"SELECT policy from app WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let policy = not_found_if_none(policy_o, "App", path)?;
|
||||
|
||||
serde_json::from_value::<Policy>(policy).map_err(to_anyhow)?
|
||||
};
|
||||
|
||||
let (username, permissioned_as, email) = match policy.execution_mode {
|
||||
@@ -1161,106 +1133,22 @@ fn build_args(
|
||||
path: String,
|
||||
args: HashMap<String, Box<RawValue>>,
|
||||
) -> Result<PushArgs<HashMap<String, Box<RawValue>>>> {
|
||||
|
||||
let key = format!("{}:{}", component, &path);
|
||||
let (static_inputs, one_of_inputs) = match policy {
|
||||
Policy { triggerables_v2: Some(t), .. } => {
|
||||
let PolicyTriggerableInputs { static_inputs, one_of_inputs } = t
|
||||
.get(&key)
|
||||
.or_else(|| t.get(&path))
|
||||
.map(|x| x.clone())
|
||||
.or_else(|| {
|
||||
if matches!(policy.execution_mode, ExecutionMode::Viewer) {
|
||||
Some(PolicyTriggerableInputs {
|
||||
static_inputs: HashMap::new(),
|
||||
one_of_inputs: HashMap::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(format!("path {} is not allowed in the app policy", path))
|
||||
})?;
|
||||
|
||||
(static_inputs, one_of_inputs)
|
||||
}
|
||||
Policy { triggerables: Some(t), .. } => {
|
||||
let static_inputs = t
|
||||
.get(&key)
|
||||
.or_else(|| t.get(&path))
|
||||
.map(|x| x.clone())
|
||||
.or_else(|| {
|
||||
if matches!(policy.execution_mode, ExecutionMode::Viewer) {
|
||||
Some(HashMap::new())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(format!("path {} is not allowed in the app policy", path))
|
||||
})?;
|
||||
|
||||
(static_inputs, HashMap::new())
|
||||
}
|
||||
_ => Err(Error::BadRequest(format!(
|
||||
"Policy is missing triggerables for {}",
|
||||
key
|
||||
)))?,
|
||||
};
|
||||
|
||||
let mut args = args.clone();
|
||||
let mut safe_args = HashMap::<String, Box<RawValue>>::new();
|
||||
|
||||
for (k, v) in one_of_inputs {
|
||||
if let Some(arg_val) = args.get(&k) {
|
||||
let arg_str = arg_val.get();
|
||||
|
||||
let options_str_vec = v.iter().map(|x| x.get()).collect::<Vec<&str>>();
|
||||
if options_str_vec.contains(&arg_str) {
|
||||
safe_args.insert(k.to_string(), arg_val.clone());
|
||||
args.remove(&k);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if multiselect
|
||||
if let Ok(args_str_vec) = serde_json::from_str::<Vec<Box<RawValue>>>(arg_val.get()) {
|
||||
if args_str_vec
|
||||
.iter()
|
||||
.all(|x| options_str_vec.contains(&x.get()))
|
||||
{
|
||||
safe_args.insert(k.to_string(), arg_val.clone());
|
||||
args.remove(&k);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(format!(
|
||||
"argument {} with value {} must be one of [{}]",
|
||||
k,
|
||||
arg_str,
|
||||
options_str_vec.join(",")
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// disallow var and res access in args coming from the user for security reasons
|
||||
let mut safe_args: HashMap<String, Box<RawValue>> = args.clone();
|
||||
for (k, v) in args {
|
||||
let arg_str = serde_json::to_string(&v).unwrap_or_else(|_| "".to_string());
|
||||
|
||||
if !arg_str.contains("$var:") && !arg_str.contains("$res:") {
|
||||
safe_args.insert(k.to_string(), v);
|
||||
} else {
|
||||
let args_str = serde_json::to_string(&v).unwrap_or_else(|_| "".to_string());
|
||||
if args_str.contains("$var:") || args_str.contains("$res:") {
|
||||
safe_args.insert(
|
||||
k.to_string(),
|
||||
RawValue::from_string(
|
||||
arg_str
|
||||
args_str
|
||||
.replace(
|
||||
"$var:",
|
||||
"The following variable has been omitted for security reasons: ",
|
||||
"The following variable has been ommited for security reasons: ",
|
||||
)
|
||||
.replace(
|
||||
"$res:",
|
||||
"The following resource has been omitted for security reasons: ",
|
||||
"The following resource has been ommited for security reasons: ",
|
||||
),
|
||||
)
|
||||
.map_err(|e| {
|
||||
@@ -1272,8 +1160,24 @@ fn build_args(
|
||||
);
|
||||
}
|
||||
}
|
||||
let key = format!("{}:{}", component, &path);
|
||||
let static_args = policy
|
||||
.triggerables
|
||||
.get(&key)
|
||||
.or_else(|| policy.triggerables.get(&path))
|
||||
.map(|x| x.clone())
|
||||
.or_else(|| {
|
||||
if matches!(policy.execution_mode, ExecutionMode::Viewer) {
|
||||
Some(HashMap::new())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(format!("path {} is not allowed in the app policy", path))
|
||||
})?;
|
||||
let mut extra = HashMap::new();
|
||||
for (k, v) in static_inputs {
|
||||
for (k, v) in static_args {
|
||||
extra.insert(k.to_string(), v.to_owned());
|
||||
}
|
||||
Ok(PushArgs { extra, args: sqlx::types::Json(safe_args) })
|
||||
|
||||
@@ -136,15 +136,6 @@ impl Migrate for CustomMigrator {
|
||||
migration.version,
|
||||
migration.description
|
||||
);
|
||||
if migration.version == 20240424083501 {
|
||||
tracing::info!("Special migration to add index concurrently on job labels 2");
|
||||
sqlx::query!(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS labeled_jobs_on_jobs"
|
||||
).execute(&mut *self.inner).await?;
|
||||
sqlx::query!(
|
||||
"CREATE INDEX CONCURRENTLY labeled_jobs_on_jobs ON completed_job USING GIN ((result -> 'wm_labels')) WHERE result ? 'wm_label';"
|
||||
).execute(&mut *self.inner).await?;
|
||||
}
|
||||
let r = self.inner.apply(migration).await;
|
||||
tracing::info!("Finished applying migration {}", migration.version);
|
||||
r
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/ee.rs
|
||||
@@ -0,0 +1,6 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
@@ -543,7 +543,7 @@ async fn update_flow(
|
||||
clear_schedule(tx.transaction_mut(), &schedule.path, &w_id).await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule).await?;
|
||||
tx = push_scheduled_job(&db, tx, schedule).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +955,7 @@ mod tests {
|
||||
continue_on_error: None,
|
||||
},
|
||||
],
|
||||
failure_module: Some(Box::new(FlowModule {
|
||||
failure_module: Some(FlowModule {
|
||||
id: "d".to_string(),
|
||||
value: FlowModuleValue::Script {
|
||||
path: "test".to_string(),
|
||||
@@ -977,7 +977,7 @@ mod tests {
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
continue_on_error: None,
|
||||
})),
|
||||
}),
|
||||
same_worker: false,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
|
||||
@@ -271,7 +271,7 @@ async fn update_folder(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
Json(mut ng): Json<UpdateFolder>,
|
||||
Json(ng): Json<UpdateFolder>,
|
||||
) -> Result<String> {
|
||||
use sql_builder::prelude::*;
|
||||
|
||||
@@ -282,25 +282,6 @@ async fn update_folder(
|
||||
if let Some(display_name) = ng.display_name {
|
||||
sqlb.set("display_name", "?".bind(&display_name));
|
||||
}
|
||||
|
||||
if !authed.is_admin {
|
||||
let prefixed_username = format!("u/{}", authed.username);
|
||||
if ng.owners.as_ref().is_some_and(|x| {
|
||||
!x.contains(&prefixed_username)
|
||||
&& !authed.groups.iter().any(|g| x.contains(&format!("g/{g}")))
|
||||
}) {
|
||||
ng.owners.as_mut().unwrap().push(prefixed_username.clone());
|
||||
if ng.extra_perms.is_none() {
|
||||
ng.extra_perms = Some(serde_json::Value::Object(serde_json::Map::new()));
|
||||
}
|
||||
ng.extra_perms
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert(prefixed_username, serde_json::json!(true));
|
||||
}
|
||||
}
|
||||
if let Some(owners) = ng.owners {
|
||||
sqlb.set(
|
||||
"owners",
|
||||
@@ -329,16 +310,9 @@ async fn update_folder(
|
||||
.sql()
|
||||
.map_err(|e| error::Error::InternalErr(e.to_string()))?;
|
||||
let nfolder = sqlx::query_as::<_, Folder>(&sql)
|
||||
.fetch_optional(&mut *tx)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let nfolder = nfolder.ok_or_else(|| {
|
||||
windmill_common::error::Error::NotAuthorized(format!(
|
||||
"You are not an owner of {} and hence cannot modify it",
|
||||
name
|
||||
))
|
||||
})?;
|
||||
|
||||
if let Some(extra_perms) = nfolder.extra_perms.as_object() {
|
||||
for o in nfolder.owners {
|
||||
if !extra_perms
|
||||
@@ -504,22 +478,13 @@ async fn delete_folder(
|
||||
|
||||
not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?;
|
||||
|
||||
let del = sqlx::query_scalar!(
|
||||
"DELETE FROM folder WHERE name = $1 AND workspace_id = $2 RETURNING 1",
|
||||
sqlx::query!(
|
||||
"DELETE FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
name,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if del.is_none() {
|
||||
return Err(windmill_common::error::Error::NotAuthorized(format!(
|
||||
"Not authorized to delete folder {}",
|
||||
name
|
||||
)));
|
||||
}
|
||||
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
|
||||
@@ -25,7 +25,7 @@ use windmill_common::{
|
||||
error::JsonResult,
|
||||
jobs::JobKind,
|
||||
scripts::to_i64,
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
utils::{paginate, Pagination},
|
||||
};
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
@@ -34,10 +34,6 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/create", post(create_input))
|
||||
.route("/update", post(update_input))
|
||||
.route("/delete/:id", post(delete_input))
|
||||
.route(
|
||||
"/:job_or_input_id/args",
|
||||
get(get_args_from_history_or_saved_input),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow, Serialize, Deserialize)]
|
||||
@@ -127,7 +123,7 @@ async fn get_input_history(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let sql = &format!(
|
||||
"select id, created_at, created_by, CASE WHEN args is null or pg_column_size(args) < 40000 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args, success from completed_job \
|
||||
"select id, created_at, created_by, args, success from completed_job \
|
||||
where {} = $1 and job_kind = $2 and workspace_id = $3 \
|
||||
order by created_at desc limit $4 offset $5",
|
||||
r.runnable_type.column_name()
|
||||
@@ -171,27 +167,6 @@ async fn get_input_history(
|
||||
Ok(Json(inputs))
|
||||
}
|
||||
|
||||
async fn get_args_from_history_or_saved_input(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, job_or_input_id)): Path<(String, Uuid)>,
|
||||
) -> JsonResult<Option<Value>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let result_o = sqlx::query_scalar!(
|
||||
"SELECT args FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT args FROM input WHERE id = $1 AND workspace_id = $2",
|
||||
job_or_input_id,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
let result = not_found_if_none(result_o, "Input args", job_or_input_id.to_string())?;
|
||||
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn list_saved_inputs(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -204,7 +179,7 @@ async fn list_saved_inputs(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let rows = sqlx::query_as::<_, InputRow>(
|
||||
"select id, workspace_id, runnable_id, runnable_type, name, CASE WHEN pg_column_size(args) < 40000 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args, created_at, created_by, is_public from input \
|
||||
"select * from input \
|
||||
where runnable_id = $1 and runnable_type = $2 and workspace_id = $3 \
|
||||
and (is_public IS true OR created_by = $4) \
|
||||
order by created_at desc limit $5 offset $6",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/job_helpers_ee.rs
|
||||
@@ -0,0 +1,5 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -19,9 +19,6 @@ use windmill_common::jobs::{
|
||||
format_completed_job_result, format_result, CompletedJobWithFormattedResult, FormattedResult,
|
||||
ENTRYPOINT_OVERRIDE,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::scripts::PREVIEW_IS_CODEBASE_HASH;
|
||||
use windmill_common::variables::get_workspace_key;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
@@ -166,7 +163,6 @@ pub fn workspaced_service() -> Router {
|
||||
.layer(cors.clone()),
|
||||
)
|
||||
.route("/run/preview", post(run_preview_script))
|
||||
.route("/run/preview_bundle", post(run_bundle_preview_script))
|
||||
.route("/add_batch_jobs/:n", post(add_batch_jobs))
|
||||
.route("/run/preview_flow", post(run_preview_flow_job))
|
||||
.route(
|
||||
@@ -255,7 +251,6 @@ pub fn global_service() -> Router {
|
||||
get(get_completed_job_result_maybe),
|
||||
)
|
||||
.route("/getupdate/:id", get(get_job_update))
|
||||
.route("/get_log_file/*file_path", get(get_log_file))
|
||||
.route("/queue/cancel/:id", post(cancel_job_api))
|
||||
.route(
|
||||
"/queue/cancel_persistent/*script_path",
|
||||
@@ -589,7 +584,6 @@ fn generate_get_job_query(no_logs: bool, table: &str) -> String {
|
||||
result,
|
||||
deleted,
|
||||
is_skipped,
|
||||
result->'wm_labels' as labels,
|
||||
CASE WHEN result is null or pg_column_size(result) < 2000000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result"
|
||||
} else {
|
||||
"scheduled_for,
|
||||
@@ -784,8 +778,6 @@ pub struct ListableCompletedJob {
|
||||
pub tag: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -846,7 +838,6 @@ pub struct ListQueueQuery {
|
||||
pub all_workspaces: Option<bool>,
|
||||
pub is_flow_step: Option<bool>,
|
||||
pub has_null_parent: Option<bool>,
|
||||
pub is_not_schedule: Option<bool>,
|
||||
}
|
||||
|
||||
fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder {
|
||||
@@ -951,10 +942,6 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
sqlb.and_where_le("scheduled_for", "now()");
|
||||
}
|
||||
|
||||
if lq.is_not_schedule.unwrap_or(false) {
|
||||
sqlb.and_where("schedule_path IS null");
|
||||
}
|
||||
|
||||
sqlb
|
||||
}
|
||||
|
||||
@@ -1025,62 +1012,49 @@ async fn cancel_all(
|
||||
) -> error::JsonResult<Vec<Uuid>> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let jobs = sqlx::query!(
|
||||
"SELECT id, running, is_flow_step FROM queue WHERE scheduled_for < now() AND workspace_id = $1 AND schedule_path IS NULL",
|
||||
let mut jobs = sqlx::query!(
|
||||
"UPDATE queue SET canceled = true, canceled_by = $2, scheduled_for = now(), suspend = 0 WHERE scheduled_for < now() AND workspace_id = $1 AND schedule_path IS NULL RETURNING id, running, is_flow_step",
|
||||
w_id,
|
||||
authed.username
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
let username = authed.username;
|
||||
let mut uuids = vec![];
|
||||
for j in jobs.iter() {
|
||||
let r = sqlx::query!(
|
||||
"UPDATE queue SET canceled = true, canceled_by = $1, scheduled_for = now(), suspend = 0 WHERE id = $2 RETURNING 1 as one",
|
||||
username,
|
||||
j.id,
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await;
|
||||
if !j.running && !j.is_flow_step.unwrap_or(false) {
|
||||
let e = serde_json::json!({"message": format!("Job canceled: cancel_all by {username}"), "name": "Canceled", "reason": "cancel_all", "canceler": username});
|
||||
let job_running = get_queued_job(&j.id, &w_id, &db).await?;
|
||||
|
||||
if r.as_ref().is_ok_and(|x| x.is_some()) {
|
||||
uuids.push(j.id);
|
||||
|
||||
if !j.running && !j.is_flow_step.unwrap_or(false) {
|
||||
let e = serde_json::json!({"message": format!("Job canceled: cancel_all by {username}"), "name": "Canceled", "reason": "cancel_all", "canceler": username});
|
||||
let job_running = get_queued_job(&j.id, &w_id, &db).await?;
|
||||
|
||||
if let Some(job_running) = job_running {
|
||||
append_logs(
|
||||
j.id,
|
||||
w_id.clone(),
|
||||
format!("canceled by {username}: cancel_all"),
|
||||
db.clone(),
|
||||
)
|
||||
.await;
|
||||
let add_job = add_completed_job_error(
|
||||
&db,
|
||||
&job_running,
|
||||
job_running.mem_peak.unwrap_or(0),
|
||||
Some(CanceledBy {
|
||||
username: Some(username.to_string()),
|
||||
reason: Some("cancel_all".to_string()),
|
||||
}),
|
||||
e,
|
||||
rsmq.clone(),
|
||||
"server",
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = add_job {
|
||||
tracing::error!("Failed to add canceled job: {}", e);
|
||||
}
|
||||
if let Some(job_running) = job_running {
|
||||
append_logs(
|
||||
j.id,
|
||||
w_id.clone(),
|
||||
format!("canceled by {username}: cancel_all"),
|
||||
db.clone(),
|
||||
)
|
||||
.await;
|
||||
let add_job = add_completed_job_error(
|
||||
&db,
|
||||
&job_running,
|
||||
job_running.mem_peak.unwrap_or(0),
|
||||
Some(CanceledBy {
|
||||
username: Some(username.to_string()),
|
||||
reason: Some("cancel_all".to_string()),
|
||||
}),
|
||||
e,
|
||||
rsmq.clone(),
|
||||
"server",
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = add_job {
|
||||
tracing::error!("Failed to add canceled job: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Failed to cancel job: {:?} {:?}", j.id, r.err());
|
||||
}
|
||||
}
|
||||
let uuids = jobs.iter_mut().map(|j| j.id).collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(uuids))
|
||||
}
|
||||
@@ -1183,14 +1157,13 @@ async fn list_jobs(
|
||||
"null as concurrent_limit",
|
||||
"null as concurrency_time_window_s",
|
||||
"priority",
|
||||
"result->'wm_labels' as labels",
|
||||
],
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let sql = if lq.success.is_none() && lq.label.is_none() {
|
||||
let sql = if lq.success.is_none() {
|
||||
let sqlq = list_queue_jobs_query(
|
||||
&w_id,
|
||||
&ListQueueQuery {
|
||||
@@ -1216,7 +1189,6 @@ async fn list_jobs(
|
||||
all_workspaces: lq.all_workspaces,
|
||||
is_flow_step: lq.is_flow_step,
|
||||
has_null_parent: lq.has_null_parent,
|
||||
is_not_schedule: lq.is_not_schedule,
|
||||
},
|
||||
&[
|
||||
"'QueuedJob' as typ",
|
||||
@@ -1250,7 +1222,6 @@ async fn list_jobs(
|
||||
"concurrent_limit",
|
||||
"concurrency_time_window_s",
|
||||
"priority",
|
||||
"null as labels",
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1983,7 +1954,6 @@ struct UnifiedJob {
|
||||
concurrent_limit: Option<i32>,
|
||||
concurrency_time_window_s: Option<i32>,
|
||||
priority: Option<i16>,
|
||||
labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl<'a> From<UnifiedJob> for Job {
|
||||
@@ -2021,7 +1991,6 @@ impl<'a> From<UnifiedJob> for Job {
|
||||
mem_peak: uj.mem_peak,
|
||||
tag: uj.tag,
|
||||
priority: uj.priority,
|
||||
labels: uj.labels,
|
||||
}),
|
||||
"QueuedJob" => Job::QueuedJob(QueuedJob {
|
||||
workspace_id: uj.workspace_id,
|
||||
@@ -2081,9 +2050,7 @@ enum PreviewKind {
|
||||
Identity,
|
||||
Http,
|
||||
Noop,
|
||||
Bundle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Preview {
|
||||
content: Option<String>,
|
||||
@@ -2304,7 +2271,7 @@ pub async fn restart_flow(
|
||||
check_license_key_valid().await?;
|
||||
|
||||
let completed_job = sqlx::query_as::<_, CompletedJob>(
|
||||
"SELECT *, result->'wm_labels' as labels from completed_job WHERE id = $1 and workspace_id = $2",
|
||||
"SELECT * from completed_job WHERE id = $1 and workspace_id = $2",
|
||||
)
|
||||
.bind(job_id)
|
||||
.bind(&w_id)
|
||||
@@ -2426,7 +2393,6 @@ pub async fn run_workflow_as_code(
|
||||
let (job_payload, tag, _delete_after_use, timeout) = match job.job_kind {
|
||||
JobKind::Preview => (
|
||||
JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content: job.raw_code.unwrap_or_default(),
|
||||
path: job.script_path,
|
||||
language: job.language.unwrap_or_else(|| ScriptLang::Deno),
|
||||
@@ -3090,7 +3056,6 @@ async fn run_preview_script(
|
||||
Some(PreviewKind::Identity) => JobPayload::Identity,
|
||||
Some(PreviewKind::Noop) => JobPayload::Noop,
|
||||
_ => JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
@@ -3125,136 +3090,6 @@ async fn run_preview_script(
|
||||
Ok((StatusCode::CREATED, uuid.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
async fn run_bundle_preview_script(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
check_license_key_valid().await?;
|
||||
|
||||
check_scopes(&authed, || format!("runscript"))?;
|
||||
if authed.is_operator {
|
||||
return Err(error::Error::NotAuthorized(
|
||||
"Operators cannot run preview jobs for security reasons".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut job_id = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
if name == "preview" {
|
||||
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
|
||||
let scheduled_for = run_query.get_scheduled_for(&db).await?;
|
||||
let tag = run_query.tag.clone().or(preview.tag.clone());
|
||||
check_tag_available_for_workspace(&w_id, &tag).await?;
|
||||
let ltx =
|
||||
PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq.clone());
|
||||
|
||||
let args = preview.args.unwrap_or_default();
|
||||
|
||||
// hmap.insert("")
|
||||
let (uuid, ntx) = push(
|
||||
&db,
|
||||
ltx,
|
||||
&w_id,
|
||||
match preview.kind {
|
||||
Some(PreviewKind::Identity) => JobPayload::Identity,
|
||||
Some(PreviewKind::Noop) => JobPayload::Noop,
|
||||
_ => JobPayload::Code(RawCode {
|
||||
hash: Some(PREVIEW_IS_CODEBASE_HASH),
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
lock: preview.lock,
|
||||
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
|
||||
concurrency_time_window_s: None, // TODO(gbouv): same as above
|
||||
cache_ttl: None,
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
}),
|
||||
},
|
||||
args,
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
run_query.job_id,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
tag,
|
||||
run_query.timeout,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
job_id = Some(uuid);
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
let id = job_id
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"script need to be passed first in the multipart upload".to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
uploaded = true;
|
||||
|
||||
if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
{
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data)
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(Error::ExecutionErr(format!("Failed to put {path} to s3")));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
if !uploaded {
|
||||
return Err(Error::BadRequest("No file uploaded".to_string()));
|
||||
}
|
||||
if job_id.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
"No script found in the uploaded file".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
tx.unwrap().commit().await?;
|
||||
|
||||
Ok((StatusCode::CREATED, job_id.unwrap().to_string()))
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
async fn run_bundle_preview_script() -> error::Result<(StatusCode, String)> {
|
||||
return Err(Error::BadRequest(
|
||||
"bundle preview is an ee feature".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RunDependenciesRequest {
|
||||
pub raw_scripts: Vec<RawScriptForDependencies>,
|
||||
@@ -3649,84 +3484,6 @@ pub struct JobUpdate {
|
||||
pub flow_status: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// #[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
// async fn get_logs_from_store(
|
||||
// log_offset: i32,
|
||||
// logs: &str,
|
||||
// log_file_index: Option<Vec<String>>,
|
||||
// ) -> Option<error::Result<Body>> {
|
||||
// if log_offset > 0 {
|
||||
// if let Some(file_index) = log_file_index {
|
||||
// tracing::debug!("Getting logs from store: {file_index:?}");
|
||||
// if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
// tracing::debug!("object store client present, streaming from there");
|
||||
|
||||
// let logs = logs.to_string();
|
||||
// let stream = async_stream::stream! {
|
||||
// for file_p in file_index {
|
||||
// let file_p_2 = file_p.clone();
|
||||
// let file = os.get(&object_store::path::Path::from(file_p)).await;
|
||||
// if let Ok(file) = file {
|
||||
// if let Ok(bytes) = file.bytes().await {
|
||||
// yield Ok(bytes::Bytes::from(bytes)) as object_store::Result<bytes::Bytes>;
|
||||
// }
|
||||
// } else {
|
||||
// tracing::debug!("error getting file from store: {file_p_2}: {}", file.err().unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
// yield Ok(bytes::Bytes::from(logs))
|
||||
// };
|
||||
// return Some(Ok(Body::from_stream(stream)));
|
||||
// } else {
|
||||
// tracing::debug!("object store client not present, cannot stream logs from store");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return None;
|
||||
// }
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::Result<Response> {
|
||||
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
let file = os
|
||||
.get(&object_store::path::Path::from(format!("logs/{file_p}")))
|
||||
.await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
use axum::http::header;
|
||||
let res = Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.body(Body::from(bytes::Bytes::from(bytes)))
|
||||
.unwrap();
|
||||
return Ok(res);
|
||||
} else {
|
||||
return Err(error::Error::InternalErr(format!(
|
||||
"Error getting bytes from file: {}",
|
||||
file_p
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(error::Error::NotFound(format!(
|
||||
"File not found: {}",
|
||||
file_p
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(error::Error::InternalErr(
|
||||
"Object store client not present, cannot stream logs from store".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::Result<Response> {
|
||||
return Err(error::Error::NotFound(format!(
|
||||
"Get log file is an EE feature: {}",
|
||||
file_p
|
||||
)));
|
||||
}
|
||||
|
||||
async fn get_job_update(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, job_id)): Path<(String, Uuid)>,
|
||||
@@ -3883,17 +3640,6 @@ fn list_completed_jobs_query(
|
||||
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
let mut wh = format!("result->'wm_labels' ? ");
|
||||
wh.push_str(&format!("'{}'", &label.replace("'", "''")));
|
||||
sqlb.and_where(&wh);
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
}
|
||||
|
||||
if lq.is_not_schedule.unwrap_or(false) {
|
||||
sqlb.and_where("schedule_path IS null");
|
||||
}
|
||||
|
||||
sqlb
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -3925,8 +3671,6 @@ pub struct ListCompletedQuery {
|
||||
pub scheduled_for_before_now: Option<bool>,
|
||||
pub all_workspaces: Option<bool>,
|
||||
pub has_null_parent: Option<bool>,
|
||||
pub label: Option<String>,
|
||||
pub is_not_schedule: Option<bool>,
|
||||
}
|
||||
|
||||
async fn list_completed_jobs(
|
||||
@@ -3974,7 +3718,6 @@ async fn list_completed_jobs(
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"priority",
|
||||
"result->'wm_labels' as labels",
|
||||
"'CompletedJob' as type",
|
||||
],
|
||||
)
|
||||
@@ -3992,7 +3735,7 @@ async fn get_completed_job<'a>(
|
||||
let job_o = sqlx::query("SELECT id, workspace_id, parent_job, created_by, created_at, duration_ms, success, script_hash, script_path,
|
||||
CASE WHEN args is null or pg_column_size(args) < 2000000 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args, CASE WHEN result is null or pg_column_size(result) < 2000000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result, logs, deleted, raw_code, canceled, canceled_by, canceled_reason, job_kind, env_id,
|
||||
schedule_path, permissioned_as, flow_status, raw_flow, is_flow_step, language, started_at, is_skipped,
|
||||
raw_lock, email, visible_to_owner, mem_peak, tag, priority, result->'wm_labels' as labels FROM completed_job WHERE id = $1 AND workspace_id = $2")
|
||||
raw_lock, email, visible_to_owner, mem_peak, tag, priority FROM completed_job WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(w_id)
|
||||
.fetch_optional(&db)
|
||||
|
||||
@@ -25,7 +25,6 @@ use db::DB;
|
||||
use git_version::git_version;
|
||||
use reqwest::Client;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
use tower::ServiceBuilder;
|
||||
@@ -105,8 +104,6 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(30))
|
||||
.danger_accept_invalid_certs(std::env::var("ACCEPT_INVALID_CERTS").is_ok())
|
||||
.build().unwrap();
|
||||
|
||||
@@ -144,12 +141,14 @@ pub async fn run_server(
|
||||
));
|
||||
let argon2 = Arc::new(Argon2::default());
|
||||
|
||||
let disable_response_logs = std::env::var("DISABLE_RESPONSE_LOGS")
|
||||
.ok()
|
||||
.map(|x| x == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
.layer(
|
||||
TraceLayer::new_for_http()
|
||||
.on_response(MyOnResponse {})
|
||||
.make_span_with(MyMakeSpan {})
|
||||
.on_request(())
|
||||
.on_failure(MyOnFailure {}),
|
||||
)
|
||||
.layer(Extension(db.clone()))
|
||||
.layer(Extension(rsmq))
|
||||
.layer(Extension(user_db))
|
||||
@@ -286,18 +285,6 @@ pub async fn run_server(
|
||||
.fallback(static_assets::static_handler)
|
||||
.layer(middleware_stack);
|
||||
|
||||
let app = if disable_response_logs {
|
||||
app
|
||||
} else {
|
||||
app.layer(
|
||||
TraceLayer::new_for_http()
|
||||
.on_response(MyOnResponse {})
|
||||
.make_span_with(MyMakeSpan {})
|
||||
.on_request(())
|
||||
.on_failure(MyOnFailure {}),
|
||||
)
|
||||
};
|
||||
|
||||
let instance_name = rd_string(5);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
@@ -333,7 +320,6 @@ async fn is_up_to_date() -> Result<String, AppError> {
|
||||
let error_reading_version = || anyhow::anyhow!("Error reading latest released version");
|
||||
let version = HTTP_CLIENT
|
||||
.get("https://api.github.com/repos/windmill-labs/windmill/releases/latest")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.context("Impossible to reach api.github")?
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/oauth2_ee.rs
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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, fmt::Debug};
|
||||
|
||||
use axum::{routing::get, Json, Router};
|
||||
use hmac::Mac;
|
||||
use hyper::HeaderMap;
|
||||
|
||||
use oauth2::{Client as OClient, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use windmill_common::more_serde::maybe_number_opt;
|
||||
|
||||
use crate::OAUTH_CLIENTS;
|
||||
use windmill_common::error;
|
||||
use windmill_common::oauth2::*;
|
||||
|
||||
use crate::db::DB;
|
||||
use std::str;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list_supabase", get(list_supabase))
|
||||
.route("/list_logins", get(list_logins))
|
||||
.route("/list_connects", get(list_connects))
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum InstanceEvent {
|
||||
UserAdded { email: String },
|
||||
// UserDeleted { email: String },
|
||||
// UserDeletedWorkspace { workspace: String, email: String },
|
||||
UserAddedWorkspace { workspace: String, email: String },
|
||||
UserInvitedWorkspace { workspace: String, email: String },
|
||||
UserJoinedWorkspace { workspace: String, email: String, username: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientWithScopes {
|
||||
_client: OClient,
|
||||
scopes: Vec<String>,
|
||||
extra_params: Option<HashMap<String, String>>,
|
||||
_extra_params_callback: Option<HashMap<String, String>>,
|
||||
_allowed_domains: Option<Vec<String>>,
|
||||
_userinfo_url: Option<String>,
|
||||
}
|
||||
|
||||
pub type BasicClientsMap = HashMap<String, ClientWithScopes>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct OAuthConfig {
|
||||
auth_url: String,
|
||||
token_url: String,
|
||||
userinfo_url: Option<String>,
|
||||
scopes: Option<Vec<String>>,
|
||||
extra_params: Option<HashMap<String, String>>,
|
||||
extra_params_callback: Option<HashMap<String, String>>,
|
||||
req_body_auth: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct OAuthClient {
|
||||
id: String,
|
||||
secret: String,
|
||||
allowed_domains: Option<Vec<String>>,
|
||||
connect_config: Option<OAuthConfig>,
|
||||
login_config: Option<OAuthConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AllClients {
|
||||
pub logins: BasicClientsMap,
|
||||
pub connects: BasicClientsMap,
|
||||
pub slack: Option<OClient>,
|
||||
}
|
||||
|
||||
pub fn build_oauth_clients(
|
||||
_base_url: &str,
|
||||
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
|
||||
) -> anyhow::Result<AllClients> {
|
||||
// Implementation is not open source
|
||||
return Ok(AllClients {
|
||||
logins: HashMap::default(),
|
||||
connects: HashMap::default(),
|
||||
slack: None,
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct TokenResponse {
|
||||
access_token: AccessToken,
|
||||
#[serde(deserialize_with = "maybe_number_opt")]
|
||||
#[serde(default)]
|
||||
expires_in: Option<u64>,
|
||||
refresh_token: Option<RefreshToken>,
|
||||
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
|
||||
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
|
||||
#[serde(default)]
|
||||
scope: Option<Vec<Scope>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Logins {
|
||||
oauth: Vec<String>,
|
||||
saml: Option<String>,
|
||||
}
|
||||
async fn list_logins() -> error::JsonResult<Logins> {
|
||||
// Implementation is not open source
|
||||
return Ok(Json(Logins { oauth: vec![], saml: None }));
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ScopesAndParams {
|
||||
scopes: Vec<String>,
|
||||
extra_params: Option<HashMap<String, String>>,
|
||||
}
|
||||
async fn list_connects() -> error::JsonResult<HashMap<String, ScopesAndParams>> {
|
||||
Ok(Json(
|
||||
(&OAUTH_CLIENTS.read().await.connects)
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_owned(),
|
||||
ScopesAndParams {
|
||||
scopes: v.scopes.clone(),
|
||||
extra_params: v.extra_params.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, ScopesAndParams>>(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn _refresh_token<'c>(
|
||||
_tx: Transaction<'c, Postgres>,
|
||||
_path: &str,
|
||||
_w_id: &str,
|
||||
_id: i32,
|
||||
) -> error::Result<String> {
|
||||
// Implementation is not open source
|
||||
Err(error::Error::BadRequest(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn list_supabase(_headers: HeaderMap) -> error::Result<String> {
|
||||
// Implementation is not open source
|
||||
Err(error::Error::BadRequest(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn check_nb_of_user(db: &DB) -> error::Result<()> {
|
||||
let nb_users_sso =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if nb_users_sso.unwrap_or(0) >= 10 {
|
||||
return Err(error::Error::BadRequest(
|
||||
"You have reached the maximum number of oauth users accounts (10) without an enterprise license"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let nb_users = sqlx::query_scalar!("SELECT COUNT(*) FROM password",)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if nb_users.unwrap_or(0) >= 50 {
|
||||
return Err(error::Error::BadRequest(
|
||||
"You have reached the maximum number of accounts (50) without an enterprise license"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SlackVerifier {
|
||||
_mac: HmacSha256,
|
||||
}
|
||||
|
||||
impl SlackVerifier {
|
||||
pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
|
||||
HmacSha256::new_from_slice(secret.as_ref())
|
||||
.map(|mac| SlackVerifier { _mac: mac })
|
||||
.map_err(|_| anyhow::anyhow!("invalid secret"))
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/oidc_ee.rs
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2023
|
||||
* 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 axum::Router;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/saml_ee.rs
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2023
|
||||
* 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.
|
||||
*/
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use axum::{routing::post, Router};
|
||||
|
||||
pub struct ServiceProviderExt();
|
||||
|
||||
pub async fn build_sp_extension() -> anyhow::Result<ServiceProviderExt> {
|
||||
return Ok(ServiceProviderExt());
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new().route("/acs", post(acs))
|
||||
}
|
||||
|
||||
pub async fn acs() -> String {
|
||||
// Implementation is not open source as it is a Windmill Enterprise Edition feature
|
||||
"SAML available only in enterprise version".to_string()
|
||||
}
|
||||
@@ -221,7 +221,7 @@ async fn create_schedule(
|
||||
.await?;
|
||||
|
||||
if ns.enabled.unwrap_or(true) {
|
||||
tx = push_scheduled_job(&db, tx, &schedule).await?
|
||||
tx = push_scheduled_job(&db, tx, schedule).await?
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -303,7 +303,7 @@ async fn edit_schedule(
|
||||
.await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule).await?;
|
||||
tx = push_scheduled_job(&db, tx, schedule).await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -512,7 +512,7 @@ pub async fn set_enabled(
|
||||
.await?;
|
||||
|
||||
if payload.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule).await?;
|
||||
tx = push_scheduled_job(&db, tx, schedule).await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -560,7 +560,7 @@ pub async fn set_enabled(
|
||||
// .await?;
|
||||
|
||||
// if payload.enabled {
|
||||
// tx = push_scheduled_job(&db, tx, &schedule).await?;
|
||||
// tx = push_scheduled_job(&db, tx, schedule).await?;
|
||||
// }
|
||||
// tx.commit().await?;
|
||||
|
||||
@@ -581,37 +581,14 @@ async fn delete_schedule(
|
||||
let path = path.to_path();
|
||||
|
||||
clear_schedule(&mut tx, path, &w_id).await?;
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT 1 FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if exists.is_none() {
|
||||
return Err(windmill_common::error::Error::NotFound(format!(
|
||||
"Schedule {} not found",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
let del = sqlx::query_scalar!(
|
||||
"DELETE FROM schedule WHERE path = $1 AND workspace_id = $2 RETURNING 1",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if del.is_none() {
|
||||
return Err(windmill_common::error::Error::NotAuthorized(format!(
|
||||
"Not authorized to delete schedule {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
@@ -800,7 +777,7 @@ pub async fn clear_schedule<'c>(
|
||||
w_id: &str,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM queue WHERE schedule_path = $1 AND running = false AND workspace_id = $2 AND is_flow_step = false",
|
||||
"DELETE FROM queue WHERE schedule_path = $1 AND running = false AND workspace_id = $2",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/scim_ee.rs
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2023
|
||||
* 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 axum::{middleware::Next, response::Response, routing::get, Router};
|
||||
use hyper::Request;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new().route("/ee", get(ee))
|
||||
}
|
||||
|
||||
pub async fn ee() -> String {
|
||||
return "Enterprise Edition".to_string();
|
||||
}
|
||||
|
||||
pub async fn has_scim_token<B>(_request: Request<B>, _next: Next) -> Response {
|
||||
//Not implemented in open-source version
|
||||
todo!()
|
||||
}
|
||||
@@ -13,9 +13,6 @@ use crate::{
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use axum::extract::Multipart;
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
response::IntoResponse,
|
||||
@@ -34,10 +31,6 @@ use std::{
|
||||
};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::error::to_anyhow;
|
||||
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
@@ -98,8 +91,6 @@ pub struct ScriptWDraft {
|
||||
pub concurrency_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_main_func: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -123,7 +114,6 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/list", get(list_scripts))
|
||||
.route("/list_search", get(list_search_scripts))
|
||||
.route("/create", post(create_script))
|
||||
.route("/create_snapshot", post(create_snapshot_script))
|
||||
.route("/archive/p/*path", post(archive_script_by_path))
|
||||
.route("/get/draft/*path", get(get_script_by_path_w_draft))
|
||||
.route("/get/p/*path", get(get_script_by_path))
|
||||
@@ -202,9 +192,7 @@ async fn list_scripts(
|
||||
"tag",
|
||||
"draft.path IS NOT NULL as has_draft",
|
||||
"draft_only",
|
||||
"ws_error_handler_muted",
|
||||
"no_main_func",
|
||||
"codebase IS NOT NULL as use_codebase"
|
||||
"ws_error_handler_muted"
|
||||
])
|
||||
.left()
|
||||
.join("favorite")
|
||||
@@ -224,10 +212,6 @@ async fn list_scripts(
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
|
||||
if authed.is_operator || lq.hide_without_main.unwrap_or(false) {
|
||||
sqlb.and_where("o.no_main_func IS NOT TRUE");
|
||||
}
|
||||
|
||||
if lq.show_archived.unwrap_or(false) {
|
||||
sqlb.and_where_eq(
|
||||
"o.created_at",
|
||||
@@ -321,82 +305,6 @@ fn hash_script(ns: &NewScript) -> i64 {
|
||||
dh.finish() as i64
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
async fn create_snapshot_script() -> Result<(StatusCode, String)> {
|
||||
Err(Error::BadRequest("Upgrade to EE to use bundle".to_string()))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
async fn create_snapshot_script(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut script_hash = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
if name == "script" {
|
||||
let ns = Some(serde_json::from_slice(&data).map_err(to_anyhow)?);
|
||||
let (new_hash, ntx) = create_script_internal(
|
||||
ns.unwrap(),
|
||||
w_id.clone(),
|
||||
authed.clone(),
|
||||
db.clone(),
|
||||
rsmq.clone(),
|
||||
user_db.clone(),
|
||||
webhook.clone(),
|
||||
)
|
||||
.await?;
|
||||
script_hash = Some(new_hash.to_string());
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
let hash = script_hash.as_ref().ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"script need to be passed first in the multipart upload".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
uploaded = true;
|
||||
if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
{
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &hash);
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data)
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(Error::ExecutionErr(format!("Failed to put {path} to s3")));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
if !uploaded {
|
||||
return Err(Error::BadRequest("No file uploaded".to_string()));
|
||||
}
|
||||
if script_hash.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
"No script found in the uploaded file".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
tx.unwrap().commit().await?;
|
||||
return Ok((StatusCode::CREATED, format!("{}", script_hash.unwrap())));
|
||||
}
|
||||
|
||||
async fn create_script(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -406,24 +314,6 @@ async fn create_script(
|
||||
Path(w_id): Path<String>,
|
||||
Json(ns): Json<NewScript>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let (hash, tx) = create_script_internal(ns, w_id, authed, db, rsmq, user_db, webhook).await?;
|
||||
tx.commit().await?;
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
}
|
||||
|
||||
async fn create_script_internal<'c>(
|
||||
ns: NewScript,
|
||||
w_id: String,
|
||||
authed: ApiAuthed,
|
||||
db: sqlx::Pool<Postgres>,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
user_db: UserDB,
|
||||
webhook: WebhookShared,
|
||||
) -> Result<(
|
||||
ScriptHash,
|
||||
QueueTransaction<'c, rsmq_async::MultiplexedRsmq>,
|
||||
)> {
|
||||
let codebase = ns.codebase.as_ref();
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if ns.ws_error_handler_muted.is_some_and(|val| val) {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -431,10 +321,12 @@ async fn create_script_internal<'c>(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
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.clone(), user_db.begin(&authed).await?).into();
|
||||
|
||||
if sqlx::query_scalar!(
|
||||
"SELECT 1 FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
hash.0,
|
||||
@@ -450,6 +342,7 @@ async fn create_script_internal<'c>(
|
||||
.to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
let clashing_script = sqlx::query_as::<_, Script>(
|
||||
"SELECT * FROM script WHERE path = $1 AND archived = false AND workspace_id = $2",
|
||||
)
|
||||
@@ -457,6 +350,7 @@ async fn create_script_internal<'c>(
|
||||
.bind(&w_id)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
|
||||
struct ParentInfo {
|
||||
p_hashes: Vec<i64>,
|
||||
perms: serde_json::Value,
|
||||
@@ -549,11 +443,13 @@ async fn create_script_internal<'c>(
|
||||
r
|
||||
}
|
||||
}?;
|
||||
|
||||
let p_hashes = parent_hashes_and_perms.as_ref().map(|v| &v.p_hashes[..]);
|
||||
let extra_perms = parent_hashes_and_perms
|
||||
.as_ref()
|
||||
.map(|v| v.perms.clone())
|
||||
.unwrap_or(json!({}));
|
||||
|
||||
let lock = if !(ns.language == ScriptLang::Python3
|
||||
|| ns.language == ScriptLang::Go
|
||||
|| ns.language == ScriptLang::Bun
|
||||
@@ -564,20 +460,23 @@ async fn create_script_internal<'c>(
|
||||
ns.lock
|
||||
.and_then(|e| if e.is_empty() { None } else { Some(e) })
|
||||
};
|
||||
|
||||
let needs_lock_gen = lock.is_none();
|
||||
|
||||
let envs = ns.envs.as_ref().map(|x| x.as_slice());
|
||||
let envs = if ns.envs.is_none() || ns.envs.as_ref().unwrap().is_empty() {
|
||||
None
|
||||
} else {
|
||||
envs
|
||||
};
|
||||
//::text::json is to ensure we use serde_json with preserve order
|
||||
sqlx::query!(
|
||||
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
|
||||
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
|
||||
draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)",
|
||||
delete_after_use, timeout, concurrency_key, visible_to_runner_only) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
ns.path,
|
||||
@@ -606,11 +505,10 @@ async fn create_script_internal<'c>(
|
||||
ns.timeout,
|
||||
ns.concurrency_key,
|
||||
ns.visible_to_runner_only,
|
||||
ns.no_main_func,
|
||||
codebase
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
let p_path_opt = parent_hashes_and_perms.as_ref().map(|x| x.p_path.clone());
|
||||
if let Some(ref p_path) = p_path_opt {
|
||||
sqlx::query!(
|
||||
@@ -648,7 +546,7 @@ async fn create_script_internal<'c>(
|
||||
clear_schedule(tx.transaction_mut(), &schedule.path, &w_id).await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule).await?;
|
||||
tx = push_scheduled_job(&db, tx, schedule).await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -660,6 +558,7 @@ async fn create_script_internal<'c>(
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if p_hashes.is_some() && !p_hashes.unwrap().is_empty() {
|
||||
audit_log(
|
||||
&mut tx,
|
||||
@@ -705,6 +604,7 @@ async fn create_script_internal<'c>(
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let permissioned_as = username_to_permissioned_as(&authed.username);
|
||||
if needs_lock_gen {
|
||||
let tag = if ns.dedicated_worker.is_some_and(|x| x) {
|
||||
@@ -753,7 +653,7 @@ async fn create_script_internal<'c>(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok((hash, new_tx))
|
||||
new_tx.commit().await?;
|
||||
} else {
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
@@ -770,8 +670,10 @@ async fn create_script_internal<'c>(
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
Ok((hash, tx))
|
||||
tx.commit().await?;
|
||||
}
|
||||
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
}
|
||||
|
||||
pub async fn get_hub_script_by_path(
|
||||
@@ -822,7 +724,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, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func 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, timeout, concurrency_key, visible_to_runner_only 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 \
|
||||
|
||||
@@ -21,17 +21,15 @@ use axum::{
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use mail_send::{mail_builder::MessageBuilder, SmtpClientBuilder};
|
||||
use serde::Deserialize;
|
||||
use tokio::time::timeout;
|
||||
use windmill_common::{
|
||||
error::{self, JsonResult, Result},
|
||||
error::{self, to_anyhow, JsonResult, Result},
|
||||
global_settings::{AUTOMATE_USERNAME_CREATION_SETTING, ENV_SETTINGS, HUB_BASE_URL_SETTING},
|
||||
server::Smtp,
|
||||
utils::send_email,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::error::to_anyhow;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
#[warn(unused_mut)]
|
||||
let r = Router::new()
|
||||
@@ -69,17 +67,34 @@ pub async fn test_email(
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let smtp = test_email.smtp;
|
||||
let to = test_email.to;
|
||||
|
||||
let client_timeout = Duration::from_secs(3);
|
||||
send_email(
|
||||
"Test email from Windmill",
|
||||
"Test email content",
|
||||
vec![to],
|
||||
smtp,
|
||||
Some(client_timeout),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut client = SmtpClientBuilder::new(smtp.host, smtp.port)
|
||||
.implicit_tls(smtp.tls_implicit.unwrap_or(false));
|
||||
if std::env::var("ACCEPT_INVALID_CERTS").is_ok() {
|
||||
client = client.allow_invalid_certs();
|
||||
}
|
||||
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())
|
||||
.subject("Test email from Windmill")
|
||||
.text_body("Test email content");
|
||||
let dur = Duration::from_secs(3);
|
||||
timeout(dur, client.connect())
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.map_err(to_anyhow)?
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
tracing::info!("Sent test email to {to}");
|
||||
Ok("Sent test email".to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-api/src/stripe_ee.rs
|
||||
@@ -0,0 +1,7 @@
|
||||
#[cfg(feature = "stripe")]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(feature = "stripe")]
|
||||
pub fn add_stripe_routes(router: Router) -> Router {
|
||||
return router;
|
||||
}
|
||||
@@ -32,6 +32,8 @@ use axum::{
|
||||
};
|
||||
use hyper::{header::LOCATION, StatusCode};
|
||||
use lazy_static::lazy_static;
|
||||
use mail_send::mail_builder::MessageBuilder;
|
||||
use mail_send::SmtpClientBuilder;
|
||||
use quick_cache::sync::Cache;
|
||||
use rand::rngs::OsRng;
|
||||
use regex::Regex;
|
||||
@@ -44,11 +46,10 @@ use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::global_settings::AUTOMATE_USERNAME_CREATION_SETTING;
|
||||
use windmill_common::users::truncate_token;
|
||||
use windmill_common::utils::send_email;
|
||||
use windmill_common::worker::{CLOUD_HOSTED, SERVER_CONFIG};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{self, Error, JsonResult, Result},
|
||||
error::{self, to_anyhow, Error, JsonResult, Result},
|
||||
users::SUPERADMIN_SECRET_EMAIL,
|
||||
utils::{not_found_if_none, rd_string, require_admin, Pagination, StripPath},
|
||||
};
|
||||
@@ -1336,25 +1337,6 @@ async fn accept_invite(
|
||||
.await?;
|
||||
|
||||
if let Some(r) = r {
|
||||
let already_in_workspace = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&nu.workspace_id,
|
||||
&email,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_in_workspace {
|
||||
tx.commit().await?;
|
||||
return Ok((
|
||||
StatusCode::CREATED,
|
||||
format!(
|
||||
"user {} accepted invite to workspace {}",
|
||||
&email, nu.workspace_id
|
||||
),
|
||||
));
|
||||
}
|
||||
let username;
|
||||
(tx, username) = add_user_to_workspace(
|
||||
&nu.workspace_id,
|
||||
@@ -1823,15 +1805,41 @@ pub fn send_email_if_possible(subject: &str, content: &str, to: &str) {
|
||||
let content = content.to_string();
|
||||
let to = to.to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = send_email_if_possible_intern(&subject, &content, to.clone()).await {
|
||||
tracing::error!("Failed to send email to {}: {}", to, e);
|
||||
if let Err(e) = send_email_if_possible_intern(&subject, &content, &to).await {
|
||||
tracing::error!("Failed to send email to {}: {}", &to, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn send_email_if_possible_intern(subject: &str, content: &str, to: String) -> Result<()> {
|
||||
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() {
|
||||
send_email(subject, content, vec![to], smtp, None).await?;
|
||||
let mut client = SmtpClientBuilder::new(smtp.host, smtp.port)
|
||||
.implicit_tls(smtp.tls_implicit.unwrap_or(false));
|
||||
if std::env::var("ACCEPT_INVALID_CERTS").is_ok() {
|
||||
client = client.allow_invalid_certs();
|
||||
}
|
||||
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)
|
||||
.subject(subject)
|
||||
.text_body(content);
|
||||
client
|
||||
.connect()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
tracing::info!("Sent email to {to}: {subject}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ impl WebhookShared {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<WebhookPayload>();
|
||||
let _process = tokio::spawn(async move {
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
// TODO: investigate pool timeouts and such if TCP load is high
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
|
||||
@@ -53,6 +53,7 @@ use windmill_common::{
|
||||
variables::ExportableListableVariable,
|
||||
};
|
||||
use windmill_git_sync::handle_deployment_metadata;
|
||||
use windmill_queue::QueueTransaction;
|
||||
|
||||
use crate::oauth2_ee::InstanceEvent;
|
||||
use crate::variables::{decrypt, encrypt};
|
||||
@@ -487,25 +488,25 @@ async fn run_slack_message_test_job(
|
||||
json!(format!("$res:{WORKSPACE_SLACK_BOT_TOKEN_PATH}")),
|
||||
);
|
||||
|
||||
let uuid = windmill_queue::push_error_handler(
|
||||
let tx: QueueTransaction<'_, _> = (rsmq.clone(), db.begin().await?).into();
|
||||
let (uuid, tx) = windmill_queue::handle_on_failure(
|
||||
&db,
|
||||
rsmq,
|
||||
tx,
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000000")?,
|
||||
None,
|
||||
Some("slack_message_test".to_string()),
|
||||
"slack_message_test",
|
||||
"slack_message_test",
|
||||
false,
|
||||
w_id.as_str(),
|
||||
&format!("script/{}", req.hub_script_path.as_str()),
|
||||
sqlx::types::Json(&fake_result),
|
||||
None,
|
||||
Some(Utc::now()),
|
||||
0,
|
||||
Utc::now(),
|
||||
Some(json!(extra_args)),
|
||||
authed.email.as_str(),
|
||||
false,
|
||||
false,
|
||||
None, // Note: we could mark it as high priority to return result quickly to the user
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(RunSlackMessageTestJobResponse {
|
||||
job_uuid: uuid.to_string(),
|
||||
@@ -721,16 +722,6 @@ async fn edit_auto_invite(
|
||||
|
||||
for user in users_to_auto_add.as_ref().unwrap() {
|
||||
auto_add_user(&user.email, &w_id, &operator, &mut tx).await?;
|
||||
send_email_if_possible(
|
||||
&format!("Added to Windmill's workspace: {w_id}"),
|
||||
&format!(
|
||||
"You have been granted access to Windmill's workspace {w_id} by {email}.
|
||||
|
||||
Access the workspace at {}/?workspace={w_id}",
|
||||
BASE_URL.read().await.clone()
|
||||
),
|
||||
&user.email,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sqlx::query!(
|
||||
@@ -1945,22 +1936,6 @@ async fn invite_user(
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let already_in_workspace = sqlx::query_scalar!(
|
||||
"SELECT EXISTS (SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&w_id,
|
||||
nu.email
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_in_workspace {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"user with email {} already exists in workspace {}",
|
||||
nu.email, w_id
|
||||
)));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_invite
|
||||
(workspace_id, email, is_admin, operator)
|
||||
@@ -2071,14 +2046,6 @@ async fn add_user(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2",
|
||||
&w_id,
|
||||
nu.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query_as!(
|
||||
Group,
|
||||
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
|
||||
@@ -2216,10 +2183,6 @@ struct ScriptMetadata {
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_main_func: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub codebase: Option<String>,
|
||||
}
|
||||
|
||||
pub fn is_none_or_false(val: &Option<bool>) -> bool {
|
||||
@@ -2496,8 +2459,6 @@ async fn tarball_workspace(
|
||||
delete_after_use: script.delete_after_use,
|
||||
restart_unless_cancelled: script.restart_unless_cancelled,
|
||||
visible_to_runner_only: script.visible_to_runner_only,
|
||||
no_main_func: script.no_main_func,
|
||||
codebase: script.codebase,
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
archive
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-audit/src/audit_ee.rs
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 windmill_common::{
|
||||
error::{Error, Result},
|
||||
utils::Pagination,
|
||||
};
|
||||
|
||||
use crate::{ActionKind, AuditLog, ListAuditLogQuery};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>(
|
||||
_db: E,
|
||||
_username: &str,
|
||||
mut _operation: &str,
|
||||
_action_kind: ActionKind,
|
||||
_w_id: &str,
|
||||
mut _resource: Option<&str>,
|
||||
_parameters: Option<HashMap<&str, &str>>,
|
||||
) -> Result<()> {
|
||||
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_audit(
|
||||
_tx: Transaction<'_, Postgres>,
|
||||
_w_id: String,
|
||||
_pagination: Pagination,
|
||||
_lq: ListAuditLogQuery,
|
||||
) -> Result<Vec<AuditLog>> {
|
||||
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
pub async fn get_audit(tx: Transaction<'_, Postgres>, _id: i32, _w_id: &str) -> Result<AuditLog> {
|
||||
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
|
||||
tx.commit().await?;
|
||||
Err(Error::NotFound(
|
||||
"Audit log not not available in Windmill Community edition".to_string(),
|
||||
))
|
||||
}
|
||||
@@ -10,7 +10,7 @@ enterprise = []
|
||||
prometheus = ["dep:prometheus"]
|
||||
flamegraph = ["dep:tracing-flame"]
|
||||
loki = ["dep:tracing-loki"]
|
||||
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts", "dep:bytes"]
|
||||
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"]
|
||||
|
||||
[lib]
|
||||
name = "windmill_common"
|
||||
@@ -46,6 +46,4 @@ object_store = { workspace = true, optional = true }
|
||||
prometheus = { workspace = true, optional = true }
|
||||
aws-config = { workspace = true, optional = true }
|
||||
aws-sdk-sts = { workspace = true, optional = true }
|
||||
indexmap.workspace = true
|
||||
bytes = { workspace = true, optional = true }
|
||||
mail-send.workspace = true
|
||||
indexmap.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-common/src/ee.rs
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::ee::LicensePlan::Community;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref LICENSE_KEY_VALID: Arc<RwLock<bool>> = Arc::new(RwLock::new(true));
|
||||
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 {
|
||||
Community,
|
||||
Pro,
|
||||
Enterprise,
|
||||
}
|
||||
|
||||
pub async fn get_license_plan() -> LicensePlan {
|
||||
// Implementation is not open source
|
||||
return Community;
|
||||
}
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::response::Response;
|
||||
use axum::{response::IntoResponse, response::Json};
|
||||
use axum::{
|
||||
response::IntoResponse,
|
||||
response::Json,
|
||||
};
|
||||
|
||||
use hyper::StatusCode;
|
||||
use sqlx::migrate::MigrateError;
|
||||
@@ -42,8 +45,6 @@ pub enum Error {
|
||||
SqlErr(#[from] sqlx::Error),
|
||||
#[error("Bad request: {0}")]
|
||||
BadRequest(String),
|
||||
#[error("Quota exceeded: {0}")]
|
||||
QuotaExceeded(String),
|
||||
#[error("Internal: {0}")]
|
||||
InternalErr(String),
|
||||
#[error("Hexadecimal decoding error: {0}")]
|
||||
@@ -58,8 +59,6 @@ pub enum Error {
|
||||
JsonErr(serde_json::Value),
|
||||
#[error("{0}")]
|
||||
OpenAIError(String),
|
||||
#[error("{0}")]
|
||||
AlreadyCompleted(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@@ -82,10 +81,9 @@ impl IntoResponse for Error {
|
||||
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(_)
|
||||
| Self::QuotaExceeded(_) => axum::http::StatusCode::BAD_REQUEST,
|
||||
Self::SqlErr(_) | Self::BadRequest(_) | Self::OpenAIError(_) => {
|
||||
axum::http::StatusCode::BAD_REQUEST
|
||||
}
|
||||
_ => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
|
||||
@@ -13,19 +13,16 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub async fn get_ip() -> anyhow::Result<String> {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = tokio::time::sleep(Duration::from_secs(10)) => {
|
||||
return Err(anyhow::anyhow!("Expected to get ip under 10s"))
|
||||
},
|
||||
ip = reqwest::ClientBuilder::new()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.timeout(Duration::from_secs(5))
|
||||
use reqwest::Result;
|
||||
|
||||
pub async fn get_ip() -> Result<String> {
|
||||
reqwest::ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()?
|
||||
.get("https://hub.windmill.dev/getip")
|
||||
.send() => Ok(ip?
|
||||
.error_for_status()?
|
||||
.text().await?),
|
||||
}
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.text()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn is_retry_default(v: &RetryStatus) -> bool {
|
||||
pub struct FlowStatus {
|
||||
pub step: i32,
|
||||
pub modules: Vec<FlowStatusModule>,
|
||||
pub failure_module: Box<FlowStatusModuleWParent>,
|
||||
pub failure_module: FlowStatusModuleWParent,
|
||||
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
#[serde(default)]
|
||||
@@ -227,7 +227,7 @@ impl FlowStatus {
|
||||
.iter()
|
||||
.map(|m| FlowStatusModule::WaitingForPriorSteps { id: m.id.clone() })
|
||||
.collect(),
|
||||
failure_module: Box::new(FlowStatusModuleWParent {
|
||||
failure_module: FlowStatusModuleWParent {
|
||||
parent_module: None,
|
||||
module_status: FlowStatusModule::WaitingForPriorSteps {
|
||||
id: f
|
||||
@@ -236,7 +236,7 @@ impl FlowStatus {
|
||||
.map(|x| x.id.clone())
|
||||
.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,
|
||||
|
||||
@@ -87,7 +87,7 @@ pub struct FlowValue {
|
||||
pub modules: Vec<FlowModule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub failure_module: Option<Box<FlowModule>>,
|
||||
pub failure_module: Option<FlowModule>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub same_worker: bool,
|
||||
|
||||
@@ -24,7 +24,6 @@ pub const OBJECT_STORE_CACHE_CONFIG_SETTING: &str = "object_store_cache_config";
|
||||
|
||||
pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation";
|
||||
pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
|
||||
|
||||
pub const ENV_SETTINGS: [&str; 50] = [
|
||||
"DISABLE_NSJAIL",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-common/src/job_s3_helpers_ee.rs
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
s3_helpers::{ObjectStoreResource, StorageResourceType},
|
||||
};
|
||||
|
||||
pub async fn get_s3_resource_internal<'c, F, Fut>(
|
||||
_resource_type: StorageResourceType,
|
||||
_s3_resource_value_raw: serde_json::Value,
|
||||
_gen_token: F,
|
||||
) -> crate::error::Result<ObjectStoreResource>
|
||||
where
|
||||
F: FnOnce(String) -> Fut,
|
||||
Fut: Future<Output = Result<String, Error>> + Send + 'static,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
@@ -243,8 +243,6 @@ pub struct CompletedJob {
|
||||
pub tag: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl CompletedJob {
|
||||
@@ -345,7 +343,6 @@ pub enum JobPayload {
|
||||
pub struct RawCode {
|
||||
pub content: String,
|
||||
pub path: Option<String>,
|
||||
pub hash: Option<i64>,
|
||||
pub language: ScriptLang,
|
||||
pub lock: Option<String>,
|
||||
pub concurrent_limit: Option<i32>,
|
||||
|
||||
@@ -11,8 +11,6 @@ use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use ee::CriticalErrorChannel;
|
||||
use error::Error;
|
||||
use scripts::ScriptLang;
|
||||
use sqlx::{Pool, Postgres};
|
||||
@@ -46,7 +44,7 @@ pub mod workspaces;
|
||||
pub mod tracing_init;
|
||||
|
||||
pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 4;
|
||||
|
||||
pub const DEFAULT_HUB_BASE_URL: &str = "https://hub.windmill.dev";
|
||||
|
||||
@@ -75,9 +73,6 @@ lazy_static::lazy_static! {
|
||||
pub static ref IS_READY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
pub static ref HUB_BASE_URL: Arc<RwLock<String>> = Arc::new(RwLock::new(DEFAULT_HUB_BASE_URL.to_string()));
|
||||
|
||||
|
||||
pub static ref CRITICAL_ERROR_CHANNELS: Arc<RwLock<Vec<CriticalErrorChannel>>> = Arc::new(RwLock::new(vec![]));
|
||||
}
|
||||
|
||||
pub async fn shutdown_signal(
|
||||
|
||||
@@ -177,48 +177,6 @@ pub fn build_object_store_client(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn attempt_fetch_bytes(
|
||||
client: Arc<dyn ObjectStore>,
|
||||
path: &str,
|
||||
) -> error::Result<bytes::Bytes> {
|
||||
use object_store::path::Path;
|
||||
|
||||
let object = client.get(&Path::from(path)).await;
|
||||
if let Err(e) = object {
|
||||
tracing::info!(
|
||||
"Failed to pull bytes from object store at path {path}. Error: {:?}",
|
||||
e
|
||||
);
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to pull bytes from object store: {path}"
|
||||
)));
|
||||
}
|
||||
|
||||
let bytes = object.unwrap().bytes().await;
|
||||
if bytes.is_err() {
|
||||
tracing::info!(
|
||||
"Failed to read bytes from object store: {path}. Error: {:?}",
|
||||
bytes.err()
|
||||
);
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to read bytes from object store: {path}"
|
||||
)));
|
||||
}
|
||||
let bytes = bytes.unwrap();
|
||||
|
||||
tracing::info!("{path} len: {}", bytes.len());
|
||||
|
||||
if bytes.len() == 0 {
|
||||
tracing::info!("object {path} not found in bucket, bytes empty",);
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"object {path} does not exist in bucket"
|
||||
)));
|
||||
}
|
||||
|
||||
return Ok(bytes);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use aws_config::{default_provider::credentials::DefaultCredentialsChain, Region};
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -450,7 +408,3 @@ impl CredentialProvider for AwsCredentialAdapter {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bundle(w_id: &str, hash: &str) -> String {
|
||||
format!("script_bundle/{}/{}", w_id, hash)
|
||||
}
|
||||
|
||||
@@ -128,8 +128,6 @@ impl Display for ScriptKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub const PREVIEW_IS_CODEBASE_HASH: i64 = -42;
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
pub struct Script {
|
||||
pub workspace_id: String,
|
||||
@@ -177,10 +175,6 @@ pub struct Script {
|
||||
pub concurrency_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_main_func: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub codebase: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
@@ -200,14 +194,6 @@ pub struct ListableScript {
|
||||
pub draft_only: Option<bool>,
|
||||
pub has_deploy_errors: bool,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_main_func: Option<bool>,
|
||||
#[serde(skip_serializing_if = "is_false")]
|
||||
pub use_codebase: bool,
|
||||
}
|
||||
|
||||
fn is_false(x: &bool) -> bool {
|
||||
return !x;
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -265,8 +251,6 @@ pub struct NewScript {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub concurrency_key: Option<String>,
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
pub no_main_func: Option<bool>,
|
||||
pub codebase: Option<String>,
|
||||
}
|
||||
|
||||
fn lock_deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
@@ -336,7 +320,6 @@ pub struct ListScriptQuery {
|
||||
pub is_template: Option<bool>,
|
||||
pub kinds: Option<String>,
|
||||
pub starred_only: Option<bool>,
|
||||
pub hide_without_main: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn to_i64(s: &str) -> crate::error::Result<i64> {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/git/windmill/../windmill-ee-private/windmill-common/src/stats_ee.rs
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::{error::Result, scripts::ScriptLang, utils::Mode, DB};
|
||||
|
||||
pub async fn get_disable_stats_setting(_db: &DB) -> bool {
|
||||
// stats details are closed source
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn schedule_stats(
|
||||
_instance_name: String,
|
||||
_mode: Mode,
|
||||
_db: &DB,
|
||||
_http_client: &reqwest::Client,
|
||||
_is_enterprise: bool,
|
||||
) -> () {
|
||||
// stats details are closed source
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow, serde::Serialize)]
|
||||
struct JobsUsage {
|
||||
language: Option<ScriptLang>,
|
||||
total_duration: i64,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
pub async fn send_stats(
|
||||
_instance_name: &String,
|
||||
_mode: &Mode,
|
||||
_http_client: &reqwest::Client,
|
||||
_db: &DB,
|
||||
_is_enterprise: bool,
|
||||
) -> Result<()> {
|
||||
// stats details are closed source
|
||||
Ok(())
|
||||
}
|
||||
@@ -53,13 +53,7 @@ pub fn initialize_tracing() {
|
||||
match json_fmt {
|
||||
true => ts_base.with(json_layer().flatten_event(true)).init(),
|
||||
false => ts_base
|
||||
.with(
|
||||
compact_layer()
|
||||
.with_ansi(style.to_lowercase() != "never")
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_target(false),
|
||||
)
|
||||
.with(compact_layer().with_ansi(style.to_lowercase() != "never"))
|
||||
.init(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,11 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee::trigger_critical_error_channels;
|
||||
use crate::ee::LICENSE_KEY_ID;
|
||||
use crate::error::{to_anyhow, Error, Result};
|
||||
use crate::global_settings::UNIQUE_ID_SETTING;
|
||||
use crate::server::Smtp;
|
||||
use crate::DB;
|
||||
use git_version::git_version;
|
||||
use mail_send::mail_builder::MessageBuilder;
|
||||
use mail_send::SmtpClientBuilder;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -90,20 +85,12 @@ pub async fn query_elems_from_hub(
|
||||
url: &str,
|
||||
query_params: Option<Vec<(&str, String)>>,
|
||||
db: &DB,
|
||||
) -> Result<(
|
||||
reqwest::StatusCode,
|
||||
reqwest::header::HeaderMap,
|
||||
axum::body::Body,
|
||||
)> {
|
||||
) -> Result<(reqwest::StatusCode, reqwest::header::HeaderMap, axum::body::Body)> {
|
||||
let response = http_get_from_hub(http_client, url, false, query_params, db).await?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
Ok((
|
||||
status,
|
||||
response.headers().clone(),
|
||||
axum::body::Body::from_stream(response.bytes_stream()),
|
||||
))
|
||||
Ok((status, response.headers().clone(), axum::body::Body::from_stream(response.bytes_stream())))
|
||||
}
|
||||
|
||||
pub async fn http_get_from_hub(
|
||||
@@ -180,61 +167,3 @@ pub enum Mode {
|
||||
Server,
|
||||
Standalone,
|
||||
}
|
||||
|
||||
pub async fn send_email(
|
||||
subject: &str,
|
||||
content: &str,
|
||||
to: Vec<String>,
|
||||
smtp: Smtp,
|
||||
client_timeout: Option<tokio::time::Duration>,
|
||||
) -> Result<()> {
|
||||
let mut client = SmtpClientBuilder::new(smtp.host, smtp.port)
|
||||
.implicit_tls(smtp.tls_implicit.unwrap_or(false));
|
||||
if std::env::var("ACCEPT_INVALID_CERTS").is_ok() {
|
||||
client = client.allow_invalid_certs();
|
||||
}
|
||||
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())
|
||||
.subject(subject)
|
||||
.text_body(content);
|
||||
|
||||
match client_timeout {
|
||||
Some(timeout) => {
|
||||
tokio::time::timeout(timeout, client.connect())
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.map_err(to_anyhow)?
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
}
|
||||
None => {
|
||||
client
|
||||
.connect()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
}
|
||||
}
|
||||
tracing::info!("Sent email to {:#?}: {subject}", to);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub async fn report_critical_error(error_message: String) -> () {
|
||||
tracing::error!("CRITICAL ERROR: {error_message}");
|
||||
#[cfg(feature = "enterprise")]
|
||||
trigger_critical_error_channels(error_message).await;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use magic_crypt::{MagicCrypt256, MagicCryptTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
|
||||
use crate::{worker::WORKER_GROUP, BASE_URL, DB};
|
||||
use crate::{BASE_URL, DB};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SECRET_SALT: Option<String> = std::env::var("SECRET_SALT").ok();
|
||||
@@ -300,12 +300,6 @@ pub async fn get_reserved_variables(
|
||||
description: "OIDC JWT token (EE only)".to_string(),
|
||||
is_custom: false,
|
||||
},
|
||||
ContextualVariable {
|
||||
name: "WM_WORKER_GROUP".to_string(),
|
||||
value: WORKER_GROUP.clone(),
|
||||
description: "name of the worker group the job is running on".to_string(),
|
||||
is_custom: false,
|
||||
},
|
||||
].into_iter().chain( sqlx::query_as::<_, (String, String)>(
|
||||
"SELECT name, value FROM workspace_env WHERE workspace_id = $1",
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user