Compare commits

..

125 Commits

Author SHA1 Message Date
pyranota
b272be9c4c feat(python, windows): enable S3 to cache wheels 2025-02-03 20:39:43 +03:00
pyranota
dfc7f123e0 Merge branch 'py-multiarch-s3-cache' of github.com:windmill-labs/windmill into py-multiarch-s3-cache 2025-02-03 20:23:04 +03:00
pyranota
0cb179dd29 remove unneeded tracing::error 2025-02-03 20:22:33 +03:00
pyranota
adfc2ba69b remove .minio-data from .gitignore 2025-02-03 20:15:47 +03:00
pyranota
e949227938 Merge branch 'main' into py-multiarch-s3-cache 2025-02-03 20:15:00 +03:00
pyranota
485f0842fb feat(python): make S3 cache arch specific
Current schema:

S3-Bucket
├── python_311
│   ├── wheel==1.0
│   └── wheel2==1.0
└── python_312
    ├── wheel==1.0
    └── wheel2==1.0

New schema:

S3-Bucket
├── linux_aarch64
│   └── ...
└── linux_x86_64
    ├── python_311
    │   ├── wheel==1.0
    │   └── wheel2==1.0
    └── python_312
        ├── wheel==1.0
        └── wheel2==1.0
2025-02-03 17:08:02 +03:00
Guilhem
8904ce04f8 trigger panels new item is collapsed by default
* fix null arg

* fix badge height jump

* Make new trigger collapsable

* keep new trigger section open when using capture

* fix spelling

* remove animation

* rename new trigger to + new

* nit
2025-02-03 14:28:38 +01:00
pyranota
4dbeae6ec8 add .minio-data to .gitignore 2025-02-03 16:11:13 +03:00
Guilhem
9f184ca4f2 fix(frontend) : fix history input arg null (#5194)
* fix null arg

* fix badge height jump
2025-02-03 09:34:43 +01:00
Ruben Fiszel
b0e3fda414 chore(main): release 1.456.0 (#5193)
* chore(main): release 1.456.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-02-01 14:49:46 +01:00
Ruben Fiszel
611d5e8bf3 feat: flow history picker for flow status + load last flow state 2025-02-01 14:38:05 +01:00
Ruben Fiszel
6d9edc8c2b fix: only start smtp servers if email domain is set 2025-02-01 09:26:02 +01:00
Ruben Fiszel
0c4901f802 nit fix hub cache init on server 2025-02-01 08:55:44 +01:00
Ruben Fiszel
f9e4e0a8d2 small ux nits for sidebar tabs 2025-02-01 08:39:15 +01:00
Ruben Fiszel
2478d29e8c nit error handling cache 2025-02-01 07:48:43 +01:00
Ruben Fiszel
c397e99adc chore(main): release 1.455.2 (#5188)
* chore(main): release 1.455.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-31 20:08:43 +01:00
Ruben Fiszel
2638dfcf00 fix: improve autoscaling num workers inc 2025-01-31 20:03:21 +01:00
pyranota
99b3308c56 Fix otel migration error (#5189)
* Fix otel migration error

    1: error returned from database: duplicate key value violates unique constraint "global_settings_pkey"
    0: while executing migration 20250131115248: error returned from database: duplicate key value violates unique constraint "global_settings_pkey"

* delete migration

* add .sqlx cache + move otel_global_settings to new location

* Fix compilation error

* Move before migrating

* Add error handler

* warn -> info

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-31 19:34:23 +01:00
Ruben Fiszel
0b0e5640d8 fix: improve history and saved inputs rever mechanism 2025-01-31 19:28:07 +01:00
Ruben Fiszel
e29a54022c Revert "fix: fix worker restart on otel setting set from undefined to null (#…" (#5190)
This reverts commit 940fb03023.
2025-01-31 18:50:33 +01:00
pyranota
940fb03023 fix: fix worker restart on otel setting set from undefined to null (#5183) 2025-01-31 17:14:44 +01:00
Ruben Fiszel
9b6e464c5a chore(main): release 1.455.1 (#5185)
* chore(main): release 1.455.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-31 14:42:53 +01:00
Ruben Fiszel
b74d3fd6e0 fix: pin malachite version 2025-01-31 14:39:18 +01:00
Ruben Fiszel
596969eac5 chore(main): release 1.455.0 (#5178)
* chore(main): release 1.455.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-31 13:56:40 +01:00
pyranota
e41fe928d8 chore: Remove shell.nix in favour of flakes (#5184) 2025-01-31 13:25:48 +01:00
Ruben Fiszel
73dbb8fb80 feat(cli): add --parallel option to push + display timings 2025-01-31 13:24:42 +01:00
Ruben Fiszel
dc00a13501 use sync decrypt 2025-01-31 10:31:19 +01:00
Ruben Fiszel
e726013369 Merge branch 'rf/restartConcurrencyLmits' 2025-01-31 09:26:49 +01:00
Ruben Fiszel
7956d3a7ed only mask passwords in more places 2025-01-31 09:26:29 +01:00
Ruben Fiszel
8a93418c25 fix: restart zombie job correctly handle concurrency limits (#5181)
* restart concurrency limits

* sqlx
2025-01-31 00:55:51 +01:00
Ruben Fiszel
587f16dced sqlx 2025-01-31 00:53:12 +01:00
Ruben Fiszel
b5703c8a37 restart concurrency limits 2025-01-31 00:39:40 +01:00
Alexander Petric
8ba0f3addd feat: duckdb connection settings with azure blob storage (#5180)
* feat: duckdb connection settings with azure blob storage

* fix main: remove unused import
2025-01-30 22:33:25 +01:00
Ruben Fiszel
60769e9ed3 improve cookie handling for cloud apps 2025-01-30 22:26:00 +01:00
Ruben Fiszel
997d3ffeb3 fix windmill.dev cookies 2025-01-30 21:39:46 +01:00
HugoCasa
ff3339dc4e fix: capture panel never stopping + http saved config (#5179) 2025-01-30 21:12:32 +01:00
Alexander Petric
8b65f1bc38 feat: polars connection settings with azure blob storage (#5175) 2025-01-30 19:54:35 +01:00
Ruben Fiszel
6c5a9238f0 chore(main): release 1.454.1 (#5176)
* chore(main): release 1.454.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-30 19:50:10 +01:00
Ruben Fiszel
b1155a9012 fix: fix codebase script execution 2025-01-30 19:06:55 +01:00
Ruben Fiszel
4296315995 chore(main): release 1.454.0 (#5174)
* chore(main): release 1.454.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-30 18:35:22 +01:00
Ruben Fiszel
4812c0cc9e fix: fix app form password handling 2025-01-30 18:31:11 +01:00
Ruben Fiszel
c8cc9d2941 fix: improve codebase support 2025-01-30 18:25:08 +01:00
Ruben Fiszel
0fdcce5d18 fix build 2025-01-30 17:03:34 +01:00
Ruben Fiszel
6fec134d3e cache input args 2025-01-30 16:41:50 +01:00
Guilhem
cd440564d2 feat: history for flow inputs (#5117)
* feat (frontend): improve capture UI

* show schema diff

* add accept reject form

* add diff for first node input

* move update schema button

* change modified properties display

* propagate change through nested components

* allow modification of nested object in schema

* reject nested changes

* clean logic

* clean code

* clean

* nit

* fix first strep input

* fix argument preview

* clean

* restore indentation for nested objects

* ignore undefined field in diff computation

* fix run button disabled update

* nit

* fix update JSON

* fix deletion of nested components

* dark mode

* clean

* replace dropdown with sidebar

* only check type for compatibility

* add shadow

* disbale dnd on edit

* auto-scroll within schema

* fix nested not dnd object diff viewer

* fix captures drawer

* open edit tab on add new arg

* change button label when schema is the same

* ajust padding

* fix arg update

* fix oneof display

* propagate change event through nested schema

* handle oneOf

* fix preview arg sync

* handle s3 object

* fix arg sync

* update schema input compatibility

* clean compatible

* accept empty array items

* clean

* increase of schema args gap

* fix nested schema update

* allow number and int compatibility

* reset args when modifying schema

* open fields on add with addPropertyV2

* feat(frontend): use new saved inputs picker

* add object viewer to saved inputs row

* Display alert info on select saved or historic input

* Allow args edit when using saved/historic inputs

* replace input library drawers with panels

* add captures and saved inputs to arg picker

* polish ui

* nit

* clean

* add json arg editor

* fix escape for deselect input

* bind schema form valid

* nit

* handle big payloads

* nit

* nit

* validity check disabled in json view

* improve infinit list request

* fix json schema inferrence

* nit

* nit

* nit

* nit

* fix tipo

* handle large captures

* fix cancel button shift

* use sideTabBar has a trigger

* nit

* fix everything

* fix

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-30 16:30:56 +01:00
Arjun Dhawan
343ff94f3c add bigquery to the list of python depenency exceptions (#5173) 2025-01-30 13:12:34 +01:00
Ruben Fiszel
90ebbff3f4 chore(main): release 1.453.1 (#5172)
* chore(main): release 1.453.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-30 12:24:18 +01:00
Ruben Fiszel
d03c92e246 fix typescript-client 3 2025-01-30 12:20:40 +01:00
Ruben Fiszel
6e8037255f fix typescript-client 2 2025-01-30 12:19:31 +01:00
Ruben Fiszel
7f516f0868 fix(typescript-client): fix typescript client mocked api behavior 2025-01-30 12:16:14 +01:00
Ruben Fiszel
512ca0d549 fix nsjail build 2025-01-30 09:20:23 +01:00
Alexander Petric
03ad0385ad improve audit aprovals (#5161)
* update audit log to track aproval and cancellation of job

* approval to resource field

* audit resource as json

* refactor aproval audit logs

* moving audit logs to worker_flow

* striping u/ from auditor name

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-30 09:13:44 +01:00
Alexander Petric
f29ee67d9d fix(frontend): fetching job result (db schema) separatly if too big (#5171) 2025-01-30 09:12:53 +01:00
Ruben Fiszel
24a853b8da fix nsjail build 2025-01-30 09:07:36 +01:00
Ruben Fiszel
c1304523d4 fix workflow dispatch and bench 2025-01-30 08:24:39 +01:00
Ruben Fiszel
46458a5cef chore(main): release 1.453.0 (#5158)
* chore(main): release 1.453.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-29 22:24:26 +01:00
HugoCasa
487c273bfb feat: websocket trigger allow returning messages (#5168)
* feat: websocket trigger allow returning messages

* handle early return
2025-01-29 21:17:36 +01:00
Ruben Fiszel
cfcd84be3f remove verify from normal ci 2025-01-29 19:35:33 +01:00
pyranota
c60f8dacf1 feat(python): add custom_wheels directory to PYTHONPATH (#5169)
* fix(python): fix uv can't find ssl certificates

- Add `PY_NATIVE_CERT` flag, forces UV to use native tls
- Rename `PIP_INDEX_CERT` to `PY_INDEX_CERT`
- Rename `PIP_TRUSTED_HOST` to `PY_TRUSTED_HOST`

For backwards compatibility PIP* variables are still accessible

* feat(python): add `custom_wheels` directory to PYTHONPATH

Add global directory by path `<CACHE_DIR>/python_xyz/custom_wheels`

For example for scripts running python 3.11, in every execution
`<CACHE_DIR>/python_311/custom_wheels` will be accessible and all wheels placed there could be imported and used.

This is usefull for preinstalling wheels before runtime

* Make it work with Nsjail

* Rework and make custom_wheels optional

* Remove `create_dir_all` from imports

* Use sync version of metadata

* Rename `custom_wheels` to `global-site-packages`
2025-01-29 19:13:47 +01:00
HugoCasa
5d8260e90e add single capture endpoint (#5170) 2025-01-29 18:13:10 +01:00
HugoCasa
eaa74d5582 team plan >80% included usage warning (#5163) 2025-01-29 18:09:47 +01:00
wendrul
5df05d5f0f nix: Update flake for C# and oracle + add flake for building parsers (#5167) 2025-01-29 18:06:53 +01:00
Ruben Fiszel
f3ef5105d3 clear cookies for cloud windmill.dev subdomains 2025-01-29 17:38:06 +01:00
pyranota
680fb1852a fix(python): fix uv can't find ssl certificates (#5157)
- Add `PY_NATIVE_CERT` flag, forces UV to use native tls
- Rename `PIP_INDEX_CERT` to `PY_INDEX_CERT`
- Rename `PIP_TRUSTED_HOST` to `PY_TRUSTED_HOST`

For backwards compatibility PIP* variables are still accessible
2025-01-29 15:07:00 +01:00
wendrul
6bfaa8c034 add missing TARGETPLATFORM arg on dockerfile (#5166) 2025-01-28 19:40:21 +01:00
dieriba
ac3bdf753c delete button now remove table to track (#5164) 2025-01-28 18:53:14 +01:00
wendrul
a1579c1654 fix: oracledb tag for native workers + client not working on arm systems (#5162)
* New migration for oracledb

* fix oracledb on ARM64 systems
2025-01-28 17:57:40 +01:00
HugoCasa
31f61ab7a2 cache caddy data in docker compose (#5159) 2025-01-28 16:08:17 +01:00
HugoCasa
5544d42a2d feat: custom response headers (#5156) 2025-01-28 15:06:20 +01:00
Ruben Fiszel
0b65cb23a4 nit build 2025-01-28 14:29:35 +01:00
Ruben Fiszel
5ec180f443 add nsjail dispatch 2025-01-28 13:27:58 +01:00
Ruben Fiszel
f8506259ad add nsjail dispatch 2025-01-28 13:13:12 +01:00
Ruben Fiszel
6682b3daa9 nit 2025-01-28 07:36:26 +01:00
Ruben Fiszel
94848d42de chore(main): release 1.452.1 (#5153)
* chore(main): release 1.452.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-28 00:19:17 +01:00
Ruben Fiszel
eaf633ce76 fix: fix broken tabs for apps 2025-01-28 00:16:21 +01:00
Ruben Fiszel
bf14434cdb chore(main): release 1.452.0 (#5152)
* chore(main): release 1.452.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-27 23:51:26 +01:00
Alexander Petric
9fb92c8fc6 update audit log to track aproval and cancellation of job (#5151)
* update audit log to track aproval and cancellation of job

* approval to resource field

* audit resource as json

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-27 23:50:20 +01:00
Ruben Fiszel
8daec06e20 improve oauth postMessage resiliency 2025-01-27 23:47:47 +01:00
dieriba
e0b6aa42a2 feat(postgres, trigger): support ssl (#5149)
* fix

* feat: support-ssl

* Update backend/windmill-api/src/postgres_triggers/trigger.rs

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: match error

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-01-27 23:11:19 +01:00
Alexander Petric
fdb712234e feat: mssql executor supports azure db redirect (#5150)
* feat: mssql executor supports azure db redirect

* notes

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-27 22:45:31 +01:00
HugoCasa
d17397b070 feat: test trigger connection (#5145)
* feat: test trigger connection

* fix build

* fix build

* update ee ref

* nit

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-27 22:40:51 +01:00
Ruben Fiszel
86e8f2a4fb fix compile 2025-01-27 22:32:45 +01:00
Ruben Fiszel
38199a2e7e fix compile 2025-01-27 22:10:46 +01:00
Ruben Fiszel
bf23ddd0a5 fix compile 2025-01-27 22:04:04 +01:00
Ruben Fiszel
e3d6aef109 fix compile 2025-01-27 22:00:48 +01:00
Ruben Fiszel
806d3ad0e7 remove lightweightschemaform 2025-01-27 21:35:46 +01:00
Alexander Petric
a0c5957065 some reactivity improvements and missing dims for tabcomponents (#5146) 2025-01-27 18:24:22 +01:00
Ruben Fiszel
23e20d01ad chore(main): release 1.451.0 (#5143)
* chore(main): release 1.451.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-27 17:54:59 +01:00
Guilhem
b8152a3259 fix(frontend): make argInput indent optional (#5144) 2025-01-27 17:25:41 +01:00
Alexander Petric
0e41023889 feat: app builder accordion list and accordion tab component (#5132)
* feat: tabs component accordion

* accordion tabs

* accordionlistcomponent

* types

* Update AppTabs.svelte

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-27 15:47:58 +01:00
Ruben Fiszel
2800903c9a change windmill-ee-full error message 2025-01-27 15:47:23 +01:00
Ruben Fiszel
da918bee83 change windmill-ee-full error message 2025-01-27 15:41:43 +01:00
Ruben Fiszel
f9ce01c1dc feat(typescript-client): add ability to mock typescript api for testing 2025-01-27 15:29:39 +01:00
Ruben Fiszel
5b9700d454 remove py312 image building 2025-01-27 11:19:01 +01:00
Ruben Fiszel
b435a5bc4f chore(main): release 1.450.1 (#5142)
* chore(main): release 1.450.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-27 00:31:06 +01:00
Ruben Fiszel
697a060e3e fix: fix SCIM/SAML settings persistence 2025-01-27 00:26:43 +01:00
dieriba
2bed97bbcc nits: show error and documentation link, add better name (#5141) 2025-01-26 22:09:49 +01:00
Ruben Fiszel
119d7ba77f remove deno-client 2025-01-26 16:05:26 +01:00
Ruben Fiszel
7567b2199b chore(main): release 1.450.0 (#5140)
* chore(main): release 1.450.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-26 11:26:03 +01:00
Ruben Fiszel
b47e9c14f2 feat(cli): improve codebase support + remove --stateful + warn when pushing stale metadata (#5139) 2025-01-26 11:04:35 +01:00
Ruben Fiszel
789c2ec4da chore(main): release 1.449.3 (#5138)
* chore(main): release 1.449.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-25 22:31:39 +01:00
Ruben Fiszel
f29c70279f fix: update tests to rust 1.83.0 2025-01-25 22:18:53 +01:00
dieriba
27a25d4af9 fix: include postgres triggers in build #5137 2025-01-25 18:00:12 +01:00
Ruben Fiszel
928ff002a1 chore(main): release 1.449.2 (#5136)
* chore(main): release 1.449.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-25 15:00:15 +01:00
Ruben Fiszel
5381c76b7d fix: update rust to 1.83.0 (#5135) 2025-01-25 14:24:59 +01:00
Ruben Fiszel
1ac63641b9 chore(main): release 1.449.1 (#5134)
* chore(main): release 1.449.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-25 12:49:38 +01:00
dieriba
411ad1b190 nit triggers 2025-01-25 12:48:29 +01:00
pyranota
2f1804b6d0 fix: Fix python refetching wheels from S3 (#5133) 2025-01-25 12:38:32 +01:00
Henri Courdent
ecf2222f18 Changelogs frontend update (#5129) 2025-01-24 19:33:12 +01:00
Ruben Fiszel
7084d2e61a chore(main): release 1.449.0 (#5122)
* chore(main): release 1.449.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-24 18:21:21 +01:00
Alexander Petric
1a1ea68216 feat(frontend): operator visibility setting per workspace (#5124)
* feat(frontend): operator visibility setting per workspace

* section + reactivity

* sqlx prep

* npm check

* more sqlx

* fix table bg dark mode

* improve db query / reactivity

* sqlx prepare

* more merge fixing

* npm check

* npm check
2025-01-24 18:17:00 +01:00
dieriba
1eaef8542b fix pg trigger display (#5127) 2025-01-24 17:07:48 +01:00
pyranota
74bb660b97 fix(bun, windows): Fix "A required privilege is not held by the client." (#5126) 2025-01-24 15:32:29 +01:00
dieriba
316a2167e7 feat: postgres triggers (#4860)
* feat: init database triggers

* feat:  wip: database_triggers

* feat:  add database triggers front view

* feat: 🚧 database_triggers

* feat: 🚧 add definition in yaml, updated backend code and added migration

* feat: 🚧 updated migration file, update openapi.yml, updated database_triggers page and backend function

* fix: struct rust

* feat: 🚧 update migrate, database trigger backend function fixed

* feat: 🚧 add resource picker front, update backend function

* feat: 🚧 edit inner database inner, update triggers

* feat: 🚧 database_triggers

* feat: 🚧 update openapi yaml, prettied websocker trigger

* feat: 🚧 database_triggers

* feat: 🚧 add resource module, update variable file, working on main loop for database_trigger

* feat: 🐛 working sqlx query

* feat: 🚧 fix query with sqlx, added main loop

* feat: 🚧 add new column database_trigg

* feat: 🛂 run jobs works

* feat: 🚧 handling slot name and replication

* feat: restructring triggers, decoding trigger message on work

* feat: 🚧 database_trigger

* feat: 🚧

* feat: 🚧 converter done, work on custom script

* feat: 🚧 multiple trigger

* feat: 🚧 adding new argument function

* feat: 🚧 database_trigger

* feat: 🚧 add generate template for front, update script picker

* feat: 🚧 template script fix bug, work on restructing backend logic

* feat: 🚧 update autogenerated script, add persistence state for template script

* feat: 🚧 update structure client

* feat: 🚧 rewrited crud function

* feat: 🚧 added publication handler

* feat: 🚧 new ui finished

* feat: 🚧 added slot function hanlder finish front ux

* feat:  ux improvement done, backend logic done

* feat: 🐛 fix where clause

* feat:

* feat:

* chore: update .sqlx and remove empty package

* fix: update publication and remove unneccessary print

* feat: finish converter to json, remove save button, remove unused crate

* feat: update migration for database trigger, fixed query, fixed frontend error with missing feature on back

* chore: update .sqlx

* chore: update sqlx

* chore: .sqlx

* chore: add unused

* fix: use right database resource in back and front

* fix build

* refactor:

* fix sqlx

* nits: retrieve all script from database trigger editor

* fix: template script object, fix route name

* merge main

* feat: rework ux

* feat/finish ux and update backend logic

* feat: database_trigger

* feat: database_trigger

* chore: update sqlx

* chore: fix ci

* nits: fix worker.rs

* feat: database_trigger

* Update frontend/src/lib/components/triggers/database/DatabaseTriggersPanel.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat:

* fix ci

* nits: database to postgres

* nits: fix .sqlx

* feat: add link to original code and open tab functionallity

* nits: update .sqlx

* nits: fix request type and yaml file

* nits: add abel suggestion

* fix: update .sqlx

* fix: update .sqlx

* nits: add comment to hex.rs

* nits: remove features, rename database to postgres name

* nits: replace database name by postgres

* nits: database name to postgres

* nits: update .sqlx

* nits: update .sqlx

* nit

* nit

* nits

* nit

* nits

* nits

* nits

* nits

* fix sqlx

* nits: update .vscode

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-24 15:12:41 +01:00
pyranota
e47dd697f9 feat(python): Multiple runtime versions (#4579)
* feat: Handle `pip install` by `uv`

Dirty and untested, but already something working

* Integrate with NSJAIL and prepare fallbacks

* Refactor fallback
no_uv disable compile and install
where no_uv_install and no_uv_compile are a bit more specific

* Remove `--disable-pip-version-check`
Reason:
   warning: pip's `--disable-pip-version-check` has no effect

* Fix backend compilation error

* Pip fallback overwrite UV's cache

* Initially refactor cache (No S3)

* Support S3

* Remove unused import

* Handle flags for NSJAIL

* Return deleted flag

* Remove verbose mode and enable link-mode=copy

* Granural migration of lockfiles

Before i realized we dont need it :)

* Initial draft (not-working)

* Add fallback

* Fix bug preventing uv from installing deps

'\n' - Love it

* Add verbosity indicator

* Iterate on feature
- Added instance python version
- Rework logic

* Fix EE build error
error[E0599]: no method named `iter` found for tuple `(PyVersion, std::vec::Vec<std::string::String>)` in the current scope

* Support S3

* Support NSJAIL

* Refactor `get_python`

* Make NSJAIL work [Unsafe]

config file missed /proc mount causing install phase to fail

* Trigger CI

* Clean up

* Make Actions build it

* Trigger CI #2

* Update Dockerfile and clean up

* Change fallbacks
now there is only no_uv and NOUV

* Expose INSTANCE_PYTHON_VERSION through env variable

* Change namings

* Include py-version to requirements.in

Also add comments and make code much cleaner

* Use const for python installation dir

It was hardcoded before

* Pin preinstalled version

* Update python_executor.rs

* Up to date branch

* Create PYCACHE dirs

TODO: PY_TAR_DIRS

* Fix after merge

* Make it safer

* Implement USE_SYSTEM_PYTHON

* Implement latest_stable option

* Load INSTANCE_PYTHON_VERSION on startup

* Check for multiple annotations used

* Fix Latest Stable button not pressed if selected

* Proper error handling for conflict on multiple annotations

* Fix merge conflicts

* Preinstall 3.11 and Latest Stable

* Preinstall latest stable in non-blocking manner

* Fix Warning

* Gate preinstall logic behind "python" feature

* Handle raw_deps properly

* Make it work with nsjail

* Revert docker-image.yml

* Revert Dockerfile

* Cleanup + Fixing

* Add windows support

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-24 11:42:48 +01:00
Ruben Fiszel
6776b2b162 pin cargo-chef 2025-01-24 00:21:54 +01:00
Alexander Petric
a88fbb238a feat: msft teams support for critical alerts (#5113)
* feat: msft teams support for critical alerts

* ee changes

* ee

* sqlx prep

* multiple teams channels

* commit file, not symlink

* improve reactivity

* docs link

* Update ee-repo-ref.txt
2025-01-23 19:57:13 +01:00
HugoCasa
e5308553cb export simple editor to windmill-components (#5120) 2025-01-23 16:13:48 +01:00
Ruben Fiszel
9cc3dd3c5a update monaco-languageclient & wrapper to 6.1.1 2025-01-23 14:29:31 +01:00
Ruben Fiszel
ff72f3a9a1 chore(main): release 1.448.1 (#5118)
* chore(main): release 1.448.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-22 18:35:51 +01:00
Ruben Fiszel
737e664792 fix: improve environment variables config 2025-01-22 18:19:37 +01:00
Ruben Fiszel
047c16acd4 chore(main): release 1.448.0 (#5111)
* chore(main): release 1.448.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-01-22 14:15:32 +01:00
HugoCasa
a8fa75a5c3 fix memory limits reading on fly.io/firecracker (#5114) 2025-01-22 12:53:38 +01:00
Guilhem
59bcce7a96 feat (frontend): improve capture UI (#5051)
* feat (frontend): improve capture UI

* show schema diff

* add accept reject form

* add diff for first node input

* move update schema button

* change modified properties display

* propagate change through nested components

* allow modification of nested object in schema

* reject nested changes

* clean logic

* clean code

* clean

* nit

* fix first strep input

* fix argument preview

* clean

* restore indentation for nested objects

* ignore undefined field in diff computation

* fix run button disabled update

* nit

* fix update JSON

* fix deletion of nested components

* dark mode

* clean

* replace dropdown with sidebar

* only check type for compatibility

* add shadow

* disbale dnd on edit

* auto-scroll within schema

* fix nested not dnd object diff viewer

* fix captures drawer

* open edit tab on add new arg

* change button label when schema is the same

* ajust padding

* fix arg update

* fix oneof display

* propagate change event through nested schema

* handle oneOf

* fix preview arg sync

* handle s3 object

* fix arg sync

* update schema input compatibility

* clean compatible

* accept empty array items

* clean

* increase of schema args gap

* fix nested schema update

* allow number and int compatibility

* reset args when modifying schema

* open fields on add with addPropertyV2

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-22 12:51:14 +01:00
pyranota
fcfad69195 feat: Migrate to bun.lock (#5112)
* bun: Migrate to bun.lock

(In backwards compatible way)

Read more: https://bun.sh/blog/bun-lock-text-lockfile

* Clean up

* More clean up

* Mount bun.lock to jailed process
2025-01-22 12:32:17 +01:00
329 changed files with 15649 additions and 8493 deletions

2
.envrc
View File

@@ -1 +1 @@
use nix
use flake

View File

@@ -2,8 +2,8 @@ name: Backend check
on:
push:
paths:
- "backend/**"
- ".github/workflows/backend-check.yml"
- "backend/**"
- ".github/workflows/backend-check.yml"
jobs:
check_oss:
@@ -16,7 +16,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.82.0
toolchain: 1.83.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend
@@ -32,16 +32,15 @@ jobs:
with:
fetch-depth: 0
- name: install xmlsec1
run: |
sudo apt-get update
sudo apt-get install -y libxml2-dev libxmlsec1-dev
sudo apt-get update
sudo apt-get install -y libxml2-dev libxmlsec1-dev
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.82.0
toolchain: 1.83.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend
@@ -71,16 +70,14 @@ jobs:
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
- name: Substitute EE code (EE logic is behind feature flag)
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.82.0
toolchain: 1.83.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend
@@ -110,8 +107,8 @@ jobs:
- name: install xmlsec1
run: |
sudo apt-get update
sudo apt-get install -y libxml2-dev libxmlsec1-dev
sudo apt-get update
sudo apt-get install -y libxml2-dev libxmlsec1-dev
- name: Substitute EE code (EE logic is behind feature flag)
run: |
@@ -120,7 +117,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.82.0
toolchain: 1.83.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend
@@ -130,5 +127,3 @@ jobs:
run: |
mkdir -p fake_frontend_build
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features

View File

@@ -54,7 +54,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.82.0
toolchain: 1.83.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: backend

View File

@@ -35,7 +35,7 @@ jobs:
with:
deno-version: v1.x
- name: benchmark
timeout-minutes: 20
timeout-minutes: 30
run: deno run --unstable -A -r
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
-c

View File

@@ -64,7 +64,7 @@ jobs:
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -81,7 +81,7 @@ jobs:
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}

View File

@@ -45,7 +45,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
- name: Rename binary with corresponding architecture
run: |

View File

@@ -1,48 +0,0 @@
name: Publish deno-client
on:
push:
tags:
- "v*"
env:
repo: windmill-deno-client
jobs:
build_deno_and_push_to_repo:
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
- name: generate_deno
run: |
cd deno-client
rm .gitignore
./build.sh
- name: Pushes to another repository
id: push_directory
uses: cpina/github-action-push-to-another-repository@devel
env:
API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }}
with:
source-directory: deno-client/
destination-github-username: ${{ github.repository_owner }}
destination-repository-name: ${{ env.repo }}
user-email: ruben@windmill.dev
commit-message: See ORIGIN_COMMIT from $GITHUB_REF
target-branch: main
tag_repo:
needs: [build_deno_and_push_to_repo]
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/${{ env.repo }}
token: ${{ secrets.DENO_PAT }}
path: ./client
- name: Push client
run: |
cd ./client
git config --global user.email "ruben@windmill.dev"
git config --global user.name "rubenfiszel[bot]"
git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}"
git push --tags

View File

@@ -67,7 +67,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}

View File

@@ -26,7 +26,11 @@ on:
description: "Tag the image"
required: true
default: "test"
nsjail:
description: "Build nsjail image (true, false)"
required: false
default: false
type: boolean
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
@@ -42,6 +46,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Read EE repo commit hash
@@ -90,7 +95,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
@@ -100,10 +105,11 @@ jobs:
build_ee:
runs-on: ubicloud
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee)
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Read EE repo commit hash
@@ -152,7 +158,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
@@ -160,68 +166,6 @@ jobs:
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
build_ee_312:
runs-on: ubicloud
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ env.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public-py312
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-py312
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Substitute EE code
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Build and push publicly ee
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
PYTHON_IMAGE=python:3.12.2-slim-bookworm
tags: |
${{ steps.meta-ee-public-py312.outputs.tags }}
labels: |
${{ steps.meta-ee-public-py312.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
# disabled until we make it 100% reliable and add more meaningful tests
# playwright:
# runs-on: [self-hosted, new]
@@ -263,7 +207,6 @@ jobs:
ARCH: amd64
steps:
- uses: actions/checkout@v4
- run: |
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
@@ -349,6 +292,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Prepare test run
if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
@@ -372,7 +316,7 @@ jobs:
needs: [run_integration_test, build]
if:
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/tags/v'))
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
steps:
- uses: actions/checkout@v4
with:
@@ -392,7 +336,7 @@ jobs:
runs-on: ubicloud
needs: [run_integration_test, build_ee]
if:
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch') && (github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/tags/v'))
steps:
- uses: actions/checkout@v4
@@ -412,7 +356,7 @@ jobs:
verify_ee_image_vulnerabilities:
runs-on: ubicloud
needs: [tag_latest_ee]
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -455,12 +399,13 @@ jobs:
needs: [build_ee]
runs-on: ubicloud
if:
(github.event_name != 'pull_request') && (github.event_name !=
'workflow_dispatch')
(github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref }}
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
@@ -489,6 +434,11 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Update Dockerfile image reference
run: |
sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail
cat ./docker/DockerfileNsjail | grep "FROM"
- name: Build and push publicly ee
uses: depot/build-push-action@v1
with:
@@ -505,7 +455,7 @@ jobs:
build_ee_reports_privately:
needs: [build_ee_nsjail]
runs-on: ubicloud
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch')
steps:
- uses: actions/checkout@v4
with:
@@ -600,6 +550,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref }}
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2

View File

@@ -47,7 +47,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
- name: Rename binary with corresponding architecture
run: |

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ CaddyfileRemoteMalo
*.swp
**/.idea/
.direnv
.vscode
.vscode

View File

@@ -1,5 +1,191 @@
# Changelog
## [1.456.0](https://github.com/windmill-labs/windmill/compare/v1.455.2...v1.456.0) (2025-02-01)
### Features
* flow history picker for flow status + load last flow state ([611d5e8](https://github.com/windmill-labs/windmill/commit/611d5e8bf3d64106c3e9698687d2ec3710988517))
### Bug Fixes
* only start smtp servers if email domain is set ([6d9edc8](https://github.com/windmill-labs/windmill/commit/6d9edc8c2b01c99339c156dc84def13b0a2205a6))
## [1.455.2](https://github.com/windmill-labs/windmill/compare/v1.455.1...v1.455.2) (2025-01-31)
### Bug Fixes
* fix worker restart on otel setting set from undefined to null ([#5183](https://github.com/windmill-labs/windmill/issues/5183)) ([940fb03](https://github.com/windmill-labs/windmill/commit/940fb030232ea7944db94bfdf8b30a96f30baa21))
* improve autoscaling num workers inc ([2638dfc](https://github.com/windmill-labs/windmill/commit/2638dfcf00522af733c49b2c02a78ec4028623e0))
* improve history and saved inputs rever mechanism ([0b0e564](https://github.com/windmill-labs/windmill/commit/0b0e5640d8fa9682ad0a4349c863b743321be1f4))
## [1.455.1](https://github.com/windmill-labs/windmill/compare/v1.455.0...v1.455.1) (2025-01-31)
### Bug Fixes
* pin malachite version ([b74d3fd](https://github.com/windmill-labs/windmill/commit/b74d3fd6e08ea991f7a7360b79d54a45a8ee58be))
## [1.455.0](https://github.com/windmill-labs/windmill/compare/v1.454.1...v1.455.0) (2025-01-31)
### Features
* **cli:** add --parallel option to push + display timings ([73dbb8f](https://github.com/windmill-labs/windmill/commit/73dbb8fb80f56942d35e5b5cda9715bba5d5c98f))
* duckdb connection settings with azure blob storage ([#5180](https://github.com/windmill-labs/windmill/issues/5180)) ([8ba0f3a](https://github.com/windmill-labs/windmill/commit/8ba0f3addd0a02dba87c30739b44f524019a5e5a))
* polars connection settings with azure blob storage ([#5175](https://github.com/windmill-labs/windmill/issues/5175)) ([8b65f1b](https://github.com/windmill-labs/windmill/commit/8b65f1bc3829d6b02bf38bf1449d3061ef62a08c))
### Bug Fixes
* capture panel never stopping + http saved config ([#5179](https://github.com/windmill-labs/windmill/issues/5179)) ([ff3339d](https://github.com/windmill-labs/windmill/commit/ff3339dc4e9ea4641b847fa5ef51bedcf6a3a429))
* restart zombie job correctly handle concurrency limits ([#5181](https://github.com/windmill-labs/windmill/issues/5181)) ([8a93418](https://github.com/windmill-labs/windmill/commit/8a93418c25d180859bda76b9218e750e4b407fed))
## [1.454.1](https://github.com/windmill-labs/windmill/compare/v1.454.0...v1.454.1) (2025-01-30)
### Bug Fixes
* fix codebase script execution ([b1155a9](https://github.com/windmill-labs/windmill/commit/b1155a9012c726c1b1c3f1a62f3aa96b8504681c))
## [1.454.0](https://github.com/windmill-labs/windmill/compare/v1.453.1...v1.454.0) (2025-01-30)
### Features
* history for flow inputs ([#5117](https://github.com/windmill-labs/windmill/issues/5117)) ([cd44056](https://github.com/windmill-labs/windmill/commit/cd440564d29aff39f6dc30ecbf0055696b07cf12))
### Bug Fixes
* fix app form password handling ([4812c0c](https://github.com/windmill-labs/windmill/commit/4812c0cc9e46fdb5944f7010efdf617f2fd23cff))
* improve codebase support ([c8cc9d2](https://github.com/windmill-labs/windmill/commit/c8cc9d2941dda2fedb959e1156c4f9b4f7dfab43))
## [1.453.1](https://github.com/windmill-labs/windmill/compare/v1.453.0...v1.453.1) (2025-01-30)
### Bug Fixes
* **frontend:** fetching job result (db schema) separatly if too big ([#5171](https://github.com/windmill-labs/windmill/issues/5171)) ([f29ee67](https://github.com/windmill-labs/windmill/commit/f29ee67d9d8547d7c4a79e14698b7d88a722e730))
* **typescript-client:** fix typescript client mocked api behavior ([7f516f0](https://github.com/windmill-labs/windmill/commit/7f516f08682876f5a7ca409ec002cba6ec0c5c5b))
## [1.453.0](https://github.com/windmill-labs/windmill/compare/v1.452.1...v1.453.0) (2025-01-29)
### Features
* custom response headers ([#5156](https://github.com/windmill-labs/windmill/issues/5156)) ([5544d42](https://github.com/windmill-labs/windmill/commit/5544d42a2db652660051b5f2e05f8db0102400fa))
* **python:** add `custom_wheels` directory to `PYTHONPATH` ([#5169](https://github.com/windmill-labs/windmill/issues/5169)) ([c60f8da](https://github.com/windmill-labs/windmill/commit/c60f8dacf191c5255ef8fca462d6d3d571b86b07))
* websocket trigger allow returning messages ([#5168](https://github.com/windmill-labs/windmill/issues/5168)) ([487c273](https://github.com/windmill-labs/windmill/commit/487c273bfbc5bf1731947c922f32cd026cca47f9))
### Bug Fixes
* oracledb tag for native workers + client not working on arm systems ([#5162](https://github.com/windmill-labs/windmill/issues/5162)) ([a1579c1](https://github.com/windmill-labs/windmill/commit/a1579c1654a2ddf9f6fc8f05f62b37bb09303752))
* **python:** fix uv can't find ssl certificates ([#5157](https://github.com/windmill-labs/windmill/issues/5157)) ([680fb18](https://github.com/windmill-labs/windmill/commit/680fb1852a192e384e23e2f5ceea473383f9f5aa))
## [1.452.1](https://github.com/windmill-labs/windmill/compare/v1.452.0...v1.452.1) (2025-01-27)
### Bug Fixes
* fix broken tabs for apps ([eaf633c](https://github.com/windmill-labs/windmill/commit/eaf633ce7675bf86108ebfc81ed735c26031ad71))
## [1.452.0](https://github.com/windmill-labs/windmill/compare/v1.451.0...v1.452.0) (2025-01-27)
### Features
* mssql executor supports azure db redirect ([#5150](https://github.com/windmill-labs/windmill/issues/5150)) ([fdb7122](https://github.com/windmill-labs/windmill/commit/fdb712234eb7f1a9374012d51e1cb25ee5aaa410))
* **postgres, trigger:** support ssl ([#5149](https://github.com/windmill-labs/windmill/issues/5149)) ([e0b6aa4](https://github.com/windmill-labs/windmill/commit/e0b6aa42a28a4273f3a7bb7465396f2756e60a92))
* test trigger connection ([#5145](https://github.com/windmill-labs/windmill/issues/5145)) ([d17397b](https://github.com/windmill-labs/windmill/commit/d17397b0703fb6a7dabf9db75d3c30ec26cdbe1e))
## [1.451.0](https://github.com/windmill-labs/windmill/compare/v1.450.1...v1.451.0) (2025-01-27)
### Features
* app builder accordion list and accordion tab component ([#5132](https://github.com/windmill-labs/windmill/issues/5132)) ([0e41023](https://github.com/windmill-labs/windmill/commit/0e41023889fb2f26ef5dc2a736dd59b819d92bf1))
* **typescript-client:** add ability to mock typescript api for testing ([f9ce01c](https://github.com/windmill-labs/windmill/commit/f9ce01c1dcda581d943e3cf58008cc6c89d39c5f))
### Bug Fixes
* **frontend:** make argInput indent optional ([#5144](https://github.com/windmill-labs/windmill/issues/5144)) ([b8152a3](https://github.com/windmill-labs/windmill/commit/b8152a3259fa0f791990e41d825f196d82c4084d))
## [1.450.1](https://github.com/windmill-labs/windmill/compare/v1.450.0...v1.450.1) (2025-01-26)
### Bug Fixes
* fix SCIM/SAML settings persistence ([697a060](https://github.com/windmill-labs/windmill/commit/697a060e3e01a183b6ac2371eee319b01ee891cf))
## [1.450.0](https://github.com/windmill-labs/windmill/compare/v1.449.3...v1.450.0) (2025-01-26)
### Features
* **cli:** improve codebase support + remove --stateful + warn when pushing stale metadata ([#5139](https://github.com/windmill-labs/windmill/issues/5139)) ([b47e9c1](https://github.com/windmill-labs/windmill/commit/b47e9c14f277339ae51fd3a40e00e8ba4e8a1390))
## [1.449.3](https://github.com/windmill-labs/windmill/compare/v1.449.2...v1.449.3) (2025-01-25)
### Bug Fixes
* include postgres triggers in build [#5137](https://github.com/windmill-labs/windmill/issues/5137) ([27a25d4](https://github.com/windmill-labs/windmill/commit/27a25d4af9ca0f0e0ed1d06a462fdd7cf966ce2b))
* update tests to rust 1.83.0 ([f29c702](https://github.com/windmill-labs/windmill/commit/f29c70279f14c2cdbf6e330ae266060f81f32bb6))
## [1.449.2](https://github.com/windmill-labs/windmill/compare/v1.449.1...v1.449.2) (2025-01-25)
### Bug Fixes
* update rust to 1.83.0 ([#5135](https://github.com/windmill-labs/windmill/issues/5135)) ([5381c76](https://github.com/windmill-labs/windmill/commit/5381c76b7d194d75f58d70b29aa3f5f8589fa899))
## [1.449.1](https://github.com/windmill-labs/windmill/compare/v1.449.0...v1.449.1) (2025-01-25)
### Bug Fixes
* Fix python refetching wheels from S3 ([#5133](https://github.com/windmill-labs/windmill/issues/5133)) ([2f1804b](https://github.com/windmill-labs/windmill/commit/2f1804b6d0e6f3492608967f8937d5e34481661b))
## [1.449.0](https://github.com/windmill-labs/windmill/compare/v1.448.1...v1.449.0) (2025-01-24)
### Features
* **frontend:** operator visibility setting per workspace ([#5124](https://github.com/windmill-labs/windmill/issues/5124)) ([1a1ea68](https://github.com/windmill-labs/windmill/commit/1a1ea682166af62133d3df2816490e3ebc92462f))
* msft teams support for critical alerts ([#5113](https://github.com/windmill-labs/windmill/issues/5113)) ([a88fbb2](https://github.com/windmill-labs/windmill/commit/a88fbb238ae21ca53bccd53ccfd3c77e2946fda2))
* postgres triggers ([#4860](https://github.com/windmill-labs/windmill/issues/4860)) ([316a216](https://github.com/windmill-labs/windmill/commit/316a2167e7648052afb3d7456c5ab9324348e56c))
* **python:** Multiple runtime versions ([#4579](https://github.com/windmill-labs/windmill/issues/4579)) ([e47dd69](https://github.com/windmill-labs/windmill/commit/e47dd697f96883f8f97bf469e69d3344f12d9633))
### Bug Fixes
* **bun, windows:** Fix "A required privilege is not held by the client." ([#5126](https://github.com/windmill-labs/windmill/issues/5126)) ([74bb660](https://github.com/windmill-labs/windmill/commit/74bb660b9799e79e31c33d96a4f06d77094287d5))
## [1.448.1](https://github.com/windmill-labs/windmill/compare/v1.448.0...v1.448.1) (2025-01-22)
### Bug Fixes
* improve environment variables config ([737e664](https://github.com/windmill-labs/windmill/commit/737e6647926bfad88ef28ddbfe91aa56d58569b8))
## [1.448.0](https://github.com/windmill-labs/windmill/compare/v1.447.6...v1.448.0) (2025-01-22)
### Features
* Migrate to bun.lock ([#5112](https://github.com/windmill-labs/windmill/issues/5112)) ([fcfad69](https://github.com/windmill-labs/windmill/commit/fcfad69195a776d4958c035dca16100368966a66))
### Bug Fixes
* improve workspace specific tags behavior ([669a1ff](https://github.com/windmill-labs/windmill/commit/669a1ff8e6ab782ce1168170db684deb5f0ec99a))
* improve workspace specific tags behavior ([e81c2c9](https://github.com/windmill-labs/windmill/commit/e81c2c983d139cd24e983f6a76de56c10a694954))
## [1.447.6](https://github.com/windmill-labs/windmill/compare/v1.447.5...v1.447.6) (2025-01-21)

View File

@@ -1,5 +1,5 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.82-slim-bookworm
ARG RUST_IMAGE=rust:1.83-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm
FROM ${RUST_IMAGE} AS rust_base
@@ -12,7 +12,7 @@ RUN apt-get -y update \
RUN rustup component add rustfmt
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version ^0.1
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version 0.1.68
RUN cargo install sccache --version ^0.8
ENV RUSTC_WRAPPER=sccache SCCACHE_DIR=/backend/sccache
@@ -95,6 +95,14 @@ ARG WITH_KUBECTL=true
ARG WITH_HELM=true
ARG WITH_GIT=true
# To change latest stable version:
# 1. Change placeholder in instanceSettings.ts
# 2. Change LATEST_STABLE_PY in dockerfile
# 3. Change #[default] annotation for PyVersion in backend
ARG LATEST_STABLE_PY=3.11.10
ENV UV_PYTHON_INSTALL_DIR=/tmp/windmill/cache/py_runtime
ENV UV_PYTHON_PREFERENCE=only-managed
RUN pip install --upgrade pip==24.2
RUN apt-get update \
@@ -161,6 +169,10 @@ ENV GO_PATH=/usr/local/go/bin/go
# Install UV
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.15/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
# Preinstall python runtimes
RUN uv python install 3.11.10
RUN uv python install $LATEST_STABLE_PY
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get -y update && apt-get install -y curl procps nodejs awscli && apt-get clean \
&& rm -rf /var/lib/apt/lists/*

3
backend/.gitignore vendored
View File

@@ -5,5 +5,4 @@ oauth2.json
tracing.folded
heaptrack*
index/
windmill-api/openapi-*.*
.docker.nu/
windmill-api/openapi-*.*

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE postgres_trigger \n SET \n server_id = $1, \n last_server_ping = now(),\n error = 'Connecting...'\n WHERE \n enabled IS TRUE \n AND workspace_id = $2 \n AND path = $3 \n AND (last_server_ping IS NULL \n OR last_server_ping < now() - INTERVAL '15 seconds'\n ) \n RETURNING true\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "01e92a4ba3074f1dce6ec98bc6c3fad4878f48db8c17c6d58590bd5df2e3350a"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, expires_at = $7",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Int4",
"Bool",
"Timestamptz"
]
},
"nullable": []
},
"hash": "08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO postgres_trigger (\n publication_name,\n replication_slot_name,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n postgres_resource_path, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7, \n $8, \n $9, \n $10\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Bool",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "124b27de35b49fbdb13a1f772044665a84325e34ae04bf2795fafb7bb6f2f0c6"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "color",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "operator_settings",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -33,8 +38,9 @@
false,
false,
false,
true
true,
null
]
},
"hash": "6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736"
"hash": "1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14"
}

View File

@@ -127,6 +127,11 @@
"ordinal": 24,
"name": "color",
"type_info": "Varchar"
},
{
"ordinal": 25,
"name": "operator_settings",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -159,6 +164,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n schemaname AS schema_name,\n tablename AS table_name,\n attnames AS columns,\n rowfilter AS where_clause\n FROM\n pg_publication_tables\n WHERE\n pubname = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "schema_name",
"type_info": "Name"
},
{
"ordinal": 1,
"name": "table_name",
"type_info": "Name"
},
{
"ordinal": 2,
"name": "columns",
"type_info": "NameArray"
},
{
"ordinal": 3,
"name": "where_clause",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
true,
true,
true,
true
]
},
"hash": "199a76c04e3f0891ad09af27b9534bbabdd8703bfdf4d43df2c65e50d4ca2c85"
}

View File

@@ -1,40 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\"",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "websocket_used!",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "http_routes_used!",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "kafka_used!",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "nats_used!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3"
}

View File

@@ -0,0 +1,46 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\", \n \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\"\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "websocket_used!",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "http_routes_used!",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "kafka_used!",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "nats_used!",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "postgres_used!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null,
null,
null,
null
]
},
"hash": "24178c21aadc1aed90f31e9362c6505a642c8f04b883c278b07e7ef5956ce121"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SHOW WAL_LEVEL;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "wal_level",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT pubname AS publication_name FROM pg_publication;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "publication_name",
"type_info": "Name"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45"
}

View File

@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, is_secret, expires_at FROM variable WHERE path = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_secret",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "expires_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
true
]
},
"hash": "44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b"
}

View File

@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n slot_name,\n active\n FROM\n pg_replication_slots \n WHERE \n plugin = 'pgoutput' AND\n slot_type = 'logical';\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "slot_name",
"type_info": "Name"
},
{
"ordinal": 1,
"name": "active",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
true,
true
]
},
"hash": "4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13"
}

View File

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

View File

@@ -127,6 +127,11 @@
"ordinal": 24,
"name": "color",
"type_info": "Varchar"
},
{
"ordinal": 25,
"name": "operator_settings",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -159,6 +164,7 @@
true,
true,
true,
true,
true
]
},

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as \"payload!: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
"describe": {
"columns": [
{
@@ -34,7 +34,7 @@
},
{
"ordinal": 3,
"name": "payload: _",
"name": "payload!: _",
"type_info": "Jsonb"
},
{
@@ -71,9 +71,9 @@
false,
false,
false,
false,
null,
true
]
},
"hash": "e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4"
"hash": "5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $10 AND path = $11",
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $11 AND path = $12",
"describe": {
"columns": [],
"parameters": {
@@ -14,11 +14,12 @@
"Jsonb",
"Varchar",
"Varchar",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915"
"hash": "63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "select path from script where hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79"
}

View File

@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "bool",
"name": "?column?",
"type_info": "Bool"
}
],

View File

@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n postgres_trigger\n SET \n last_server_ping = now(),\n error = $1\n WHERE\n workspace_id = $2\n AND path = $3\n AND server_id = $4 \n AND enabled IS TRUE\n RETURNING 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "833a4ecec12dfe67f28016a135ffe682b023d1868a182b7cac16ce799433c257"
}

View File

@@ -1,34 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "last_ping",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
true
]
},
"hash": "84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value FROM global_settings WHERE name = 'teams'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf"
}

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n puballtables AS all_table,\n pubinsert AS insert,\n pubupdate AS update,\n pubdelete AS delete\n FROM\n pg_publication\n WHERE\n pubname = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "all_table",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "insert",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "update",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "delete",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM postgres_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "8e1afb488096330890b1675d2b3052d2064fcc8f373fecfebd40914768b2b1cf"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE postgres_trigger \n SET \n script_path = $1, \n path = $2, \n is_flow = $3, \n edited_by = $4, \n email = $5, \n postgres_resource_path = $6, \n replication_slot_name = $7,\n publication_name = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "946977f0d525abf6267bf02e7a887434abd3e213b8c3c488166ca58fe3321147"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n postgres_trigger \n SET\n last_server_ping = NULL \n WHERE \n workspace_id = $1 \n AND path = $2 \n AND server_id IS NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "95e420b60fba20b36b2c6675998587d8cad3b67d4dfa9de52777d4ea9490b6b7"
}

View File

@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "WITH zombie_jobs AS (\n UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false \n RETURNING id, workspace_id, last_ping\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id, workspace_id, last_ping FROM zombie_jobs",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "last_ping",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
true
]
},
"hash": "9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value)\n VALUES ('teams', $1)\n ON CONFLICT (name) DO UPDATE SET value = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb"
]
},
"nullable": []
},
"hash": "9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14"
}

View File

@@ -0,0 +1,104 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n workspace_id,\n path,\n script_path,\n replication_slot_name,\n publication_name,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n postgres_resource_path\n FROM\n postgres_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "replication_slot_name",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "publication_name",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 10,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 11,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 12,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 13,
"name": "enabled",
"type_info": "Bool"
},
{
"ordinal": 14,
"name": "postgres_resource_path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false,
false
]
},
"hash": "9cb31818d4db8a0e294884ab3dec08bfc262f99c875bf16c25bfb5e987efe978"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, is_secret\n FROM variable\n WHERE path = $1 AND workspace_id = $2",
"query": "SELECT value, is_secret \n FROM variable \n WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -25,5 +25,5 @@
false
]
},
"hash": "f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9"
"hash": "a5a03b9235b25bca359235f2e546197f02ca1cf898d7f2686419749b1cb0679e"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE postgres_trigger SET enabled = FALSE, error = $1, server_id = NULL, last_server_ping = NULL WHERE workspace_id = $2 AND path = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "a6c168c60bc8c42f70b18565e824efe29311aabfba6e09efa10bab6a551d658b"
}

View File

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

View File

@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "bool",
"name": "?column?",
"type_info": "Bool"
}
],

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase AS \"codebase: String\" FROM script WHERE hash = $1 LIMIT 1",
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1",
"describe": {
"columns": [
{
@@ -52,8 +52,8 @@
},
{
"ordinal": 4,
"name": "codebase: String",
"type_info": "Varchar"
"name": "use_tar",
"type_info": "Bool"
}
],
"parameters": {
@@ -66,8 +66,8 @@
true,
false,
true,
true
null
]
},
"hash": "0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664"
"hash": "d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799"
}

View File

@@ -0,0 +1,61 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload!: _\", trigger_extra as \"trigger_extra: _\" FROM capture WHERE id = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 2,
"name": "trigger_kind: _",
"type_info": {
"Custom": {
"name": "trigger_kind",
"kind": {
"Enum": [
"webhook",
"http",
"websocket",
"kafka",
"email",
"nats"
]
}
}
}
},
{
"ordinal": 3,
"name": "payload!: _",
"type_info": "Jsonb"
},
{
"ordinal": 4,
"name": "trigger_extra: _",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
true
]
},
"hash": "e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT flow_version.value->>'early_return' as early_return\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "early_return",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1"
}

View File

@@ -0,0 +1,107 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n replication_slot_name,\n publication_name,\n postgres_resource_path\n FROM \n postgres_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 10,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 11,
"name": "enabled",
"type_info": "Bool"
},
{
"ordinal": 12,
"name": "replication_slot_name",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "publication_name",
"type_info": "Varchar"
},
{
"ordinal": 14,
"name": "postgres_resource_path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false,
false,
false,
false
]
},
"hash": "fcc11a9353ea101109aec30f8bdd4b2ce906fffc3c51e77d083121dbd68dadd4"
}

View File

@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE postgres_trigger \n SET \n enabled = $1, \n email = $2, \n edited_by = $3, \n edited_at = now(), \n server_id = NULL, \n error = NULL\n WHERE \n path = $4 AND \n workspace_id = $5 \n RETURNING 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Bool",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "fd1db7530acf3c84b2ab696504905a50d1ed4f69629c43de7d874769c340d909"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM _sqlx_migrations WHERE version=20250131115248",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "fe58ca3cef78c73867d309156df2b74f7127bf6cba25c9a0c5ca5eedd6a19aff"
}

View File

@@ -1,5 +1,15 @@
{
"python.analysis.typeCheckingMode": "basic",
"rust-analyzer.linkedProjects": ["./windmill-common/Cargo.toml"],
"rust-analyzer.showUnlinkedFileNotification": false
"rust-analyzer.showUnlinkedFileNotification": false,
"remote.portsAttributes": {
"8000": {
"label": "Development Server",
"onAutoForward": "openPreview"
}
},
"remote.autoForwardPorts": true,
"conventionalCommits.scopes": [
"restructring triggers, decoding trigger message on work"
],
}

523
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.447.6"
version = "1.456.0"
authors.workspace = true
edition.workspace = true
@@ -30,7 +30,7 @@ members = [
]
[workspace.package]
version = "1.447.6"
version = "1.456.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -61,7 +61,7 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud"]
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "enterprise", "parquet"]
sqlx = ["windmill-worker/sqlx"]
deno_core = ["windmill-worker/deno_core", "dep:deno_core"]
deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"]
kafka = ["windmill-api/kafka"]
nats = ["windmill-api/nats"]
otel = ["windmill-common/otel", "windmill-worker/otel"]
@@ -73,6 +73,7 @@ oracledb = ["windmill-worker/oracledb"]
mssql = ["windmill-worker/mssql"]
bigquery = ["windmill-worker/bigquery"]
websocket = ["windmill-api/websocket"]
postgres_trigger = ["windmill-api/postgres_trigger"]
python = ["windmill-worker/python"]
smtp = ["windmill-api/smtp", "windmill-common/smtp"]
csharp = ["windmill-worker/csharp"]
@@ -114,6 +115,9 @@ serde.workspace = true
deno_core = { workspace = true, optional = true }
object_store = { workspace = true, optional = true }
quote.workspace = true
memchr.workspace = true
v8 = { workspace = true, optional = true }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { optional = true, workspace = true }
@@ -130,6 +134,7 @@ windmill-api-client.workspace = true
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] }
[workspace.dependencies]
windmill-api = { path = "./windmill-api", default-features = false }
windmill-queue = { path = "./windmill-queue" }
@@ -154,6 +159,8 @@ windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
windmill-api-client = { path = "./windmill-api-client" }
v8 = "=130.0.7" # Exact version
memchr = "2.7.4"
axum = { version = "^0.7", features = ["multipart"] }
headers = "^0"
hyper = { version = "^1", features = ["full"] }
@@ -184,6 +191,8 @@ rand = "^0"
rand_core = { version = "^0", features = ["std"] }
magic-crypt = "^3"
git-version = "^0"
malachite = "=0.4.18"
malachite-bigint = "=0.2.0"
rustpython-parser = "^0"
php-parser-rs = { git = "https://github.com/php-rust-tools/parser", rev = "ec4cb411dec09450946ef57920b7ffced7f6495d" }
cron = "^0"
@@ -234,7 +243,7 @@ sqlx = { version = "0.8.0", features = [
"runtime-tokio-rustls",
"bigdecimal"
] }
bigdecimal = "^0"
bigdecimal = {version = "^0"}
dotenv = "^0"
ulid = { version = "^1", features = ["uuid"] }
futures = "^0"
@@ -260,6 +269,8 @@ wasm-bindgen-test = "0.3.42"
convert_case = "0.6.0"
getrandom = "0.2"
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
rust-postgres = { package = "tokio-postgres", git = "https://github.com/imor/rust-postgres", rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b"}
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/imor/rust-postgres", features = ["runtime"], rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b" }
bit-vec = "=0.6.3"
mappable-rc = "^0"
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]}
@@ -269,7 +280,7 @@ native-tls = "^0"
# samael = { git="https://github.com/njaremko/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = ["xmlsec"] }
samael = { version="0.0.14", features = ["xmlsec"] }
gcp_auth = "0.9.0"
rust_decimal = { version = "^1", features = ["db-postgres"]}
rust_decimal = { version = "^1", features = ["db-postgres", "serde-float"]}
jsonwebtoken = "8.3.0"
pem = "3.0.1"
nix = { version = "0.27.1", features = ["process", "signal"] }
@@ -287,6 +298,7 @@ openssl = "=0.10"
mail-parser = "^0"
matchit = "=0.7.3"
rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored"] }
pg_escape = "0.1.1"
async-nats = "0.38.0"
nkeys = "0.4.4"
@@ -311,6 +323,7 @@ opentelemetry-semantic-conventions = { version = "*", features = ["semconv_exper
bollard = "0.18.1"
tonic = { version = "^0", features = ["tls-native-roots"] }
byteorder = "1.5.0"
tikv-jemallocator = { version = "0.5" }
tikv-jemalloc-sys = { version = "^0.5" }

View File

@@ -1 +1 @@
ddcfdfc18a9833a5fc4e62ad62a265ef1e06a0aa
15ddca63acd769e4e7355269a239639c50989def

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS postgres_trigger;

View File

@@ -0,0 +1,69 @@
-- Add up migration script here
CREATE TABLE postgres_trigger(
path VARCHAR(255) NOT NULL,
script_path VARCHAR(255) NOT NULL,
is_flow BOOLEAN NOT NULL,
workspace_id VARCHAR(50) NOT NULL,
edited_by VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL,
edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
extra_perms JSONB NULL,
postgres_resource_path VARCHAR(255) NOT NULL,
error TEXT NULL,
server_id VARCHAR(50) NULL,
last_server_ping TIMESTAMPTZ NULL,
replication_slot_name VARCHAR(255) NOT NULL,
publication_name VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL,
CONSTRAINT PK_postgres_trigger PRIMARY KEY (path,workspace_id),
CONSTRAINT fk_postgres_trigger_workspace FOREIGN KEY (workspace_id)
REFERENCES workspace(id) ON DELETE CASCADE
);
GRANT ALL ON postgres_trigger TO windmill_user;
GRANT ALL ON postgres_trigger TO windmill_admin;
ALTER TABLE postgres_trigger ENABLE ROW LEVEL SECURITY;
CREATE POLICY admin_policy ON postgres_trigger FOR ALL TO windmill_admin USING (true);
CREATE POLICY see_folder_extra_perms_user_select ON postgres_trigger FOR SELECT TO windmill_user
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_insert ON postgres_trigger FOR INSERT TO windmill_user
WITH CHECK (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_update ON postgres_trigger FOR UPDATE TO windmill_user
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_delete ON postgres_trigger FOR DELETE TO windmill_user
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'f' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_own ON postgres_trigger FOR ALL TO windmill_user
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'u' AND SPLIT_PART(postgres_trigger.path, '/', 2) = current_setting('session.user'));
CREATE POLICY see_member ON postgres_trigger FOR ALL TO windmill_user
USING (SPLIT_PART(postgres_trigger.path, '/', 1) = 'g' AND SPLIT_PART(postgres_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
CREATE POLICY see_extra_perms_user_select ON postgres_trigger FOR SELECT TO windmill_user
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
CREATE POLICY see_extra_perms_user_insert ON postgres_trigger FOR INSERT TO windmill_user
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_user_update ON postgres_trigger FOR UPDATE TO windmill_user
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_user_delete ON postgres_trigger FOR DELETE TO windmill_user
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_groups_select ON postgres_trigger FOR SELECT TO windmill_user
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
CREATE POLICY see_extra_perms_groups_insert ON postgres_trigger FOR INSERT TO windmill_user
WITH CHECK (exists(
SELECT key, value FROM jsonb_each_text(extra_perms)
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND value::boolean));
CREATE POLICY see_extra_perms_groups_update ON postgres_trigger FOR UPDATE TO windmill_user
USING (exists(
SELECT key, value FROM jsonb_each_text(extra_perms)
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND value::boolean));
CREATE POLICY see_extra_perms_groups_delete ON postgres_trigger FOR DELETE TO windmill_user
USING (exists(
SELECT key, value FROM jsonb_each_text(extra_perms)
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND value::boolean));

View File

@@ -0,0 +1 @@
DELETE FROM global_settings WHERE name = 'teams';

View File

@@ -0,0 +1 @@
INSERT INTO global_settings (name, value) VALUES ('teams', '{}');

View File

@@ -0,0 +1 @@
ALTER TABLE workspace_settings DROP COLUMN operator_settings;

View File

@@ -0,0 +1,11 @@
ALTER TABLE workspace_settings ADD COLUMN operator_settings JSONB DEFAULT '{
"runs": true,
"groups": true,
"folders": true,
"workers": true,
"triggers": true,
"resources": true,
"schedules": true,
"variables": true,
"audit_logs": true
}';

View File

@@ -0,0 +1 @@
ALTER TABLE cloud_workspace_settings DROP COLUMN last_warning_sent;

View File

@@ -0,0 +1 @@
ALTER TABLE cloud_workspace_settings ADD COLUMN last_warning_sent TIMESTAMP NULL;

View File

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

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["oracledb"]'::jsonb) where name = 'worker__native' and config @> '{"worker_tags": ["nativets", "postgresql", "mysql", "graphql", "snowflake", "bigquery", "mssql"]}'::jsonb AND NOT config->'worker_tags' @> '"oracledb"'::jsonb;

View File

@@ -0,0 +1 @@
ALTER TABLE websocket_trigger DROP COLUMN can_return_message;

View File

@@ -0,0 +1 @@
ALTER TABLE websocket_trigger ADD COLUMN can_return_message BOOLEAN NOT NULL DEFAULT FALSE;

View File

@@ -0,0 +1 @@
DELETE FROM global_settings WHERE name = 'otel';

View File

@@ -0,0 +1,3 @@
INSERT INTO global_settings (name, value)
VALUES ('otel', '{}')
ON CONFLICT (name) DO NOTHING;

View File

@@ -1,11 +1,11 @@
use std::collections::BTreeMap;
use std::sync::{Mutex, OnceLock};
use std::{
alloc::{self, Layout},
ffi::{c_char, c_int, c_void},
mem::align_of,
ptr,
};
use std::collections::BTreeMap;
use std::sync::{Mutex, OnceLock};
use wasm_bindgen::prelude::*;
/* -------------------------------- stdlib.h -------------------------------- */
@@ -71,7 +71,6 @@ pub unsafe extern "C" fn free(buf: *mut c_void) {
alloc::dealloc(buf, layout);
}
// In all these allocations, we store the layout before the data for later retrieval.
// This is because we need to know the layout when deallocating the memory.
// Here are some helper methods for that:

View File

@@ -18,6 +18,8 @@ regex.workspace = true
windmill-parser.workspace = true
windmill-common.workspace = true
rustpython-parser.workspace = true
malachite.workspace = true
malachite-bigint.workspace = true
phf.workspace = true
itertools.workspace = true
serde_json.workspace = true

View File

@@ -21,7 +21,7 @@ use rustpython_parser::{
Parse,
};
use sqlx::{Pool, Postgres};
use windmill_common::error;
use windmill_common::{error, worker::PythonAnnotations};
const DEF_MAIN: &str = "def main(";
@@ -37,6 +37,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
"smb" => "pysmb",
"PIL" => "Pillow",
"googleapiclient" => "google-api-python-client",
"googlecloudbigquery" => "google-cloud-bigquery",
"dateutil" => "python-dateutil",
"mailparser" => "mail-parser",
"mailparser-reply" => "mail-parser-reply",
@@ -171,14 +172,75 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<String>>
return Ok(nimports);
}
#[async_recursion]
pub async fn parse_python_imports(
code: &str,
w_id: &str,
path: &str,
db: &Pool<Postgres>,
already_visited: &mut Vec<String>,
annotated_pyv_numeric: &mut Option<u32>,
) -> error::Result<Vec<String>> {
parse_python_imports_inner(
code,
w_id,
path,
db,
already_visited,
annotated_pyv_numeric,
&mut annotated_pyv_numeric.and_then(|_| Some(path.to_owned())),
)
.await
}
#[async_recursion]
async fn parse_python_imports_inner(
code: &str,
w_id: &str,
path: &str,
db: &Pool<Postgres>,
already_visited: &mut Vec<String>,
annotated_pyv_numeric: &mut Option<u32>,
path_where_annotated_pyv: &mut Option<String>,
) -> error::Result<Vec<String>> {
let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code);
// we pass only if there is none or only one annotation
// Naive:
// 1. Check if there are multiple annotated version
// 2. If no, take one and compare with annotated version
// 3. We continue if same or replace none with new one
// Optimized:
// 1. Iterate over all annotations compare each with annotated_pyv and replace on flight
// 2. If annotated_pyv is different version, throw and error
// This way we make sure there is no multiple annotations for same script
// and we get detailed span on conflicting versions
let mut check = |is_py_xyz, numeric| -> error::Result<()> {
if is_py_xyz {
if let Some(v) = annotated_pyv_numeric {
if *v != numeric {
return Err(error::Error::from(anyhow::anyhow!(
"Annotated 2 or more different python versions: \n - py{v} at {}\n - py{numeric} at {path}\nIt is possible to use only one.",
path_where_annotated_pyv.clone().unwrap_or("Unknown".to_owned())
)));
}
} else {
*annotated_pyv_numeric = Some(numeric);
}
*path_where_annotated_pyv = Some(path.to_owned());
}
Ok(())
};
check(py310, 310)?;
check(py311, 311)?;
check(py312, 312)?;
check(py313, 313)?;
let find_requirements = code
.lines()
.find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:"));
@@ -225,11 +287,21 @@ pub async fn parse_python_imports(
.fetch_optional(db)
.await?
.unwrap_or_else(|| "".to_string());
if already_visited.contains(&rpath) {
vec![]
} else {
already_visited.push(rpath.clone());
parse_python_imports(&code, w_id, &rpath, db, already_visited).await?
parse_python_imports_inner(
&code,
w_id,
&rpath,
db,
already_visited,
annotated_pyv_numeric,
path_where_annotated_pyv,
)
.await?
}
} else {
vec![replace_import(n.to_string())]

View File

@@ -25,6 +25,7 @@ def main():
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
// println!("{}", serde_json::to_string(&r)?);
@@ -57,6 +58,7 @@ def main():
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
println!("{}", serde_json::to_string(&r)?);
@@ -87,6 +89,7 @@ def main():
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
println!("{}", serde_json::to_string(&r)?);

View File

@@ -0,0 +1,95 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1737885589,
"narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1738117527,
"narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "6a3dc6ce4132bd57359214d986db376f2333c14d",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,37 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = {
nixpkgs,
flake-utils,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
rust = pkgs.rust-bin.nightly.latest.default.override {
extensions = [
"rust-src"
];
targets = ["wasm32-unknown-unknown"];
};
in {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
rust
nodejs
wasm-pack
sccache
];
RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache";
CARGO_PATH = "${rust}/bin/cargo";
};
});
}

View File

@@ -9,20 +9,19 @@
use anyhow::Context;
use monitor::{
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config,
reload_nuget_config_setting, reload_timeout_wait_result_setting,
send_current_log_file_to_object_store, send_logs_to_object_store,
reload_instance_python_version_setting, reload_nuget_config_setting,
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
send_logs_to_object_store,
};
use rand::Rng;
use sqlx::{postgres::PgListener, Pool, Postgres};
use std::{
collections::HashMap,
fs::{create_dir_all, DirBuilder},
net::{IpAddr, Ipv4Addr, SocketAddr},
time::Duration,
};
use tokio::{
fs::{create_dir_all, DirBuilder, File},
io::AsyncReadExt,
};
use tokio::{fs::File, io::AsyncReadExt};
use uuid::Uuid;
use windmill_api::HTTP_CLIENT;
@@ -33,19 +32,20 @@ use windmill_common::{
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING,
TIMEOUT_WAIT_RESULT_SETTING,
TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
scripts::ScriptLang,
stats_ee::schedule_stats,
utils::{hostname, rd_string, Mode, GIT_VERSION},
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, WORKER_GROUP},
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP},
DB, METRICS_ENABLED,
};
@@ -69,8 +69,9 @@ use windmill_worker::{
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
BUN_DEPSTAR_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS,
DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
POWERSHELL_CACHE_DIR, PY311_CACHE_DIR, RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY311_CACHE_DIR,
TMP_LOGS_DIR, UV_CACHE_DIR,
POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR,
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR,
TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
};
use crate::monitor::{
@@ -137,8 +138,8 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
)
})?;
create_dir_all(HUB_CACHE_DIR).await?;
create_dir_all(BUN_BUNDLE_CACHE_DIR).await?;
create_dir_all(HUB_CACHE_DIR)?;
create_dir_all(BUN_BUNDLE_CACHE_DIR)?;
for path in paths.values() {
tracing::info!("Caching hub script at {path}");
@@ -149,7 +150,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
.is_some_and(|x| x == &ScriptLang::Deno)
{
let job_dir = format!("{}/cache_init/{}", TMP_DIR, Uuid::new_v4());
create_dir_all(&job_dir).await?;
create_dir_all(&job_dir)?;
let _ = windmill_worker::generate_deno_lock(
&Uuid::nil(),
&res.content,
@@ -167,7 +168,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
} else if res.language.as_ref().is_some_and(|x| x == &ScriptLang::Bun) {
let job_id = Uuid::new_v4();
let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id);
create_dir_all(&job_dir).await?;
create_dir_all(&job_dir)?;
if let Some(lockfile) = res.lockfile {
let _ = windmill_worker::prepare_job_dir(&lockfile, &job_dir).await?;
let envs = windmill_worker::get_common_bun_proc_envs(None).await;
@@ -502,7 +503,6 @@ Windmill Community Edition {GIT_VERSION}
DirBuilder::new()
.recursive(true)
.create("/tmp/windmill")
.await
.expect("could not create initial server dir");
#[cfg(feature = "tantivy")]
@@ -734,6 +734,9 @@ Windmill Community Edition {GIT_VERSION}
SMTP_SETTING => {
reload_smtp_config(&db).await;
},
TEAMS_SETTING => {
tracing::info!("Teams setting changed.");
},
INDEXER_SETTING => {
reload_indexer_config(&db).await;
},
@@ -762,6 +765,9 @@ Windmill Community Edition {GIT_VERSION}
PIP_INDEX_URL_SETTING => {
reload_pip_index_url_setting(&db).await
},
INSTANCE_PYTHON_VERSION_SETTING => {
reload_instance_python_version_setting(&db).await
},
NPM_CONFIG_REGISTRY_SETTING => {
reload_npm_config_registry_setting(&db).await
},
@@ -779,11 +785,12 @@ Windmill Community Edition {GIT_VERSION}
},
EXPOSE_METRICS_SETTING => {
tracing::info!("Metrics setting changed, restarting");
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
let rd_delay = rand::thread_rng().gen_range(0..40);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
send_delayed_killpill(&tx, 40, "metrics setting change").await;
},
EMAIL_DOMAIN_SETTING => {
tracing::info!("Email domain setting changed");
if server_mode {
send_delayed_killpill(&tx, 4, "email domain setting change").await;
}
},
EXPOSE_DEBUG_METRICS_SETTING => {
@@ -793,29 +800,17 @@ Windmill Community Edition {GIT_VERSION}
},
OTEL_SETTING => {
tracing::info!("OTEL setting changed, restarting");
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
let rd_delay = rand::thread_rng().gen_range(0..4);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill");
}
send_delayed_killpill(&tx, 4, "OTEL setting change").await;
},
REQUEST_SIZE_LIMIT_SETTING => {
if server_mode {
tracing::info!("Request limit size change detected, killing server expecting to be restarted");
// we wait a bit randomly to avoid having all servers shutdown at same time
let rd_delay = rand::thread_rng().gen_range(0..4);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
send_delayed_killpill(&tx, 4, "request size limit change").await;
}
},
SAML_METADATA_SETTING => {
tracing::info!("SAML metadata change detected, killing server expecting to be restarted");
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
send_delayed_killpill(&tx, 0, "SAML metadata change").await;
},
HUB_BASE_URL_SETTING => {
if let Err(e) = reload_hub_base_url_setting(&db, server_mode).await {
@@ -1013,12 +1008,18 @@ pub async fn run_workers(
TMP_LOGS_DIR,
UV_CACHE_DIR,
TAR_PIP_CACHE_DIR,
TAR_PY311_CACHE_DIR,
DENO_CACHE_DIR,
DENO_CACHE_DIR_DEPS,
DENO_CACHE_DIR_NPM,
BUN_CACHE_DIR,
PY310_CACHE_DIR,
PY311_CACHE_DIR,
PY312_CACHE_DIR,
PY313_CACHE_DIR,
TAR_PY310_CACHE_DIR,
TAR_PY311_CACHE_DIR,
TAR_PY312_CACHE_DIR,
TAR_PY313_CACHE_DIR,
PIP_CACHE_DIR,
BUN_DEPSTAR_CACHE_DIR,
BUN_BUNDLE_CACHE_DIR,
@@ -1032,7 +1033,6 @@ pub async fn run_workers(
DirBuilder::new()
.recursive(true)
.create(x)
.await
.expect("could not create initial worker dir");
}
@@ -1083,3 +1083,18 @@ pub async fn run_workers(
futures::future::try_join_all(handles).await?;
Ok(())
}
async fn send_delayed_killpill(
tx: &tokio::sync::broadcast::Sender<()>,
max_delay_secs: u64,
context: &str,
) {
// Random delay to avoid all servers/workers shutting down simultaneously
let rd_delay = rand::rng().random_range(0..max_delay_secs);
tracing::info!("Scheduling {context} shutdown in {rd_delay}s");
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill for {context}");
}
}

View File

@@ -41,10 +41,10 @@ use windmill_common::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING,
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
@@ -68,8 +68,8 @@ use windmill_common::{
use windmill_queue::cancel_job;
use windmill_worker::{
create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender,
SendResult, BUNFIG_INSTALL_SCOPES, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, NPM_CONFIG_REGISTRY,
NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
SendResult, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR,
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
};
#[cfg(feature = "parquet")]
@@ -198,6 +198,7 @@ pub async fn initial_load(
reload_pip_index_url_setting(&db).await;
reload_npm_config_registry_setting(&db).await;
reload_bunfig_install_scopes_setting(&db).await;
reload_instance_python_version_setting(&db).await;
reload_nuget_config_setting(&db).await;
}
}
@@ -908,6 +909,16 @@ pub async fn reload_pip_index_url_setting(db: &DB) {
.await;
}
pub async fn reload_instance_python_version_setting(db: &DB) {
reload_option_setting_with_tracing(
db,
INSTANCE_PYTHON_VERSION_SETTING,
"INSTANCE_PYTHON_VERSION",
INSTANCE_PYTHON_VERSION.clone(),
)
.await;
}
pub async fn reload_npm_config_registry_setting(db: &DB) {
reload_option_setting_with_tracing(
db,
@@ -1083,7 +1094,7 @@ pub async fn reload_option_setting_with_tracing<T: FromStr + DeserializeOwned>(
}
}
async fn load_value_from_global_settings(
pub async fn load_value_from_global_settings(
db: &DB,
setting_name: &str,
) -> error::Result<Option<serde_json::Value>> {
@@ -1096,6 +1107,7 @@ async fn load_value_from_global_settings(
.map(|x| x.value);
Ok(r)
}
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
db: &DB,
setting_name: &str,
@@ -1479,7 +1491,7 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> {
#[cfg(feature = "oauth2")]
{
let mut l = windmill_api::OAUTH_CLIENTS.write().await;
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths)
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths, db).await
.map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e))
.unwrap();
}
@@ -1495,9 +1507,20 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> {
async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker_name: &str) {
if *RESTART_ZOMBIE_JOBS {
let restarted = sqlx::query!(
"UPDATE queue SET running = false, started_at = null
WHERE last_ping < now() - ($1 || ' seconds')::interval
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
"WITH zombie_jobs AS (
UPDATE queue SET running = false, started_at = null
WHERE last_ping < now() - ($1 || ' seconds')::interval
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false
RETURNING id, workspace_id, last_ping
),
update_concurrency AS (
UPDATE concurrency_counter cc
SET job_uuids = job_uuids - zj.id::text
FROM zombie_jobs zj
INNER JOIN concurrency_key ck ON ck.job_id = zj.id
WHERE cc.concurrency_id = ck.key
)
SELECT id, workspace_id, last_ping FROM zombie_jobs",
*ZOMBIE_JOB_TIMEOUT,
)
.fetch_all(db)
@@ -1641,12 +1664,31 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
tracing::error!(error_message);
report_critical_error(error_message, db.clone(), Some(&flow.workspace_id), None).await;
// if the flow hasn't started and is a zombie, we can simply restart it
let mut tx = db.begin().await?;
let concurrency_key =
sqlx::query_scalar!("SELECT key FROM concurrency_key WHERE job_id = $1", flow.id)
.fetch_optional(&mut *tx)
.await?;
if let Some(key) = concurrency_key {
sqlx::query!(
"UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1",
key,
flow.id.hyphenated().to_string()
)
.execute(&mut *tx)
.await?;
}
sqlx::query!(
"UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false",
flow.id
)
.execute(db)
.execute(&mut *tx)
.await?;
tx.commit().await?;
} else {
let id = flow.id.clone();
let last_ping = flow.last_ping.clone();

View File

@@ -1,7 +1,7 @@
use serde::de::DeserializeOwned;
use std::future::Future;
use std::{str::FromStr, sync::Arc};
use windmill_api_client::types::{NewScript, NewScriptLanguage};
use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage};
#[cfg(feature = "enterprise")]
use chrono::Timelike;

View File

@@ -28,6 +28,7 @@ zip = ["dep:async_zip"]
oauth2 = ["dep:async-oauth2"]
http_trigger = ["dep:matchit"]
static_frontend = ["dep:rust-embed"]
postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"]
[dependencies]
windmill-queue.workspace = true
@@ -107,8 +108,13 @@ rdkafka = { workspace = true, optional = true }
async-nats = { workspace = true, optional = true }
nkeys = { workspace = true, optional = true }
const_format.workspace = true
pin-project.workspace = true
http.workspace = true
async-stream.workspace = true
ulid.workspace = true
rust-postgres = { workspace = true, optional = true }
pg_escape = { workspace = true, optional = true }
byteorder = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
rust_decimal = { workspace = true, optional = true }
rust-postgres-native-tls = { workspace = true, optional = true}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
use crate::{
db::{ApiAuthed, DB},
variables::decrypt,
variables::get_variable_or_self,
};
use anthropic::AnthropicCache;
use axum::{
body::Bytes,
@@ -19,7 +20,6 @@ use serde::{Deserialize, Deserializer};
use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
use windmill_common::error::{to_anyhow, Result};
use windmill_common::variables::build_crypt;
use windmill_common::error::Error;
@@ -346,38 +346,11 @@ lazy_static! {
pub static ref AI_KEY_CACHE: Cache<String, AiCache> = Cache::new(500);
}
struct Variable {
value: String,
is_secret: bool,
}
#[derive(Deserialize, Debug)]
struct ProxyQueryParams {
no_cache: Option<bool>,
}
async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<String> {
if !path.starts_with("$var:") {
return Ok(path);
}
let path = path.strip_prefix("$var:").unwrap().to_string();
let mut variable = sqlx::query_as!(
Variable,
"SELECT value, is_secret
FROM variable
WHERE path = $1 AND workspace_id = $2",
&path,
&w_id
)
.fetch_one(db)
.await?;
if variable.is_secret {
let mc = build_crypt(db, w_id).await?;
variable.value = decrypt(&mc, variable.value)?;
}
Ok(variable.value)
}
#[derive(Deserialize, Debug)]
pub struct AiResource {
pub path: String,

View File

@@ -13,7 +13,6 @@ use crate::{
resources::get_resource_value_interpolated_internal,
users::{require_owner_of_path, OptAuthed},
utils::WithStarredInfoQuery,
variables::encrypt,
webhook_util::{WebhookMessage, WebhookShared},
HTTP_CLIENT,
};
@@ -54,6 +53,7 @@ use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
#[cfg(feature = "parquet")]
use windmill_common::s3_helpers::build_object_store_client;
use windmill_common::variables::encrypt;
use windmill_common::{
apps::{AppScriptId, ListAppQuery},
cache::{self, future::FutureCachedExt},

View File

@@ -35,9 +35,9 @@ use windmill_queue::{PushArgs, PushArgsOwned};
#[cfg(feature = "http_trigger")]
use crate::http_triggers::{build_http_trigger_extra, HttpMethod};
#[cfg(all(feature = "enterprise", feature = "kafka"))]
use crate::kafka_triggers_ee::KafkaResourceSecurity;
use crate::kafka_triggers_ee::KafkaTriggerConfigConnection;
#[cfg(all(feature = "enterprise", feature = "nats"))]
use crate::nats_triggers_ee::NatsResourceAuth;
use crate::nats_triggers_ee::NatsTriggerConfigConnection;
use crate::{
args::WebhookArgs,
db::{ApiAuthed, DB},
@@ -56,6 +56,7 @@ pub fn workspaced_service() -> Router {
.route("/get_configs/:runnable_kind/*path", get(get_configs))
.route("/list/:runnable_kind/*path", get(list_captures))
.route("/:id", delete(delete_capture))
.route("/:id", get(get_capture))
}
pub fn workspaced_unauthed_service() -> Router {
@@ -110,14 +111,6 @@ struct HttpTriggerConfig {
http_method: HttpMethod,
}
#[cfg(all(feature = "enterprise", feature = "kafka"))]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum KafkaTriggerConfigConnection {
Resource { kafka_resource_path: String },
Static { brokers: Vec<String>, security: KafkaResourceSecurity },
}
#[cfg(all(feature = "enterprise", feature = "kafka"))]
#[derive(Serialize, Deserialize)]
pub struct KafkaTriggerConfig {
@@ -127,14 +120,6 @@ pub struct KafkaTriggerConfig {
pub group_id: String,
}
#[cfg(all(feature = "enterprise", feature = "nats"))]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum NatsTriggerConfigConnection {
Resource { nats_resource_path: String },
Static { servers: Vec<String>, auth: NatsResourceAuth, require_tls: bool },
}
#[cfg(all(feature = "enterprise", feature = "nats"))]
#[derive(Serialize, Deserialize)]
pub struct NatsTriggerConfig {
@@ -296,7 +281,7 @@ async fn list_captures(
let captures = sqlx::query_as!(
Capture,
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload: _", trigger_extra as "trigger_extra: _"
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '"WINDMILL_TOO_BIG"'::jsonb END as "payload!: _", trigger_extra as "trigger_extra: _"
FROM capture
WHERE workspace_id = $1
AND path = $2 AND is_flow = $3
@@ -319,6 +304,24 @@ async fn list_captures(
Ok(Json(captures))
}
async fn get_capture(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, id)): Path<(String, i64)>,
) -> JsonResult<Capture> {
let mut tx = user_db.begin(&authed).await?;
let capture = sqlx::query_as!(
Capture,
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE id = $1 AND workspace_id = $2"#,
id,
&w_id,
)
.fetch_one(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(capture))
}
async fn delete_capture(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -173,6 +173,13 @@ pub async fn migrate(db: &DB) -> Result<(), Error> {
let migrator = db.acquire().await?;
let mut custom_migrator = CustomMigrator { inner: migrator };
if let Err(err) = sqlx::query!("DELETE FROM _sqlx_migrations WHERE version=20250131115248")
.execute(db)
.await
{
tracing::info!("Could not remove sqlx migration with version=20250131115248: {err:#}");
}
match sqlx::migrate!("../migrations")
.run_direct(&mut custom_migrator)
.await

View File

@@ -521,7 +521,7 @@ async fn get_http_route_trigger(
trigger.email.clone(),
&trigger.workspace_id,
&db,
Some(username_override.unwrap_or("anonymous".to_string())),
Some(username_override.unwrap_or(format!("http-{}", trigger.path))),
)
.await?;
@@ -685,12 +685,6 @@ async fn route_job(
.await,
);
let label_prefix = Some(format!(
"http-{}-{}-",
method.as_str().to_lowercase(),
trigger.route_path
));
let run_query = RunJobQuery::default();
if trigger.is_flow {
@@ -703,7 +697,7 @@ async fn route_job(
StripPath(trigger.script_path.to_owned()),
run_query,
args,
label_prefix,
None,
)
.await
.into_response()
@@ -716,7 +710,7 @@ async fn route_job(
user_db,
args,
trigger.workspace_id.clone(),
label_prefix,
None,
)
.await
.into_response()
@@ -731,7 +725,7 @@ async fn route_job(
StripPath(trigger.script_path.to_owned()),
run_query,
args,
label_prefix,
None,
)
.await
.into_response()
@@ -744,7 +738,7 @@ async fn route_job(
user_db,
trigger.workspace_id.clone(),
args,
label_prefix,
None,
)
.await
.into_response()

View File

@@ -9,12 +9,14 @@
use axum::body::Body;
use axum::http::HeaderValue;
use futures::TryFutureExt;
use http::{HeaderMap, HeaderName};
use itertools::Itertools;
use quick_cache::sync::Cache;
use serde_json::value::RawValue;
use sqlx::Pool;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
#[cfg(feature = "prometheus")]
use std::sync::atomic::Ordering;
use tokio::io::AsyncReadExt;
@@ -1966,20 +1968,34 @@ async fn resume_suspended_job_internal(
resume_immediately_if_relevant(parent_flow_info, job_id, &mut tx).await?;
}
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
let audit_author = match authed {
Some(authed) => (&authed).into(),
None => {
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
AuditAuthor { email: approver.clone(), username: approver, username_override: None }
}
None => AuditAuthor {
email: approver.clone(),
username: approver.clone(),
username_override: None,
},
};
audit_log(
&mut *tx,
&audit_author,
"jobs.approved",
"jobs.suspend_resume",
ActionKind::Update,
&w_id,
Some(&job_id.to_string()),
Some(
&serde_json::json!({
"approved": approved,
"job_id": job_id,
"details": if approved {
format!("Approved by {}", &approver)
} else {
format!("Cancelled by {}", &approver)
}
})
.to_string(),
),
None,
)
.await?;
@@ -3522,15 +3538,17 @@ lazy_static::lazy_static! {
pub struct WindmillCompositeResult {
windmill_status_code: Option<u16>,
windmill_content_type: Option<String>,
windmill_headers: Option<HashMap<String, String>>,
result: Option<Box<RawValue>>,
}
pub async fn run_wait_result(
pub async fn run_wait_result_internal(
db: &DB,
uuid: Uuid,
w_id: String,
node_id_for_empty_return: Option<String>,
username: &str,
) -> error::Result<Response> {
) -> error::Result<(Box<RawValue>, bool)> {
let mut result = None;
let mut success = false;
let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600);
@@ -3602,81 +3620,110 @@ pub async fn run_wait_result(
};
tokio::time::sleep(core::time::Duration::from_millis(delay)).await;
}
if let Some(result) = result {
g.done = true;
Ok((result, success))
} else {
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
}
}
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
match composite_result {
Ok(WindmillCompositeResult {
windmill_status_code,
windmill_content_type,
result: result_value,
}) => {
if windmill_content_type.is_none() && windmill_status_code.is_none() {
return Ok((
if success {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
},
Json(result),
)
.into_response());
}
pub async fn run_wait_result(
db: &DB,
uuid: Uuid,
w_id: String,
node_id_for_empty_return: Option<String>,
username: &str,
) -> error::Result<Response> {
let (result, success) =
run_wait_result_internal(db, uuid, w_id, node_id_for_empty_return, username).await?;
let status_code_or_default = windmill_status_code
.map(|val| match StatusCode::from_u16(val) {
Ok(sc) => Ok(sc),
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
})
.unwrap_or_else(|| {
if !success {
Ok(StatusCode::INTERNAL_SERVER_ERROR)
} else if result_value.is_some() {
Ok(StatusCode::OK)
} else {
Ok(StatusCode::NO_CONTENT)
}
})?;
if windmill_content_type.is_some() {
let serialized_json_result = result_value
.map(|val| val.get().to_owned())
.unwrap_or_else(String::new);
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
// it falls back to the original serialized JSON if it doesn't work.
let serialized_result =
serde_json::from_str::<String>(serialized_json_result.as_str())
.ok()
.unwrap_or(serialized_json_result);
return Ok((
status_code_or_default,
[(
http::header::CONTENT_TYPE,
HeaderValue::from_str(windmill_content_type.unwrap().as_str()).unwrap(),
)],
serialized_result,
)
.into_response());
}
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
match composite_result {
Ok(WindmillCompositeResult {
windmill_status_code,
windmill_content_type,
windmill_headers,
result: result_value,
}) => {
if windmill_content_type.is_none()
&& windmill_status_code.is_none()
&& windmill_headers.is_none()
{
return Ok((
status_code_or_default,
Json(result_value), // default to JSON result if no content type is provided
if success {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
},
Json(result),
)
.into_response());
}
_ => Ok((
if success {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
},
Json(result),
)
.into_response()),
let status_code_or_default = windmill_status_code
.map(|val| match StatusCode::from_u16(val) {
Ok(sc) => Ok(sc),
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
})
.unwrap_or_else(|| {
if !success {
Ok(StatusCode::INTERNAL_SERVER_ERROR)
} else if result_value.is_some() {
Ok(StatusCode::OK)
} else {
Ok(StatusCode::NO_CONTENT)
}
})?;
let mut headers = HeaderMap::new();
if let Some(windmill_headers) = windmill_headers {
for (k, v) in windmill_headers {
let k = HeaderName::from_str(k.as_str()).map_err(|err| {
Error::InternalErr(format!("Invalid header name {k}: {err}"))
})?;
let v = HeaderValue::from_str(v.as_str()).map_err(|err| {
Error::InternalErr(format!("Invalid header value {v}: {err}"))
})?;
headers.insert(k, v);
}
}
if let Some(content_type) = windmill_content_type {
let serialized_json_result = result_value
.map(|val| val.get().to_owned())
.unwrap_or_else(String::new);
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
// it falls back to the original serialized JSON if it doesn't work.
let serialized_result =
serde_json::from_str::<String>(serialized_json_result.as_str())
.ok()
.unwrap_or(serialized_json_result);
headers.insert(
http::header::CONTENT_TYPE,
HeaderValue::from_str(content_type.as_str()).map_err(|err| {
Error::InternalErr(format!("Invalid content type {content_type}: {err}"))
})?,
);
return Ok((status_code_or_default, headers, serialized_result).into_response());
}
if let Some(result_value) = result_value {
return Ok((status_code_or_default, headers, Json(result_value)).into_response());
} else {
Ok((status_code_or_default, headers).into_response())
}
}
} else {
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
_ => Ok((
if success {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
},
Json(result),
)
.into_response()),
}
}

View File

@@ -9,9 +9,12 @@ pub fn workspaced_service() -> Router {
Router::new()
}
pub async fn start_kafka_consumers(
pub fn start_kafka_consumers(
_db: DB,
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> () {
// implementation is not open source
}
#[derive(Serialize, Deserialize)]
pub enum KafkaTriggerConfigConnection {}

View File

@@ -34,7 +34,11 @@ use http::HeaderValue;
use reqwest::Client;
#[cfg(feature = "oauth2")]
use std::collections::HashMap;
use windmill_common::global_settings::load_value_from_global_settings;
use windmill_common::global_settings::EMAIL_DOMAIN_SETTING;
use windmill_common::worker::HUB_CACHE_DIR;
use std::fs::DirBuilder;
use std::time::Duration;
use std::{net::SocketAddr, sync::Arc};
use tokio::sync::RwLock;
@@ -73,6 +77,8 @@ mod http_triggers;
mod indexer_ee;
mod inputs;
mod integration;
#[cfg(feature = "postgres_trigger")]
mod postgres_triggers;
#[cfg(feature = "enterprise")]
mod apps_ee;
@@ -100,6 +106,8 @@ mod slack_approvals;
mod smtp_server_ee;
mod static_assets;
mod stripe_ee;
#[cfg(feature = "enterprise")]
mod teams_ee;
mod tracing_init;
mod triggers;
mod users;
@@ -199,6 +207,13 @@ pub async fn run_server(
) -> anyhow::Result<()> {
let user_db = UserDB::new(db.clone());
for x in [HUB_CACHE_DIR] {
DirBuilder::new()
.recursive(true)
.create(x)
.expect("could not create initial server dir");
}
#[cfg(feature = "enterprise")]
let ext_jwks = ExternalJwks::load().await;
let auth_cache = Arc::new(crate::auth::AuthCache::new(
@@ -238,16 +253,32 @@ pub async fn run_server(
#[cfg(feature = "embedding")]
load_embeddings_db(&db);
#[cfg(feature = "smtp")]
let mut start_smtp_server = false;
if let Some(smtp_settings) =
load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await?
{
let smtp_server = Arc::new(SmtpServer {
db: db.clone(),
user_db: user_db,
auth_cache: auth_cache.clone(),
base_internal_url: base_internal_url.clone(),
});
if let Err(err) = smtp_server.start_listener_thread(addr).await {
tracing::error!("Error starting SMTP server: {err:#}");
if smtp_settings.as_str().unwrap_or("") != "" {
start_smtp_server = true;
}
}
if !start_smtp_server {
tracing::info!("SMTP server not started because email domain is not set");
} else {
#[cfg(feature = "smtp")]
{
let smtp_server = Arc::new(SmtpServer {
db: db.clone(),
user_db: user_db,
auth_cache: auth_cache.clone(),
base_internal_url: base_internal_url.clone(),
});
if let Err(err) = smtp_server.start_listener_thread(addr).await {
tracing::error!("Error starting SMTP server: {err:#}");
}
}
#[cfg(not(feature = "smtp"))]
{
tracing::info!("SMTP server not started because SMTP feature is not enabled");
}
}
}
@@ -292,19 +323,24 @@ pub async fn run_server(
#[cfg(feature = "websocket")]
{
let ws_killpill_rx = rx.resubscribe();
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx).await;
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx);
}
#[cfg(all(feature = "enterprise", feature = "kafka"))]
{
let kafka_killpill_rx = rx.resubscribe();
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx).await;
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx);
}
#[cfg(all(feature = "enterprise", feature = "nats"))]
{
let nats_killpill_rx = rx.resubscribe();
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx).await;
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx);
}
#[cfg(feature = "postgres_trigger")]
{
let db_killpill_rx = rx.resubscribe();
postgres_triggers::start_database(db.clone(), db_killpill_rx);
}
}
@@ -375,7 +411,16 @@ pub async fn run_server(
Router::new()
})
.nest("/kafka_triggers", kafka_triggers_service)
.nest("/nats_triggers", nats_triggers_service),
.nest("/nats_triggers", nats_triggers_service)
.nest("/postgres_triggers", {
#[cfg(feature = "postgres_trigger")]
{
postgres_triggers::workspaced_service()
}
#[cfg(not(feature = "postgres_trigger"))]
Router::new()
}),
)
.nest("/workspaces", workspaces::global_service())
.nest(
@@ -435,6 +480,17 @@ pub async fn run_server(
jobs::workspace_unauthed_service().layer(cors.clone()),
)
.route("/slack", post(slack_approvals::slack_app_callback_handler))
.nest("/teams", {
#[cfg(feature = "enterprise")]
{
teams_ee::teams_service()
}
#[cfg(not(feature = "enterprise"))]
{
Router::new()
}
})
.route(
"/w/:workspace_id/jobs/slack_approval/:job_id",
get(slack_approvals::request_slack_approval),

View File

@@ -9,9 +9,9 @@ pub fn workspaced_service() -> Router {
Router::new()
}
pub async fn start_nats_consumers(
_db: DB,
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> () {
pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
// implementation is not open source
}
#[derive(Serialize, Deserialize)]
pub enum NatsTriggerConfigConnection {}

View File

@@ -80,9 +80,10 @@ pub struct AllClients {
}
#[cfg(feature = "oauth2")]
pub fn build_oauth_clients(
pub async fn build_oauth_clients(
_base_url: &str,
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
_db: &DB,
) -> anyhow::Result<AllClients> {
// Implementation is not open source
return Ok(AllClients {

View File

@@ -0,0 +1,24 @@
use thiserror::Error;
/**
* This implementation is inspired by Postgres replication functionality
* from https://github.com/supabase/pg_replicate
*
* Original implementation:
* - https://github.dev/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/bool.rs
*
*/
#[derive(Debug, Error)]
pub enum ParseBoolError {
#[error("invalid input value: {0}")]
InvalidInput(String),
}
pub fn parse_bool(s: &str) -> Result<bool, ParseBoolError> {
match s {
"t" => Ok(true),
"f" => Ok(false),
_ => Err(ParseBoolError::InvalidInput(s.to_string())),
}
}

View File

@@ -0,0 +1,255 @@
use core::str;
use std::{
num::{ParseFloatError, ParseIntError},
str::FromStr,
};
use super::{
bool::{parse_bool, ParseBoolError},
hex::{from_bytea_hex, ByteaHexParseError},
};
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use rust_decimal::Decimal;
use rust_postgres::types::Type;
use serde_json::{to_value, Number, Value};
use thiserror::Error;
use uuid::Uuid;
/**
* This implementation is inspired by Postgres replication functionality
* from https://github.com/supabase/pg_replicate
*
* Original implementation:
* - https://github.com/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/text.rs
*
*/
#[derive(Debug, Error)]
pub enum ConverterError {
#[error("invalid bool value")]
InvalidBool(#[from] ParseBoolError),
#[error("invalid int value")]
InvalidInt(#[from] ParseIntError),
#[error("invalid float value")]
InvalidFloat(#[from] ParseFloatError),
#[error("invalid numeric: {0}")]
InvalidNumeric(#[from] rust_decimal::Error),
#[error("invalid bytea: {0}")]
InvalidBytea(#[from] ByteaHexParseError),
#[error("invalid uuid: {0}")]
InvalidUuid(#[from] uuid::Error),
#[error("invalid json: {0}")]
InvalidJson(#[from] serde_json::Error),
#[error("invalid timestamp: {0} ")]
InvalidTimestamp(#[from] chrono::ParseError),
#[error("invalid array: {0}")]
InvalidArray(#[from] ArrayParseError),
#[error("{0}")]
Custom(String),
}
fn convert_into<T>(number: T) -> Number
where
T: Sized,
serde_json::Number: From<T>,
{
serde_json::Number::from(number)
}
pub struct Converter;
#[derive(Debug, Error)]
pub enum ArrayParseError {
#[error("input too short")]
InputTooShort,
#[error("missing braces")]
MissingBraces,
}
fn f64_to_json_number(raw_val: f64) -> Result<Value, ConverterError> {
let temp = serde_json::Number::from_f64(raw_val.into())
.ok_or(ConverterError::Custom("invalid json-float".to_string()))?;
Ok(Value::Number(temp))
}
impl Converter {
pub fn try_from_str(typ: Option<Type>, str: &str) -> Result<Value, ConverterError> {
let value = match typ.unwrap_or(Type::TEXT) {
Type::BOOL => Value::Bool(parse_bool(str)?),
Type::BOOL_ARRAY => {
Converter::parse_array(str, |str| Ok(Value::Bool(parse_bool(str)?)))?
}
Type::CHAR | Type::BPCHAR | Type::VARCHAR | Type::NAME | Type::TEXT => {
Value::String(str.to_string())
}
Type::CHAR_ARRAY
| Type::BPCHAR_ARRAY
| Type::VARCHAR_ARRAY
| Type::NAME_ARRAY
| Type::TEXT_ARRAY => {
Converter::parse_array(str, |str| Ok(Value::String(str.to_string())))?
}
Type::INT2 => Value::Number(convert_into(str.parse::<i16>()?)),
Type::INT2_ARRAY => Converter::parse_array(str, |str| {
Ok(Value::Number(convert_into(str.parse::<i16>()?)))
})?,
Type::INT4 => Value::Number(convert_into(str.parse::<i32>()?)),
Type::INT4_ARRAY => Converter::parse_array(str, |str| {
Ok(Value::Number(convert_into(str.parse::<i32>()?)))
})?,
Type::INT8 => Value::Number(convert_into(str.parse::<i64>()?)),
Type::INT8_ARRAY => Converter::parse_array(str, |str| {
Ok(Value::Number(convert_into(str.parse::<i64>()?)))
})?,
Type::FLOAT4 => f64_to_json_number(str.parse::<f64>()?)?,
Type::FLOAT4_ARRAY => {
Converter::parse_array(str, |str| f64_to_json_number(str.parse::<f64>()?))?
}
Type::FLOAT8 => f64_to_json_number(str.parse::<f64>()?)?,
Type::FLOAT8_ARRAY => {
Converter::parse_array(str, |str| f64_to_json_number(str.parse::<f64>()?))?
}
Type::NUMERIC => serde_json::json!(Decimal::from_str(str)?),
Type::NUMERIC_ARRAY => {
Converter::parse_array(str, |str| Ok(serde_json::json!(Decimal::from_str(str)?)))?
}
Type::BYTEA => to_value(from_bytea_hex(str)?).unwrap(),
Type::BYTEA_ARRAY => {
Converter::parse_array(str, |str| Ok(to_value(from_bytea_hex(str)?).unwrap()))?
}
Type::DATE => {
let date = NaiveDate::parse_from_str(str, "%Y-%m-%d")?;
Value::String(date.to_string())
}
Type::DATE_ARRAY => Converter::parse_array(str, |str| {
let date = NaiveDate::parse_from_str(str, "%Y-%m-%d")?;
Ok(Value::String(date.to_string()))
})?,
Type::TIME => {
let time = NaiveTime::parse_from_str(str, "%H:%M:%S%.f")?;
Value::String(time.to_string())
}
Type::TIME_ARRAY => Converter::parse_array(str, |str| {
let time = NaiveTime::parse_from_str(str, "%H:%M:%S%.f")?;
Ok(Value::String(time.to_string()))
})?,
Type::TIMESTAMP => {
let timestamp = NaiveDateTime::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f")?;
Value::String(timestamp.to_string())
}
Type::TIMESTAMP_ARRAY => Converter::parse_array(str, |str| {
let timestamp = NaiveDateTime::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f")?;
Ok(Value::String(timestamp.to_string()))
})?,
Type::TIMESTAMPTZ => {
let val =
match DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%#z") {
Ok(val) => val,
Err(_) => {
DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%:z")?
}
};
let utc: DateTime<Utc> = val.into();
Value::String(utc.to_string())
}
Type::TIMESTAMPTZ_ARRAY => {
match Converter::parse_array(str, |str| {
let utc: DateTime<Utc> =
DateTime::<FixedOffset>::parse_from_str(str, "%Y-%m-%d %H:%M:%S%.f%#z")?
.into();
Ok(Value::String(utc.to_string()))
}) {
Ok(val) => val,
Err(_) => Converter::parse_array(str, |str| {
let utc: DateTime<Utc> = DateTime::<FixedOffset>::parse_from_str(
str,
"%Y-%m-%d %H:%M:%S%.f%#z",
)?
.into();
Ok(Value::String(utc.to_string()))
})?,
}
}
Type::UUID => Value::String(Uuid::parse_str(str)?.to_string()),
Type::UUID_ARRAY => Converter::parse_array(str, |str| {
Ok(Value::String(Uuid::parse_str(str)?.to_string()))
})?,
Type::JSON | Type::JSONB => serde_json::from_str::<serde_json::Value>(str)?,
Type::JSON_ARRAY | Type::JSONB_ARRAY => Converter::parse_array(str, |str| {
Ok(serde_json::from_str::<serde_json::Value>(str)?)
})?,
Type::OID => Value::Number(convert_into(str.parse::<u32>()?)),
Type::OID_ARRAY => Converter::parse_array(str, |str| {
Ok(Value::Number(convert_into(str.parse::<u32>()?)))
})?,
_ => Value::String(str.to_string()),
};
Ok(value)
}
fn parse_array<P>(str: &str, mut parse: P) -> Result<Value, ConverterError>
where
P: FnMut(&str) -> Result<Value, ConverterError>,
{
if str.len() < 2 {
return Err(ArrayParseError::InputTooShort.into());
}
if !str.starts_with('{') || !str.ends_with('}') {
return Err(ArrayParseError::MissingBraces.into());
}
let mut res = vec![];
let str = &str[1..(str.len() - 1)];
let mut val_str = String::with_capacity(10);
let mut in_quotes = false;
let mut in_escape = false;
let mut chars = str.chars();
let mut done = str.is_empty();
while !done {
loop {
match chars.next() {
Some(c) => match c {
c if in_escape => {
val_str.push(c);
in_escape = false;
}
'"' => in_quotes = !in_quotes,
'\\' => in_escape = true,
',' if !in_quotes => {
break;
}
c => {
val_str.push(c);
}
},
None => {
done = true;
break;
}
}
}
let val = if val_str.to_lowercase() == "null" {
Value::Null
} else {
parse(&val_str)?
};
res.push(val);
val_str.clear();
}
let arr = Value::Array(res);
Ok(arr)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
use std::num::ParseIntError;
/**
* This implementation is inspired by Postgres replication functionality
* from https://github.com/supabase/pg_replicate
*
* Original implementation:
* - https://github.dev/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/hex.rs
*
*/
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ByteaHexParseError {
#[error("missing prefix '\\x'")]
InvalidPrefix,
#[error("invalid byte")]
OddNumerOfDigits,
#[error("parse int result: {0}")]
ParseInt(#[from] ParseIntError),
}
pub fn from_bytea_hex(s: &str) -> Result<Vec<u8>, ByteaHexParseError> {
if s.len() < 2 || &s[..2] != "\\x" {
return Err(ByteaHexParseError::InvalidPrefix);
}
let mut result = Vec::with_capacity((s.len() - 2) / 2);
let s = &s[2..];
if s.len() % 2 != 0 {
return Err(ByteaHexParseError::OddNumerOfDigits);
}
for i in (0..s.len()).step_by(2) {
let val = u8::from_str_radix(&s[i..i + 2], 16)?;
result.push(val);
}
Ok(result)
}

View File

@@ -0,0 +1,134 @@
use std::collections::HashMap;
use rust_postgres::types::Type;
use super::handler::Language;
fn postgres_to_typescript_type(postgres_type: Option<Type>) -> String {
let data_type = match postgres_type {
Some(postgres_type) => match postgres_type {
Type::BOOL => "boolean",
Type::BOOL_ARRAY => "Array<boolean>",
Type::CHAR | Type::BPCHAR | Type::VARCHAR | Type::NAME | Type::TEXT => "string",
Type::CHAR_ARRAY
| Type::BPCHAR_ARRAY
| Type::VARCHAR_ARRAY
| Type::NAME_ARRAY
| Type::TEXT_ARRAY => "Array<string>",
Type::INT2 | Type::INT4 | Type::INT8 | Type::NUMERIC => "number",
Type::INT2_ARRAY | Type::INT4_ARRAY | Type::INT8_ARRAY => "Array<number>",
Type::FLOAT4 | Type::FLOAT8 => "number",
Type::FLOAT8_ARRAY | Type::FLOAT4_ARRAY => "Array<number>",
Type::NUMERIC_ARRAY => "Array<number>",
Type::BYTEA => "Array<number>",
Type::BYTEA_ARRAY => "Array<Array<number>>",
Type::DATE => "string",
Type::DATE_ARRAY => "Array<string>",
Type::TIME => "string",
Type::TIME_ARRAY => "Array<string>",
Type::TIMESTAMPTZ | Type::TIMESTAMP => "Date",
Type::TIMESTAMPTZ_ARRAY | Type::TIMESTAMP_ARRAY => "Array<Date>",
Type::UUID => "string",
Type::UUID_ARRAY => "Array<string>",
Type::JSON | Type::JSONB | Type::JSON_ARRAY | Type::JSONB_ARRAY => "unknown",
Type::OID => "number",
Type::OID_ARRAY => "Array<number>",
_ => "string",
},
None => "string",
};
data_type.to_string()
}
fn into_body_struct(language: Language, mapped_info: Vec<MappingInfo>) -> String {
let mut block = String::new();
match language {
Language::Typescript => {
block.push_str("{\r\n");
for field in mapped_info {
let typescript_type = postgres_to_typescript_type(field.data_type);
let mut key = field.column_name;
if field.is_nullable {
key.push('?');
}
let full_field = format!("\t\t{}: {},\r\n", key, typescript_type);
block.push_str(&full_field);
}
block.push_str("\t}");
}
}
block
}
#[derive(Debug)]
pub struct MappingInfo {
data_type: Option<Type>,
is_nullable: bool,
column_name: String,
}
impl MappingInfo {
pub fn new(column_name: String, data_type: Option<Type>, is_nullable: bool) -> Self {
Self { column_name, data_type, is_nullable }
}
}
pub struct Mapper {
to_template: HashMap<String, HashMap<String, Vec<MappingInfo>>>,
language: Language,
}
impl Mapper {
pub fn new(
to_template: HashMap<String, HashMap<String, Vec<MappingInfo>>>,
language: Language,
) -> Self {
Self { to_template, language }
}
fn into_typescript_template(self) -> Vec<String> {
let mut struct_definitions = Vec::new();
for (_, mapping_info) in self.to_template {
let last_elem = mapping_info.len() - 1;
for (i, (_, mapped_info)) in mapping_info.into_iter().enumerate() {
let mut struct_body = into_body_struct(Language::Typescript, mapped_info);
let struct_body = if i != last_elem {
struct_body.push_str("\r\n");
struct_body
} else {
struct_body
};
struct_definitions.push(struct_body);
}
}
struct_definitions
}
pub fn get_template(self) -> String {
let struct_definition = match self.language {
Language::Typescript => self.into_typescript_template(),
};
let struct_definition = if struct_definition.is_empty() {
"any".to_string()
} else {
struct_definition.join("\t| ")
};
format!(
r#"
export async function main(
transaction_type: "insert" | "update" | "delete",
schema_name: string,
table_name: string,
row: {}
) {{
}}
"#,
struct_definition
)
}
}

View File

@@ -0,0 +1,154 @@
use crate::{
db::{ApiAuthed, DB},
jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery},
resources::get_resource_value_interpolated_internal,
users::fetch_api_authed,
};
use serde_json::value::RawValue;
use std::collections::HashMap;
use axum::{
routing::{delete, get, post},
Router,
};
use handler::{
alter_publication, create_postgres_trigger, create_publication, create_slot,
create_template_script, delete_postgres_trigger, delete_publication, drop_slot_name,
exists_postgres_trigger, get_postgres_trigger, get_publication_info, get_template_script,
is_database_in_logical_level, list_database_publication, list_postgres_triggers,
list_slot_name, set_enabled, update_postgres_trigger, Database, PostgresTrigger,
};
use windmill_common::{db::UserDB, error::Error, utils::StripPath};
use windmill_queue::PushArgsOwned;
mod bool;
mod converter;
mod handler;
mod hex;
mod mapper;
mod relation;
mod replication_message;
mod trigger;
pub use trigger::start_database;
pub async fn get_database_resource(
authed: ApiAuthed,
user_db: Option<UserDB>,
db: &DB,
database_resource_path: &str,
w_id: &str,
) -> Result<Database, Error> {
let resource = get_resource_value_interpolated_internal(
&authed,
user_db,
&db,
&w_id,
&database_resource_path,
None,
"",
)
.await
.map_err(|_| Error::NotFound("Database resource do not exist".to_string()))?;
let resource = match resource {
Some(resource) => serde_json::from_value::<Database>(resource).map_err(Error::SerdeJson)?,
None => {
return {
Err(Error::NotFound(
"Database resource do not exist".to_string(),
))
}
}
};
Ok(resource)
}
fn publication_service() -> Router {
Router::new()
.route("/get/:publication_name/*path", get(get_publication_info))
.route("/create/:publication_name/*path", post(create_publication))
.route("/update/:publication_name/*path", post(alter_publication))
.route(
"/delete/:publication_name/*path",
delete(delete_publication),
)
.route("/list/*path", get(list_database_publication))
}
fn slot_service() -> Router {
Router::new()
.route("/list/*path", get(list_slot_name))
.route("/create/*path", post(create_slot))
.route("/delete/*path", delete(drop_slot_name))
}
pub fn workspaced_service() -> Router {
Router::new()
.route("/create", post(create_postgres_trigger))
.route("/list", get(list_postgres_triggers))
.route("/get/*path", get(get_postgres_trigger))
.route("/update/*path", post(update_postgres_trigger))
.route("/delete/*path", delete(delete_postgres_trigger))
.route("/exists/*path", get(exists_postgres_trigger))
.route("/setenabled/*path", post(set_enabled))
.route("/get_template_script/:id", get(get_template_script))
.route("/create_template_script", post(create_template_script))
.route(
"/is_valid_postgres_configuration/*path",
get(is_database_in_logical_level),
)
.nest("/publication", publication_service())
.nest("/slot", slot_service())
}
async fn run_job(
args: Option<HashMap<String, Box<RawValue>>>,
extra: Option<HashMap<String, Box<RawValue>>>,
db: &DB,
trigger: &PostgresTrigger,
) -> anyhow::Result<()> {
let args = PushArgsOwned { args: args.unwrap_or_default(), extra };
let authed = fetch_api_authed(
trigger.edited_by.clone(),
trigger.email.clone(),
&trigger.workspace_id,
db,
Some(format!("postgres-{}", trigger.path)),
)
.await?;
let user_db = UserDB::new(db.clone());
let run_query = RunJobQuery::default();
if trigger.is_flow {
run_flow_by_path_inner(
authed,
db.clone(),
user_db,
trigger.workspace_id.clone(),
StripPath(trigger.script_path.to_owned()),
run_query,
args,
None,
)
.await?;
} else {
run_script_by_path_inner(
authed,
db.clone(),
user_db,
trigger.workspace_id.clone(),
StripPath(trigger.script_path.to_owned()),
run_query,
args,
None,
)
.await?;
}
Ok(())
}

View File

@@ -0,0 +1,74 @@
use core::str;
use serde_json::{Map, Value};
use std::{collections::HashMap, str::Utf8Error};
use super::{
converter::{Converter, ConverterError},
replication_message::{Columns, RelationBody, TupleData},
};
use rust_postgres::types::Oid;
#[derive(Debug, thiserror::Error)]
pub enum RelationConversionError {
#[error("Could not find matching table")]
FailToFindMatchingTable,
#[error("Binary data not supported")]
BinaryFormatNotSupported,
#[error("decode error: {0}")]
FromBytes(#[from] ConverterError),
#[error("invalid string value")]
InvalidStr(#[from] Utf8Error),
}
pub struct RelationConverter(HashMap<Oid, RelationBody>);
impl RelationConverter {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn add_relation(&mut self, relation: RelationBody) {
self.0.insert(relation.o_id, relation);
}
pub fn get_columns(&self, o_id: Oid) -> Result<&Columns, RelationConversionError> {
self.0
.get(&o_id)
.map(|relation_body| &relation_body.columns)
.ok_or(RelationConversionError::FailToFindMatchingTable)
}
pub fn get_relation(&self, o_id: Oid) -> Result<&RelationBody, RelationConversionError> {
self.0
.get(&o_id)
.ok_or(RelationConversionError::FailToFindMatchingTable)
}
pub fn body_to_json(
&self,
to_decode: (Oid, Vec<TupleData>),
) -> Result<Map<String, Value>, RelationConversionError> {
let (o_id, tuple_data) = to_decode;
let mut object: Map<String, Value> = Map::new();
let columns = self.get_columns(o_id)?;
for (i, column) in columns.iter().enumerate() {
let value = match &tuple_data[i] {
TupleData::Null | TupleData::UnchangedToast => Value::Null,
TupleData::Binary(_) => {
return Err(RelationConversionError::BinaryFormatNotSupported)
}
TupleData::Text(bytes) => {
let str = str::from_utf8(&bytes[..])?;
Converter::try_from_str(column.type_o_id.clone(), str)?
}
};
object.insert(column.name.clone(), value);
}
Ok(object)
}
}

View File

@@ -0,0 +1,510 @@
#![allow(unused)]
use core::str;
use std::{
cmp,
io::{self, Cursor, Read},
str::Utf8Error,
};
use byteorder::{BigEndian, ReadBytesExt};
use bytes::Bytes;
use rust_postgres::types::{Oid, Type};
use thiserror::Error;
use super::trigger::LogicalReplicationSettings;
const PRIMARY_KEEPALIVE_BYTE: u8 = b'k';
const X_LOG_DATA_BYTE: u8 = b'w';
/**
* This implementation is inspired by Postgres replication functionality
* from https://github.com/supabase/pg_replicate
*
* Original implementation:
* - https://github.com/supabase/pg_replicate/blob/main/pg_replicate/src/conversions/cdc_event.rs
*
*/
#[derive(Debug)]
pub struct PrimaryKeepAliveBody {
pub wal_end: u64,
pub timestamp: i64,
pub reply: bool,
}
impl PrimaryKeepAliveBody {
pub fn new(wal_end: u64, timestamp: i64, reply: bool) -> PrimaryKeepAliveBody {
PrimaryKeepAliveBody { wal_end, timestamp, reply }
}
}
const BEGIN_BYTE: u8 = b'B';
const COMMIT_BYTE: u8 = b'C';
const ORIGIN_BYTE: u8 = b'O';
const RELATION_BYTE: u8 = b'R';
const TYPE_BYTE: u8 = b'Y';
const INSERT_BYTE: u8 = b'I';
const UPDATE_BYTE: u8 = b'U';
const DELETE_BYTE: u8 = b'D';
const TUPLE_NEW_BYTE: u8 = b'N';
const TUPLE_KEY_BYTE: u8 = b'K';
const TUPLE_OLD_BYTE: u8 = b'O';
const TUPLE_DATA_NULL_BYTE: u8 = b'n';
const TUPLE_DATA_TOAST_BYTE: u8 = b'u';
const TUPLE_DATA_TEXT_BYTE: u8 = b't';
const TUPLE_DATA_BINARY_BYTE: u8 = b'b';
const REPLICA_IDENTITY_DEFAULT_BYTE: i8 = 0x64;
const REPLICA_IDENTITY_NOTHING_BYTE: i8 = 0x6E;
const REPLICA_IDENTITY_FULL_BYTE: i8 = 0x66;
const REPLICA_IDENTITY_INDEX_BYTE: i8 = 0x69;
#[derive(Debug)]
pub enum ReplicaIdentity {
Default,
Nothing,
Full,
Index,
}
#[derive(Debug)]
pub struct Column {
pub flags: i8,
pub name: String,
pub type_o_id: Option<Type>,
pub type_modifier: i32,
}
impl Column {
pub fn new(flags: i8, name: String, type_o_id: Option<Type>, type_modifier: i32) -> Self {
Self { flags, name, type_o_id, type_modifier }
}
}
pub type Columns = Vec<Column>;
#[derive(Debug)]
pub struct RelationBody {
pub transaction_id: Option<i32>,
pub o_id: Oid,
pub namespace: String,
pub name: String,
pub replica_identity: ReplicaIdentity,
pub columns: Columns,
}
impl RelationBody {
pub fn new(
transaction_id: Option<i32>,
o_id: Oid,
namespace: String,
name: String,
replica_identity: ReplicaIdentity,
columns: Columns,
) -> Self {
Self { transaction_id, o_id, namespace, name, replica_identity, columns }
}
}
#[derive(Debug)]
pub struct InsertBody {
pub transaction_id: Option<i32>,
pub o_id: Oid,
pub tuple: Vec<TupleData>,
}
impl InsertBody {
pub fn new(transaction_id: Option<i32>, o_id: Oid, tuple: Vec<TupleData>) -> Self {
Self { transaction_id, o_id, tuple }
}
}
#[derive(Debug)]
pub struct UpdateBody {
transaction_id: Option<i32>,
pub o_id: Oid,
pub old_tuple: Option<Vec<TupleData>>,
pub key_tuple: Option<Vec<TupleData>>,
pub new_tuple: Vec<TupleData>,
}
impl UpdateBody {
pub fn new(
transaction_id: Option<i32>,
o_id: Oid,
old_tuple: Option<Vec<TupleData>>,
key_tuple: Option<Vec<TupleData>>,
new_tuple: Vec<TupleData>,
) -> Self {
Self { transaction_id, o_id, old_tuple, key_tuple, new_tuple }
}
}
#[derive(Debug)]
pub struct DeleteBody {
transaction_id: Option<i32>,
pub o_id: Oid,
pub old_tuple: Option<Vec<TupleData>>,
pub key_tuple: Option<Vec<TupleData>>,
}
impl DeleteBody {
pub fn new(
transaction_id: Option<i32>,
o_id: Oid,
old_tuple: Option<Vec<TupleData>>,
key_tuple: Option<Vec<TupleData>>,
) -> Self {
Self { transaction_id, o_id, old_tuple, key_tuple }
}
}
#[derive(Debug)]
pub enum TupleData {
Null,
UnchangedToast,
Text(Bytes),
Binary(Bytes),
}
impl TupleData {
fn parse(buf: &mut Buffer) -> Result<Vec<TupleData>, ConversionError> {
let number_of_columns = buf.read_i16::<BigEndian>()?;
let mut tuples = Vec::with_capacity(number_of_columns as usize);
for _ in 0..number_of_columns {
let byte = buf.read_u8()?;
let tuple_data = match byte {
TUPLE_DATA_NULL_BYTE => TupleData::Null,
TUPLE_DATA_TOAST_BYTE => TupleData::UnchangedToast,
TUPLE_DATA_TEXT_BYTE => {
let len = buf.read_i32::<BigEndian>()?;
let mut data = vec![0; len as usize];
buf.read_exact(&mut data)?;
TupleData::Text(data.into())
}
TUPLE_DATA_BINARY_BYTE => {
let len = buf.read_i32::<BigEndian>()?;
let mut data = vec![0; len as usize];
buf.read_exact(&mut data)?;
TupleData::Binary(data.into())
}
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown replication message byte `{}`", byte),
)));
}
};
tuples.push(tuple_data);
}
Ok(tuples)
}
}
#[derive(Debug)]
pub enum TransactionBody {
Insert(InsertBody),
Update(UpdateBody),
Delete(DeleteBody),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum LogicalReplicationMessage {
Begin,
Commit,
Relation(RelationBody),
Type,
Insert(InsertBody),
Update(UpdateBody),
Delete(DeleteBody),
}
#[derive(Debug)]
pub struct XLogDataBody {
pub wal_start: u64,
pub wal_end: u64,
pub timestamp: i64,
pub data: Bytes,
}
#[derive(Error, Debug)]
pub enum ConversionError {
#[error("Error: {0}")]
Io(#[from] io::Error),
#[error("Utf8Error conversion: {0}")]
Utf8(#[from] Utf8Error),
}
struct Buffer {
bytes: Bytes,
idx: usize,
}
impl Buffer {
pub fn new(bytes: Bytes, idx: usize) -> Buffer {
Buffer { bytes, idx }
}
fn slice(&self) -> &[u8] {
&self.bytes[self.idx..]
}
fn read_cstr(&mut self) -> Result<String, ConversionError> {
match self.slice().iter().position(|&x| x == 0) {
Some(pos) => {
let start = self.idx;
let end = start + pos;
let cstr = str::from_utf8(&self.bytes[start..end])?.to_owned();
self.idx = end + 1;
Ok(cstr)
}
None => Err(ConversionError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected EOF",
))),
}
}
}
impl Read for Buffer {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = {
let slice = self.slice();
let len = cmp::min(slice.len(), buf.len());
buf[..len].copy_from_slice(&slice[..len]);
len
};
self.idx += len;
Ok(len)
}
}
impl XLogDataBody {
pub fn new(wal_start: u64, wal_end: u64, timestamp: i64, data: Bytes) -> XLogDataBody {
XLogDataBody { wal_start, wal_end, timestamp, data }
}
pub fn parse(
self,
logical_replication_settings: &LogicalReplicationSettings,
) -> Result<LogicalReplicationMessage, ConversionError> {
let mut buf = Buffer::new(self.data.clone(), 0);
let byte = buf.read_u8()?;
let logical_replication_message = match byte {
BEGIN_BYTE => {
buf.read_i64::<BigEndian>()?;
buf.read_i64::<BigEndian>()?;
buf.read_i32::<BigEndian>()?;
LogicalReplicationMessage::Begin
}
COMMIT_BYTE => {
buf.read_i8()?;
buf.read_u64::<BigEndian>()?;
buf.read_u64::<BigEndian>()?;
buf.read_i64::<BigEndian>()?;
LogicalReplicationMessage::Commit
}
RELATION_BYTE => {
let transaction_id = match logical_replication_settings.streaming {
true => Some(buf.read_i32::<BigEndian>()?),
false => None,
};
let o_id = buf.read_u32::<BigEndian>()?;
let namespace = buf.read_cstr()?;
let name = buf.read_cstr()?;
let replica_identity = match buf.read_i8()? {
REPLICA_IDENTITY_DEFAULT_BYTE => ReplicaIdentity::Default,
REPLICA_IDENTITY_NOTHING_BYTE => ReplicaIdentity::Nothing,
REPLICA_IDENTITY_FULL_BYTE => ReplicaIdentity::Full,
REPLICA_IDENTITY_INDEX_BYTE => ReplicaIdentity::Index,
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown replica identity byte `{}`", byte),
)));
}
};
let num_of_column = buf.read_i16::<BigEndian>()?;
let mut columns = Vec::with_capacity(num_of_column as usize);
for _ in 0..num_of_column {
let flags = buf.read_i8()?;
let name = buf.read_cstr()?;
let o_id = buf.read_u32::<BigEndian>()?;
let type_modifier = buf.read_i32::<BigEndian>()?;
let type_o_id = Type::from_oid(o_id);
let column = Column::new(flags, name, type_o_id, type_modifier);
columns.push(column);
}
LogicalReplicationMessage::Relation(RelationBody::new(
transaction_id,
o_id,
namespace,
name,
replica_identity,
columns,
))
}
TYPE_BYTE => {
buf.read_u32::<BigEndian>()?;
buf.read_cstr()?;
buf.read_cstr()?;
LogicalReplicationMessage::Type
}
INSERT_BYTE => {
let transaction_id = match logical_replication_settings.streaming {
true => Some(buf.read_i32::<BigEndian>()?),
false => None,
};
let o_id = buf.read_u32::<BigEndian>()?;
let byte = buf.read_u8()?;
let tuple = match byte {
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unexpected tuple byte `{}`", byte),
)));
}
};
LogicalReplicationMessage::Insert(InsertBody::new(transaction_id, o_id, tuple))
}
UPDATE_BYTE => {
let transaction_id = match logical_replication_settings.streaming {
true => Some(buf.read_i32::<BigEndian>()?),
false => None,
};
let o_id = buf.read_u32::<BigEndian>()?;
let byte = buf.read_u8()?;
let mut key_tuple = None;
let mut old_tuple = None;
let new_tuple = match byte {
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
TUPLE_OLD_BYTE | TUPLE_KEY_BYTE => {
if byte == TUPLE_OLD_BYTE {
old_tuple = Some(TupleData::parse(&mut buf)?);
} else {
key_tuple = Some(TupleData::parse(&mut buf)?);
}
match buf.read_u8()? {
TUPLE_NEW_BYTE => TupleData::parse(&mut buf)?,
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unexpected tuple byte `{}`", byte),
)));
}
}
}
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown tuple byte `{}`", byte),
)));
}
};
LogicalReplicationMessage::Update(UpdateBody::new(
transaction_id,
o_id,
old_tuple,
key_tuple,
new_tuple,
))
}
DELETE_BYTE => {
let transaction_id = match logical_replication_settings.streaming {
true => Some(buf.read_i32::<BigEndian>()?),
false => None,
};
let o_id = buf.read_u32::<BigEndian>()?;
let tag = buf.read_u8()?;
let mut key_tuple = None;
let mut old_tuple = None;
match tag {
TUPLE_OLD_BYTE => old_tuple = Some(TupleData::parse(&mut buf)?),
TUPLE_KEY_BYTE => key_tuple = Some(TupleData::parse(&mut buf)?),
tag => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown tuple tag `{}`", tag),
)));
}
}
LogicalReplicationMessage::Delete(DeleteBody::new(
transaction_id,
o_id,
old_tuple,
key_tuple,
))
}
byte => {
return Err(ConversionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown replication message tag `{}`", byte),
)));
}
};
Ok(logical_replication_message)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum ReplicationMessage {
XLogData(XLogDataBody),
PrimaryKeepAlive(PrimaryKeepAliveBody),
}
impl ReplicationMessage {
pub fn parse(buf: Bytes) -> io::Result<Self> {
let (byte, mut message) = buf.split_first().unwrap();
let replication_message = match *byte {
X_LOG_DATA_BYTE => {
let len = buf.len();
let wal_start = message.read_u64::<BigEndian>()?;
let wal_end = message.read_u64::<BigEndian>()?;
let timestamp = message.read_i64::<BigEndian>()?;
let len = len - message.len();
let data = buf.slice(len..);
ReplicationMessage::XLogData(XLogDataBody::new(wal_start, wal_end, timestamp, data))
}
PRIMARY_KEEPALIVE_BYTE => {
let wal_end = message.read_u64::<BigEndian>()?;
let timestamp = message.read_i64::<BigEndian>()?;
let reply = message.read_u8()?;
ReplicationMessage::PrimaryKeepAlive(PrimaryKeepAliveBody::new(
wal_end,
timestamp,
reply == 1,
))
}
byte => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unknown replication message byte `{}`", byte),
));
}
};
Ok(replication_message)
}
}

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