Compare commits
3 Commits
inkeep-end
...
draftgloba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da64385123 | ||
|
|
22b480cffe | ||
|
|
b406428ace |
@@ -47,7 +47,6 @@ Windmill uses a workspace-based architecture with multiple crates:
|
||||
- Group related routes together
|
||||
- Use consistent response formats (JSON)
|
||||
- Follow proper authentication and authorization patterns
|
||||
- Do not forget to update backend/windmill-api/openapi.yaml after modifying an api endpoint
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
|
||||
1
.github/workflows/aider-after-review.yaml
vendored
1
.github/workflows/aider-after-review.yaml
vendored
@@ -90,5 +90,4 @@ jobs:
|
||||
with:
|
||||
needs_processing: false
|
||||
base_prompt: ${{ needs.check-and-prepare.outputs.prompt_content }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
secrets: inherit
|
||||
|
||||
198
.github/workflows/aider-common.yml
vendored
198
.github/workflows/aider-common.yml
vendored
@@ -33,11 +33,7 @@ on:
|
||||
description: "Prompt for probe-chat"
|
||||
required: false
|
||||
type: string
|
||||
default: 'I''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST. Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: ["file1.py", "file2.py"]'
|
||||
rules_files:
|
||||
description: "Rules files for Aider"
|
||||
required: false
|
||||
type: string
|
||||
default: 'I''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST. REQUEST: $FINAL_PROMPT. Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: ["file1.py", "file2.py"]'
|
||||
outputs:
|
||||
files_to_edit:
|
||||
description: "Files identified by probe-chat for editing"
|
||||
@@ -71,7 +67,6 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -119,7 +114,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v3
|
||||
@@ -129,18 +124,27 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache Aider installation
|
||||
id: cache-aider
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.local/bin/aider
|
||||
key: ${{ runner.os }}-aider-install-${{ hashFiles('**/requirements.txt', '**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-aider-install-
|
||||
|
||||
- name: Install Aider and Dependencies
|
||||
run: |
|
||||
echo "Installing Aider..."
|
||||
python -m pip install uv
|
||||
python -m venv ~/uv-env
|
||||
source ~/uv-env/bin/activate
|
||||
uv pip install configargparse==1.7
|
||||
uv pip install aider-chat==0.83.1
|
||||
uv pip install -U google-generativeai
|
||||
if [ -f ~/.local/bin/aider ] && [ -x ~/.local/bin/aider ]; then
|
||||
echo "Using cached Aider installation"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
else
|
||||
echo "Installing Aider..."
|
||||
python -m pip install aider-install; aider-install
|
||||
fi
|
||||
pip install -U google-generativeai
|
||||
sudo apt-get update && sudo apt-get install -y jq
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
echo "VIRTUAL_ENV_PATH=$HOME/uv-env" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Prompt for Aider
|
||||
id: create_prompt
|
||||
@@ -202,7 +206,7 @@ jobs:
|
||||
fi
|
||||
else
|
||||
echo "No issue title or body given. Using base prompt."
|
||||
FINAL_PROMPT_CONTENT=$(printf "%s\nINSTRUCTION:\n%s" "$BASE_PROMPT_ENV" "$INSTRUCTION_ENV")
|
||||
FINAL_PROMPT_CONTENT="$BASE_PROMPT_ENV"
|
||||
fi
|
||||
|
||||
echo "Final prompt: $FINAL_PROMPT_CONTENT"
|
||||
@@ -215,11 +219,11 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
FINAL_PROMPT: ${{ steps.create_prompt.outputs.final_prompt }}
|
||||
PROBE_PROMPT: ${{ inputs.probe_prompt }}
|
||||
run: |
|
||||
echo "Running probe-chat to find relevant files..."
|
||||
|
||||
MESSAGE_FOR_PROBE=$(printf "%s\nREQUEST:\n%s" "$PROBE_PROMPT" "$FINAL_PROMPT")
|
||||
# escape the final prompt
|
||||
printf -v MESSAGE_FOR_PROBE 'I'\''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST.\nREQUEST: %s. Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: ["file1.py", "file2.py"]' "$FINAL_PROMPT"
|
||||
|
||||
set -o pipefail
|
||||
PROBE_OUTPUT=$(npx --yes @buger/probe-chat@latest --max-iterations 50 --model-name gemini-2.5-pro-preview-05-06 --message "$MESSAGE_FOR_PROBE") || {
|
||||
@@ -252,63 +256,21 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-aider-
|
||||
|
||||
- name: Prepare branch for Aider
|
||||
id: prepare_branch
|
||||
env:
|
||||
ISSUE_ID: ${{ inputs.issue_id }}
|
||||
run: |
|
||||
if [[ "$ISSUE_ID" != "" ]]; then
|
||||
BRANCH_NAME="aider-fix-issue-${ISSUE_ID}"
|
||||
|
||||
# Check if branch exists remotely
|
||||
if git ls-remote --heads origin $BRANCH_NAME | grep -q $BRANCH_NAME; then
|
||||
echo "Branch $BRANCH_NAME already exists remotely, fetching it"
|
||||
git fetch origin $BRANCH_NAME
|
||||
git checkout $BRANCH_NAME
|
||||
git pull origin $BRANCH_NAME
|
||||
else
|
||||
echo "Creating new branch $BRANCH_NAME"
|
||||
git checkout -b $BRANCH_NAME
|
||||
fi
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# We're in a pull_request_review event
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
PR_HEAD_REF="${{ github.event.pull_request.head.ref }}"
|
||||
|
||||
echo "Handling pull_request_review for PR #$PR_NUMBER on branch $PR_HEAD_REF"
|
||||
|
||||
# Ensure we're on the correct branch
|
||||
git config pull.rebase true
|
||||
git fetch origin $PR_HEAD_REF
|
||||
git checkout $PR_HEAD_REF
|
||||
git pull origin $PR_HEAD_REF
|
||||
|
||||
echo "Using PR branch $PR_HEAD_REF for PR #$PR_NUMBER"
|
||||
echo "BRANCH_NAME=$PR_HEAD_REF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run Aider
|
||||
id: run_aider
|
||||
shell: bash
|
||||
env:
|
||||
FILES_TO_EDIT: ${{ steps.probe_files.outputs.files_to_edit }}
|
||||
FINAL_PROMPT: ${{ steps.create_prompt.outputs.final_prompt }}
|
||||
RULES_FILES: ${{ inputs.rules_files }}
|
||||
run: |
|
||||
source $VIRTUAL_ENV_PATH/bin/activate
|
||||
|
||||
echo "$FINAL_PROMPT" > .aider_final_prompt.txt
|
||||
echo "FILES_TO_EDIT: $FILES_TO_EDIT"
|
||||
|
||||
RULES=""
|
||||
if [ -n "$RULES_FILES" ]; then
|
||||
for rule in $RULES_FILES; do
|
||||
RULES="$RULES --read $rule"
|
||||
done
|
||||
fi
|
||||
|
||||
aider \
|
||||
$RULES \
|
||||
--read .cursor/rules/rust-best-practices.mdc \
|
||||
--read .cursor/rules/svelte5-best-practices.mdc \
|
||||
--read .cursor/rules/windmill-overview.mdc \
|
||||
$FILES_TO_EDIT \
|
||||
--model gemini/gemini-2.5-pro-preview-05-06 \
|
||||
--message-file .aider_final_prompt.txt \
|
||||
@@ -333,31 +295,40 @@ jobs:
|
||||
id: commit_and_push
|
||||
env:
|
||||
ISSUE_ID: ${{ inputs.issue_id }}
|
||||
BRANCH_NAME: ${{ steps.prepare_branch.outputs.BRANCH_NAME }}
|
||||
run: |
|
||||
if [[ "$ISSUE_ID" != "" ]]; then
|
||||
# Check if there are any uncommitted changes
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "Found uncommitted changes, committing them"
|
||||
git add .
|
||||
git commit -m "Aider changes"
|
||||
BRANCH_NAME="aider-fix-issue-${ISSUE_ID}"
|
||||
|
||||
# Check if branch exists remotely
|
||||
if git ls-remote --heads origin $BRANCH_NAME | grep -q $BRANCH_NAME; then
|
||||
echo "Branch $BRANCH_NAME already exists remotely, fetching it"
|
||||
git fetch origin $BRANCH_NAME
|
||||
git checkout $BRANCH_NAME
|
||||
git pull origin $BRANCH_NAME
|
||||
else
|
||||
echo "Creating new branch $BRANCH_NAME"
|
||||
git checkout -b $BRANCH_NAME
|
||||
fi
|
||||
|
||||
# Push changes to the branch
|
||||
if git push origin $BRANCH_NAME; then
|
||||
echo "Pushed to branch $BRANCH_NAME"
|
||||
echo "PR_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
echo "CHANGES_APPLIED_MESSAGE=Aider changes pushed to branch $BRANCH_NAME." >> $GITHUB_OUTPUT
|
||||
echo "CHANGES_APPLIED=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::warning::Push to PR branch $BRANCH_NAME failed."
|
||||
echo "CHANGES_APPLIED_MESSAGE=Aider ran, but failed to push changes to PR branch $BRANCH_NAME." >> $GITHUB_OUTPUT
|
||||
echo "CHANGES_APPLIED=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "Created/checked out branch $BRANCH_NAME for issue #${ISSUE_ID}"
|
||||
git push origin $BRANCH_NAME
|
||||
echo "Pushed to branch $BRANCH_NAME"
|
||||
echo "PR_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
echo "CHANGES_APPLIED_MESSAGE=Aider changes pushed to branch $BRANCH_NAME." >> $GITHUB_OUTPUT
|
||||
else
|
||||
# We're in a pull_request_review event
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
PR_HEAD_REF="${{ github.event.pull_request.head.ref }}"
|
||||
echo "Attempting to push changes to PR branch $PR_HEAD_REF"
|
||||
|
||||
echo "Handling pull_request_review for PR #$PR_NUMBER on branch $PR_HEAD_REF"
|
||||
|
||||
# Ensure we're on the correct branch
|
||||
git config pull.rebase true
|
||||
git fetch origin $PR_HEAD_REF
|
||||
git checkout $PR_HEAD_REF
|
||||
git pull origin $PR_HEAD_REF
|
||||
|
||||
echo "Attempting to push changes to PR branch $PR_HEAD_REF for PR #$PR_NUMBER"
|
||||
if git push origin $PR_HEAD_REF; then
|
||||
echo "Push to $PR_HEAD_REF successful (or no new changes to push)."
|
||||
echo "CHANGES_APPLIED_MESSAGE=Aider changes (if any) pushed to PR branch $PR_HEAD_REF." >> $GITHUB_OUTPUT
|
||||
@@ -378,20 +349,23 @@ jobs:
|
||||
PR_BRANCH: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }}
|
||||
ISSUE_NUM: ${{ inputs.issue_id }}
|
||||
ISSUE_TITLE: ${{ inputs.issue_title }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
# Create PR description in a temporary file to avoid command line length limits and ensure it stays under 40k chars
|
||||
HEADER="This PR was created automatically by Aider to fix issue #${ISSUE_NUM}."
|
||||
# if event is repository_dispatch, add the issue title to the header
|
||||
if [ "$GITHUB_EVENT_NAME" == "repository_dispatch" ]; then
|
||||
if [[ "${{ github.event.client_payload.source }}" == "linear" ]]; then
|
||||
HEADER="This PR was created automatically by Aider to fix issue #linear:${ISSUE_NUM}."
|
||||
elif [[ "${{ github.event.client_payload.source }}" == "discord" ]]; then
|
||||
HEADER="This PR was created automatically by Aider to fix issue #discord:${ISSUE_NUM}."
|
||||
fi
|
||||
# Debug: Check latest commit and branch status
|
||||
echo "Checking latest commit on branch $PR_BRANCH"
|
||||
git log -1 --pretty=format:"%h - %an, %ar : %s"
|
||||
echo "Changes not yet committed:"
|
||||
git status --porcelain
|
||||
# Check if there are any changes to commit
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "Found uncommitted changes, committing them"
|
||||
git add .
|
||||
git commit -m "Aider changes for issue #${ISSUE_NUM}"
|
||||
git push origin $PR_BRANCH
|
||||
fi
|
||||
|
||||
# Create PR description in a temporary file to avoid command line length limits and ensure it stays under 40k chars
|
||||
cat > /tmp/pr-description.md << EOL | head -c 40000
|
||||
$HEADER
|
||||
This PR was created automatically by Aider to fix issue #${ISSUE_NUM}.
|
||||
|
||||
## Aider Output
|
||||
\`\`\`
|
||||
@@ -401,16 +375,11 @@ jobs:
|
||||
|
||||
# Create PR using the file for the body content, handle errors gracefully
|
||||
set +e # Don't exit on error
|
||||
PR_TITLE="[Aider PR] Fix: ${ISSUE_TITLE}"
|
||||
if [ -z "$ISSUE_TITLE" ]; then
|
||||
PR_TITLE="[Aider PR] AI changes after request"
|
||||
fi
|
||||
gh pr create \
|
||||
--title "$PR_TITLE" \
|
||||
--title "[Aider PR] Fix: ${ISSUE_TITLE}" \
|
||||
--body-file /tmp/pr-description.md \
|
||||
--head "$PR_BRANCH" \
|
||||
--base main \
|
||||
--draft
|
||||
--base main
|
||||
PR_CREATE_EXIT_CODE=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
@@ -468,13 +437,12 @@ jobs:
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
JOB_STATUS: ${{ job.status }}
|
||||
PR_CREATED: ${{ steps.create_pr.outputs.PR_CREATED }}
|
||||
PR_URL: ${{ steps.create_pr.outputs.PR_URL }}
|
||||
run: |
|
||||
echo "Commenting on issue/PR #${{ github.event.issue.number }} to let the user know Aider has finished working on the request."
|
||||
|
||||
if [[ "$JOB_STATUS" == "success" ]]; then
|
||||
if [[ "$PR_CREATED" == "true" ]]; then
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created. $PR_URL"
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created."
|
||||
else
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR."
|
||||
fi
|
||||
@@ -492,14 +460,12 @@ jobs:
|
||||
JOB_STATUS: ${{ job.status }}
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
PR_CREATED: ${{ steps.create_pr.outputs.PR_CREATED }}
|
||||
PR_URL: ${{ steps.create_pr.outputs.PR_URL }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
SOURCE: ${{ github.event.client_payload.source }}
|
||||
run: |
|
||||
echo "Notifying user about Aider completion status for $SOURCE request #${{ github.event.client_payload.issue_id }}"
|
||||
echo "Commenting on linear issue #${{ github.event.client_payload.issue_id }} to let the user know Aider has finished working on the request."
|
||||
|
||||
if [[ "$JOB_STATUS" == "success" ]]; then
|
||||
if [[ "$PR_CREATED" == "true" ]]; then
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created. $PR_URL"
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created."
|
||||
else
|
||||
COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR."
|
||||
fi
|
||||
@@ -507,16 +473,8 @@ jobs:
|
||||
COMMENT_BODY="⚠️ Aider encountered issues while working on your request. Please check the workflow logs for details."
|
||||
fi
|
||||
|
||||
if [[ "$SOURCE" == "discord" ]]; then
|
||||
curl -X POST \
|
||||
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://discord.com/api/v10/channels/${{ github.event.client_payload.channel_id }}/messages" \
|
||||
-d "{\"content\":\"${COMMENT_BODY}\"}"
|
||||
else
|
||||
curl -X POST \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.linear.app/graphql" \
|
||||
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"${COMMENT_BODY}\\\" }) { success } }\"}"
|
||||
fi
|
||||
curl -X POST \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.linear.app/graphql" \
|
||||
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"${COMMENT_BODY}\\\" }) { success } }\"}"
|
||||
|
||||
25
.github/workflows/aider.yaml
vendored
25
.github/workflows/aider.yaml
vendored
@@ -72,7 +72,6 @@ jobs:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
run: |
|
||||
echo "Determining inputs for Aider..."
|
||||
ISSUE_TITLE_VAL=""
|
||||
@@ -92,25 +91,12 @@ jobs:
|
||||
|
||||
if [[ ! -z "$PR_BODY_VAL" ]]; then
|
||||
REFERENCED_ISSUE=""
|
||||
if [[ "$PR_BODY_VAL" =~ \#linear:([a-f0-9-]+) ]]; then
|
||||
if [[ "$PR_BODY_VAL" =~ \#([0-9]+) ]]; then
|
||||
REFERENCED_ISSUE="${BASH_REMATCH[1]}"
|
||||
echo "Found referenced Linear issue #$REFERENCED_ISSUE in PR description"
|
||||
LINEAR_ISSUE_JSON=$(curl -s -H "Authorization: $LINEAR_API_KEY" \
|
||||
"https://api.linear.app/graphql" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"query\":\"query { issue(id: \\\"$REFERENCED_ISSUE\\\") { title description } }\"}")
|
||||
|
||||
if [[ $? -eq 0 && ! "$LINEAR_ISSUE_JSON" =~ "error" ]]; then
|
||||
ISSUE_TITLE_VAL=$(jq -r '.data.issue.title // ""' <<< "$LINEAR_ISSUE_JSON")
|
||||
ISSUE_BODY_VAL=$(jq -r '.data.issue.description // ""' <<< "$LINEAR_ISSUE_JSON")
|
||||
echo "Successfully fetched Linear issue details"
|
||||
else
|
||||
echo "Error fetching Linear issue details for #$REFERENCED_ISSUE"
|
||||
fi
|
||||
elif [[ "$PR_BODY_VAL" =~ \#([0-9]+) ]]; then
|
||||
REFERENCED_ISSUE="${BASH_REMATCH[1]}"
|
||||
echo "Found referenced GitHub issue #$REFERENCED_ISSUE in PR description"
|
||||
fi
|
||||
|
||||
if [[ ! -z "$REFERENCED_ISSUE" ]]; then
|
||||
echo "Found referenced issue #$REFERENCED_ISSUE in PR description"
|
||||
|
||||
ISSUE_DETAILS_JSON=$(gh issue view "$REFERENCED_ISSUE" --json title,body --repo "$GITHUB_REPOSITORY")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
@@ -161,5 +147,4 @@ jobs:
|
||||
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
|
||||
instruction: ${{ needs.check-and-prepare.outputs.comment_content }}
|
||||
issue_id: ${{ github.event.issue.number }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
secrets: inherit
|
||||
|
||||
2
.github/workflows/backend-check.yml
vendored
2
.github/workflows/backend-check.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
timeout-minutes: 16
|
||||
run: |
|
||||
mkdir -p fake_frontend_build
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh)
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
|
||||
|
||||
check_ee:
|
||||
runs-on: ubicloud-standard-8
|
||||
|
||||
4
.github/workflows/backend-test.yml
vendored
4
.github/workflows/backend-test.yml
vendored
@@ -45,9 +45,9 @@ jobs:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.1.43
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.6.2"
|
||||
version: "0.4.18"
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
|
||||
4
.github/workflows/build-publish-rh-image.yml
vendored
4
.github/workflows/build-publish-rh-image.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
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,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -51,7 +51,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,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
25
.github/workflows/claude.yml
vendored
25
.github/workflows/claude.yml
vendored
@@ -13,10 +13,10 @@ on:
|
||||
jobs:
|
||||
check-membership:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai') && !contains(github.event.comment.user.login, '[bot]')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai') && !contains(github.event.comment.user.login, '[bot]')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai') && !contains(github.event.review.user.login, '[bot]')) ||
|
||||
(github.event_name == 'issues' && contains(github.event.issue.body, '/ai') && !contains(github.event.issue.user.login, '[bot]'))
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/aider') && !contains(github.event.comment.user.login, '[bot]')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/aider') && !contains(github.event.comment.user.login, '[bot]')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/aider') && !contains(github.event.review.user.login, '[bot]')) ||
|
||||
(github.event_name == 'issues' && contains(github.event.issue.body, '/aider') && !contains(github.event.issue.user.login, '[bot]'))
|
||||
runs-on: ubicloud-standard-2
|
||||
outputs:
|
||||
is_member: ${{ steps.check-membership.outputs.is_member }}
|
||||
@@ -69,17 +69,6 @@ jobs:
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
allowed_tools: "mcp__github__create_pull_request,Bash(npm run check),Bash(npm install),Bash(cargo check),Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y)"
|
||||
custom_instructions: "IMPORTANT INSTRUCTIONS:
|
||||
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main, with the title starting with [Claude PR].
|
||||
- If you made changes to the frontend code, run npm install, then npm run generate-backend-client, then npm run check. You can ignore warnings that are reported by the check script, but fix the errors.
|
||||
- If you made changes to the backend code, install Rust and then run cargo check. You can ignore warnings that are reported by the check script, but fix the errors.
|
||||
- DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
|
||||
AVAILABLE TOOLS:
|
||||
- mcp__github__create_pull_request: Create a PR from a branch to main
|
||||
- Bash(npm run check): Run the check script. You should run this tool after making changes to the frontend code.
|
||||
- Bash(npm install): Install dependencies. You need this to run npm run check.
|
||||
- Bash(npm run generate-backend-client): Generate the backend client. You need this to run npm run check.
|
||||
- Bash(cargo check): Run the cargo check script. You should run this tool after making changes to the backend code.
|
||||
- Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y): Install Rust. You need this to run cargo check."
|
||||
trigger_phrase: "/ai"
|
||||
allowed_tools: "mcp__github__create_pull_request"
|
||||
custom_instructions: "IMPORTANT: Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a PR from that branch to main, with the title starting with [Claude PR]"
|
||||
trigger_phrase: "/aider"
|
||||
|
||||
2
.github/workflows/discord-notification.yml
vendored
2
.github/workflows/discord-notification.yml
vendored
@@ -29,4 +29,4 @@ jobs:
|
||||
DISCORD_GUILD_ID: "930051556043276338"
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
secrets:
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_PR_BOT_TOKEN }}
|
||||
|
||||
4
.github/workflows/docker-image.yml
vendored
4
.github/workflows/docker-image.yml
vendored
@@ -92,7 +92,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp,private
|
||||
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,private,deno_core,mcp
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
|
||||
@@ -21,29 +21,18 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Acknowledge Request
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
|
||||
run: |
|
||||
if [[ "${{ github.event.client_payload.source }}" == "linear" ]]; then
|
||||
echo "Commenting on Linear issue #${{ github.event.client_payload.issue_id }} to acknowledge the request."
|
||||
curl -X POST \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.linear.app/graphql" \
|
||||
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes.\\\" }) { success } }\"}"
|
||||
elif [[ "${{ github.event.client_payload.source }}" == "discord" ]]; then
|
||||
echo "Commenting on Discord thread #${{ github.event.client_payload.channel_id }} to acknowledge the request."
|
||||
curl -X POST \
|
||||
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://discord.com/api/v10/channels/${{ github.event.client_payload.channel_id }}/messages" \
|
||||
-d "{\"content\":\"🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes.\"}"
|
||||
fi
|
||||
echo "Commenting on Linear issue #${{ github.event.client_payload.issue_id }} to acknowledge the request."
|
||||
curl -X POST \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.linear.app/graphql" \
|
||||
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes.\\\" }) { success } }\"}"
|
||||
|
||||
- name: Determine inputs for Aider
|
||||
id: determine_inputs
|
||||
@@ -76,5 +65,4 @@ jobs:
|
||||
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
|
||||
instruction: ${{ needs.check-and-prepare.outputs.instruction }}
|
||||
issue_id: ${{ github.event.client_payload.issue_id }}
|
||||
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
|
||||
secrets: inherit
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -53,7 +53,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,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
fi
|
||||
# 2) get the first message in that thread
|
||||
messages=$(curl -H "Authorization: Bot $BOT_TOKEN" \
|
||||
"https://discord.com/api/v10/channels/$thread_id/messages")
|
||||
"https://discord.com/api/v10/channels/$thread_id/messages?limit=1")
|
||||
message_id=$(echo "$messages" | jq -r '.[-1].id')
|
||||
|
||||
if [ -z "$message_id" ]; then
|
||||
|
||||
34
.github/workflows/validate-openapi.yml
vendored
34
.github/workflows/validate-openapi.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Validate OpenAPI Spec
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'backend/windmill-api/openapi*'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'backend/windmill-api/openapi*'
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install openapi-generator-cli
|
||||
run: npm install @openapitools/openapi-generator-cli -g
|
||||
|
||||
- name: Validate openapi.yaml
|
||||
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi.yaml
|
||||
|
||||
- name: Validate openapi-deref.json
|
||||
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.json
|
||||
|
||||
# Does not work well with dereferenced yaml
|
||||
# - name: Validate openapi-deref.yaml
|
||||
# run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.yaml
|
||||
|
||||
86
CHANGELOG.md
86
CHANGELOG.md
@@ -1,91 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.494.0](https://github.com/windmill-labs/windmill/compare/v1.493.4...v1.494.0) (2025-05-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* array of s3 objects in input maker ([806d669](https://github.com/windmill-labs/windmill/commit/806d66972568d21a1621acd1b30db5ae9b217341))
|
||||
* **rust:** shared build directory ([#5610](https://github.com/windmill-labs/windmill/issues/5610)) ([ed61d97](https://github.com/windmill-labs/windmill/commit/ed61d9770031c1a04908880dbd3e5fb692df9946))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow disable tabs for sidebar/accordion tabs ([#5838](https://github.com/windmill-labs/windmill/issues/5838)) ([80277d1](https://github.com/windmill-labs/windmill/commit/80277d14d02e8e596c7002326946142226d382a6))
|
||||
|
||||
## [1.493.4](https://github.com/windmill-labs/windmill/compare/v1.493.3...v1.493.4) (2025-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* templatev2 delete issue ([#5834](https://github.com/windmill-labs/windmill/issues/5834)) ([ed3ad32](https://github.com/windmill-labs/windmill/commit/ed3ad327a235c16b9f3aa7f8edeefe61b0c01da3))
|
||||
|
||||
## [1.493.3](https://github.com/windmill-labs/windmill/compare/v1.493.2...v1.493.3) (2025-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* evalv2 prohibit component delete ([e302aa3](https://github.com/windmill-labs/windmill/commit/e302aa38b5977dd406ae05e1d8dbb74cb7dc3d17))
|
||||
* faster layout for larger graphs ([8d12bcc](https://github.com/windmill-labs/windmill/commit/8d12bcc8ee2991909ea0d9bb57f04f0d4106c69f))
|
||||
|
||||
## [1.493.2](https://github.com/windmill-labs/windmill/compare/v1.493.1...v1.493.2) (2025-05-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve monaco editor memory leak ([e0f4f83](https://github.com/windmill-labs/windmill/commit/e0f4f83ebf4416c3bcc24433a7bf606349e1f75a))
|
||||
* improve monaco javascript extra lib refresh ([7b70348](https://github.com/windmill-labs/windmill/commit/7b70348b4bba3726e3fb26c964219a5a2aa6af55))
|
||||
|
||||
## [1.493.1](https://github.com/windmill-labs/windmill/compare/v1.493.0...v1.493.1) (2025-05-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve monaco javascript extra lib refresh ([a2c8ea6](https://github.com/windmill-labs/windmill/commit/a2c8ea69a3962a350273717cd237d8a96523fd00))
|
||||
|
||||
## [1.493.0](https://github.com/windmill-labs/windmill/compare/v1.492.1...v1.493.0) (2025-05-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add aws oidc support for instance s3 storage ([#5810](https://github.com/windmill-labs/windmill/issues/5810)) ([5b96bcc](https://github.com/windmill-labs/windmill/commit/5b96bccedd6e68fea631580dd49338301ad0305f))
|
||||
* duckdb sql lang support ([#5761](https://github.com/windmill-labs/windmill/issues/5761)) ([fdefd4b](https://github.com/windmill-labs/windmill/commit/fdefd4be9398b9610a539360353fd61b521732d4))
|
||||
* **python:** inline script metadata (PEP 723) ([#5712](https://github.com/windmill-labs/windmill/issues/5712)) ([2622253](https://github.com/windmill-labs/windmill/commit/26222539e66bce7e88f86a7e5917e6ca99350865))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing http_trigger_version_seq grants ([#5816](https://github.com/windmill-labs/windmill/issues/5816)) ([306f3ea](https://github.com/windmill-labs/windmill/commit/306f3eabd1c03fa904b0e59438de124a0e680597))
|
||||
* avoid monaco memory leak ([0d459d5](https://github.com/windmill-labs/windmill/commit/0d459d5d223728270854e37715ecc1663ede9870))
|
||||
* error handler node rendering at top level ([feae9b0](https://github.com/windmill-labs/windmill/commit/feae9b09240ba306c007013a36d2aefb0b273766))
|
||||
* **frontend:** auto completion and render of tailwind classes in app editor ([#5817](https://github.com/windmill-labs/windmill/issues/5817)) ([5897e7e](https://github.com/windmill-labs/windmill/commit/5897e7e01b8839425c30c2a97481ef7bb9090661))
|
||||
|
||||
## [1.492.1](https://github.com/windmill-labs/windmill/compare/v1.492.0...v1.492.1) (2025-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix strum compile ([59f6024](https://github.com/windmill-labs/windmill/commit/59f6024cbdaface9c9f0ed61c4a415a13b558515))
|
||||
|
||||
## [1.492.0](https://github.com/windmill-labs/windmill/compare/v1.491.5...v1.492.0) (2025-05-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* job search pagination + result count ([#5789](https://github.com/windmill-labs/windmill/issues/5789)) ([55ae766](https://github.com/windmill-labs/windmill/commit/55ae76648475ce9ff14b2fa33b2a71b90fbd50a1))
|
||||
* **python:** add annotation to skip result post-processing ([#5769](https://github.com/windmill-labs/windmill/issues/5769)) ([07c2ff5](https://github.com/windmill-labs/windmill/commit/07c2ff5668f4725a3b9a8a2655248b0945ac251c))
|
||||
* shift/ctrl+click/enter to open ctrl+k menu results in new tab ([#5800](https://github.com/windmill-labs/windmill/issues/5800)) ([66a997a](https://github.com/windmill-labs/windmill/commit/66a997afc399de2d592c469faf9a5b2cd6433aac))
|
||||
* triggers git sync ([#5766](https://github.com/windmill-labs/windmill/issues/5766)) ([065a814](https://github.com/windmill-labs/windmill/commit/065a814d35a5749725c2ada1155481abba782684))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve app css consistency ([88482c3](https://github.com/windmill-labs/windmill/commit/88482c3bd76ddad16738354f7531d16fa806ad2f))
|
||||
* improve docker mode unexpected exit handling ([7c24fbc](https://github.com/windmill-labs/windmill/commit/7c24fbcef2ecfe5fc034870c4c65dd80513301a4))
|
||||
* postgres trigger ssl issue ([#5790](https://github.com/windmill-labs/windmill/issues/5790)) ([b9a776c](https://github.com/windmill-labs/windmill/commit/b9a776c97b3411af18e58cde7a070c4955aaaab4))
|
||||
* specify using inline type in system prompt for AI ([#5787](https://github.com/windmill-labs/windmill/issues/5787)) ([791296f](https://github.com/windmill-labs/windmill/commit/791296fa41c5bc45c32944db8bc1b66e1515ea82))
|
||||
* workspace preprocessor improvements ([#5784](https://github.com/windmill-labs/windmill/issues/5784)) ([30edcdf](https://github.com/windmill-labs/windmill/commit/30edcdfe0e950b0ab850942bcbc9b4b5ff4fc00c))
|
||||
|
||||
## [1.491.5](https://github.com/windmill-labs/windmill/compare/v1.491.4...v1.491.5) (2025-05-17)
|
||||
|
||||
|
||||
|
||||
74
CLAUDE.md
74
CLAUDE.md
@@ -1,3 +1,71 @@
|
||||
To have an overview of what this app does, see @.cursor/rules/windmill-overview.mdc
|
||||
For backend modifications, follow the rules mentioned here @.cursor/rules/rust-best-practices.mdc
|
||||
For frontend modifications, follow the rules mentioned here @.cursor/rules/svelte5-best-practices.mdc
|
||||
# Windmill Overview
|
||||
|
||||
Windmill is an open-source developer platform for building internal tools, API integrations, background jobs, workflows, and user interfaces. It offers a unified system where scripts are automatically turned into sharable UIs and can be composed into flows or embedded in custom applications.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
- **Script Development and Execution**: Write and run scripts in Python, TypeScript/JavaScript (Deno/Bun), Go, Bash, SQL, and other languages
|
||||
- **Workflow Orchestration**: Compose scripts into multi-step flows with conditional logic, loops, and error handling
|
||||
- **UI Generation**: Automatically generate UIs from scripts or build custom applications with a low-code editor
|
||||
- **Job Scheduling**: Trigger scripts and flows on schedules, webhooks, or external events
|
||||
- **Resource Management**: Securely store and use credentials, databases, and other connections
|
||||
|
||||
## Platform Architecture
|
||||
|
||||
The Windmill platform consists of several key components:
|
||||
|
||||
- **Frontend UI**: Web-based interface for script and flow development, app building, and result visualization
|
||||
- **API Server**: Central API that handles authentication, resource management, and job coordination
|
||||
- **Workers**: Execute scripts in their respective environments with proper sandboxing
|
||||
- **Database**: PostgreSQL database for storage of scripts, flows, resources, job results, and more
|
||||
- **Job Queue**: Queue system for managing job execution, implemented in PostgreSQL
|
||||
- **Client Libraries**: Libraries for interacting with Windmill from Python, TypeScript, or command line
|
||||
|
||||
# Windmill Backend Architecture
|
||||
|
||||
The Windmill backend is written in Rust and consists of several services working together. These services are designed for horizontal scaling with stateless API servers and workers that can be deployed across multiple machines.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **API Server (`windmill-api`)**: Handles HTTP requests, authentication, and resource management
|
||||
- **Queue Manager (`windmill-queue`)**: Manages the job queue in PostgreSQL
|
||||
- **Worker System (`windmill-worker`)**: Executes jobs in sandboxed environments
|
||||
- **Common Utilities (`windmill-common`)**: Shared code used by multiple services
|
||||
- **Git Sync (`windmill-git-sync`)**: Synchronizes scripts with Git repositories
|
||||
|
||||
## Job Execution System
|
||||
|
||||
The job execution process follows these steps:
|
||||
|
||||
1. The API server receives a request to run a script or flow and creates a job record in the database
|
||||
2. The job is added to the queue system in PostgreSQL
|
||||
3. Workers continuously poll the queue for jobs matching their capabilities
|
||||
4. When a job is picked up, it's routed to the appropriate language executor
|
||||
5. The script is executed in a sandboxed environment using NSJAIL for security
|
||||
6. Results are processed and stored in the database
|
||||
7. For flows, each step creates a new job that goes through the same process
|
||||
|
||||
Windmill supports worker tags and groups to route jobs to workers with specific capabilities or resource access.
|
||||
|
||||
# Windmill Frontend Architecture
|
||||
|
||||
The Windmill frontend is built with Svelte and provides several key interfaces for interacting with the platform.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **Script Builder**: Code editor with language support, schema inference, and dependency management
|
||||
- **Flow Builder**: Visual editor for creating multi-step workflows with branching and looping
|
||||
- **App Editor**: Grid-based editor for building custom UIs that integrate scripts and flows
|
||||
- **Schema Form System**: Generates form interfaces from script parameters automatically
|
||||
- **Result Viewer**: Visualizes job results, logs, and execution status
|
||||
|
||||
The frontend uses the Monaco editor (same as VS Code) for code editing, with specialized language support for all supported script languages.
|
||||
|
||||
## UI Framework
|
||||
|
||||
The frontend is built with Svelte, providing a reactive and component-based architecture. Key frontend technologies include:
|
||||
|
||||
- **Svelte/SvelteKit**: Core framework for UI components and routing
|
||||
- **Monaco Editor**: Code editing experience similar to VS Code
|
||||
- **Schema Form**: Automatic UI generation from TypeScript/JSON schemas
|
||||
- **Tailwind CSS**: Utility-first CSS framework for styling
|
||||
|
||||
4
backend/.gitignore
vendored
4
backend/.gitignore
vendored
@@ -5,6 +5,4 @@ oauth2.json
|
||||
tracing.folded
|
||||
heaptrack*
|
||||
index/
|
||||
windmill-api/openapi-*.*
|
||||
.duckdb/*
|
||||
*ee.rs
|
||||
windmill-api/openapi-*.*
|
||||
@@ -1 +0,0 @@
|
||||
!*ee.rs
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM sqs_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": "13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf"
|
||||
}
|
||||
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
20
backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SHOW WAL_LEVEL;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "wal_level",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48"
|
||||
}
|
||||
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
20
backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT pubname AS publication_name FROM pg_publication;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "publication_name",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45"
|
||||
}
|
||||
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
26
backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n slot_name,\n active\n FROM\n pg_replication_slots \n WHERE \n plugin = 'pgoutput' AND\n slot_type = 'logical';\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "slot_name",
|
||||
"type_info": "Name"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
|
||||
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,5 +11,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1"
|
||||
"hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM gcp_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": "6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9"
|
||||
}
|
||||
22
backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json
generated
Normal file
22
backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT slot_name FROM pg_replication_slots where slot_name = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "slot_name",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396"
|
||||
}
|
||||
22
backend/.sqlx/query-7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2.json
generated
Normal file
22
backend/.sqlx/query-7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n active_pid \n FROM \n pg_replication_slots \n WHERE \n slot_name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "active_pid",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2"
|
||||
}
|
||||
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
40
backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n puballtables AS all_table,\n pubinsert AS insert,\n pubupdate AS update,\n pubdelete AS delete\n FROM\n pg_publication\n WHERE\n pubname = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "all_table",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "insert",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "update",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "delete",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM mqtt_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": "a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d"
|
||||
}
|
||||
22
backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json
generated
Normal file
22
backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT pubname FROM pg_publication WHERE pubname = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "pubname",
|
||||
"type_info": "Name"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60"
|
||||
}
|
||||
40
backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json
generated
Normal file
40
backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n schemaname AS schema_name,\n tablename AS table_name,\n CASE\n WHEN array_length(attnames, 1) = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = pg_publication_tables.schemaname AND table_name = pg_publication_tables.tablename)\n THEN NULL\n ELSE attnames\n END 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,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154"
|
||||
}
|
||||
3
backend/.vscode/settings.json
vendored
3
backend/.vscode/settings.json
vendored
@@ -11,6 +11,5 @@
|
||||
"remote.autoForwardPorts": true,
|
||||
"conventionalCommits.scopes": [
|
||||
"restructring triggers, decoding trigger message on work"
|
||||
],
|
||||
"rust-analyzer.cargo.features": ["postgres_trigger"]
|
||||
]
|
||||
}
|
||||
|
||||
104
backend/CLAUDE.md
Normal file
104
backend/CLAUDE.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Windmill Backend - Rust Best Practices
|
||||
|
||||
## Project Structure
|
||||
|
||||
Windmill uses a workspace-based architecture with multiple crates:
|
||||
|
||||
- **windmill-api**: API server functionality
|
||||
- **windmill-worker**: Job execution
|
||||
- **windmill-common**: Shared code used by all crates
|
||||
- **windmill-queue**: Job & flow queuing
|
||||
- **windmill-audit**: Audit logging
|
||||
- Other specialized crates (git-sync, autoscaling, etc.)
|
||||
|
||||
## Adding New Code
|
||||
|
||||
### Module Organization
|
||||
|
||||
- Place new code in the appropriate crate based on functionality
|
||||
- For API endpoints, create or modify files in `windmill-api/src/` organized by domain
|
||||
- For shared functionality, use `windmill-common/src/`
|
||||
- Use the `_ee.rs` suffix for enterprise-only modules
|
||||
- Follow existing patterns for file structure and organization
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use the custom `Error` enum from `windmill-common::error`
|
||||
- Return `Result<T, Error>` or `JsonResult<T>` for functions that can fail
|
||||
- Use the `?` operator for error propagation
|
||||
- Add location tracking to errors using `#[track_caller]`
|
||||
|
||||
### Database Operations
|
||||
|
||||
- Use `sqlx` for database operations with prepared statements
|
||||
- Leverage existing database helper functions in `db.rs` modules
|
||||
- Use transactions for multi-step operations
|
||||
- Handle database errors properly
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- Follow existing patterns in the `windmill-api` crate
|
||||
- Use axum's routing system and extractors
|
||||
- Group related routes together
|
||||
- Use consistent response formats (JSON)
|
||||
- Follow proper authentication and authorization patterns
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
When generating code, especially involving `serde`, `sqlx`, and `tokio`, prioritize performance by applying the following principles:
|
||||
|
||||
### Serde Optimizations (Serialization & Deserialization)
|
||||
|
||||
- **Specify Structure Explicitly:** When defining structs for Serde (`#[derive(Serialize, Deserialize)]`), use `#[serde(...` attributes extensively. This includes:
|
||||
- `#[serde(rename = "...")]` or `#[serde(alias = "...")]` to map external names precisely, avoiding dynamic lookups.
|
||||
- `#[serde(default)]` for optional fields with default values, reducing parsing complexity.
|
||||
- `#[serde(skip_serializing_if = "...")]` to avoid writing fields that meet a certain condition (e.g., `Option::is_none()`, `Vec::is_empty()`, or a custom function), reducing output size and serialization work.
|
||||
- `#[serde(skip_serializing)]` or `#[serde(skip_deserializing)]` for fields that should _not_ be included.
|
||||
- **Prefer Borrowing:** Where possible and safe (data lifetime allows), use `Cow<'a, str>` or `&'a str` (with `#[serde(borrow)]`) instead of `String` for string fields during deserialization. This avoids allocating new strings, enabling zero-copy reading from the input buffer. Apply this principle to byte slices (`&'a [u8]` / `Cow<'a, [u8]>`) and potentially borrowed vectors as well.
|
||||
- **Avoid Intermediate `Value`:** Unless the data structure is truly dynamic or unknown at compile time, deserialize directly into a well-defined struct or enum rather than into `serde_json::Value` (or equivalent for other formats). This avoids unnecessary heap allocations and type switching.
|
||||
|
||||
### SQLx Optimizations (Database Interaction)
|
||||
|
||||
- **Select Only Necessary Columns:** In `SELECT` queries, list specific column names rather than using `SELECT *`. This reduces data transferred from the database and the work needed for hydration/deserialization.
|
||||
- **Batch Operations:** For multiple `INSERT`, `UPDATE`, or `DELETE` statements, prefer executing them in a single query if the database and driver support it efficiently (e.g., `INSERT INTO ... VALUES (...), (...), ...`). This minimizes round trips to the database.
|
||||
- **Avoid N+1 Queries:** Do not loop through results of one query and execute a separate query for each item (e.g., fetching users, then querying for each user's profile in a loop). Instead, use JOINs or a single query with an `IN` clause to fetch related data efficiently.
|
||||
- **Deserialize Directly:** Use `#[derive(FromRow)]` on structs and ensure the struct fields match the selected columns in the query. This allows SQLx to hydrate objects directly, avoiding intermediate data structures.
|
||||
- **Parameterize Queries:** Always use SQLx's query methods (`.bind(...)`) to pass values as parameters rather than string formatting. This prevents SQL injection and allows the database to cache query plans, improving performance on repeated executions.
|
||||
|
||||
### Tokio Optimizations (Asynchronous Runtime)
|
||||
|
||||
- **Avoid Blocking Operations:** **Crucially**, never perform blocking operations (synchronous file I/O, `std::thread::sleep`, CPU-bound loops, `std::sync::Mutex::lock`, blocking network calls without `tokio::net`) directly within an `async fn` or a standard `tokio::spawn` task. Blocking pauses the entire worker thread, potentially starving other tasks. Use `tokio::task::spawn_blocking` for CPU-intensive work or blocking I/O.
|
||||
- **Use Tokio's Async Primitives:** Prefer `tokio::sync` (channels, mutexes, semaphores), `tokio::io`, `tokio::net`, and `tokio::time` over their `std` counterparts in asynchronous contexts. These are designed to yield control back to the scheduler.
|
||||
- **Manage Concurrency:** Be mindful of how many tasks are spawned. Creating a new task for every tiny piece of work can introduce overhead. Group related asynchronous operations where appropriate.
|
||||
- **Handle Shared State Efficiently:** Use `Arc` for shared ownership in concurrent tasks. When shared state needs mutation, prefer `tokio::sync::Mutex` over `std::sync::Mutex` in `async` code. Consider `tokio::sync::RwLock` if reads significantly outnumber writes. Minimize the duration for which locks are held.
|
||||
- **Understand `.await`:** Place `.await` strategically to allow the runtime to switch to other ready tasks. Ensure that `.await` points to genuinely asynchronous operations.
|
||||
- **Backpressure:** If dealing with data streams or queues between tasks, implement backpressure mechanisms (e.g., bounded channels like `tokio::sync::mpsc::channel`) to prevent one component from overwhelming another or critical resources like the database.
|
||||
|
||||
## Enterprise Features
|
||||
|
||||
- Use feature flags for enterprise functionality
|
||||
- Conditionally compile with `#[cfg(feature = "enterprise")]`
|
||||
- Isolate enterprise code in separate modules
|
||||
|
||||
## Code Style
|
||||
|
||||
- Group imports by external and internal crates
|
||||
- Place struct/enum definitions before implementations
|
||||
- Group similar functionality together
|
||||
- Use descriptive naming consistent with the codebase
|
||||
- Follow existing patterns for async code using tokio
|
||||
|
||||
## Testing
|
||||
|
||||
- Write unit tests for core functionality
|
||||
- Use the `#[cfg(test)]` module for test code
|
||||
- For database tests, use the existing test utilities
|
||||
|
||||
## Common Crates Used
|
||||
|
||||
- **tokio**: For async runtime
|
||||
- **axum**: For web server and routing
|
||||
- **sqlx**: For database operations
|
||||
- **serde**: For serialization/deserialization
|
||||
- **tracing**: For logging and diagnostics
|
||||
- **reqwest**: For HTTP client functionality
|
||||
832
backend/Cargo.lock
generated
832
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.494.0"
|
||||
version = "1.491.5"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -32,7 +32,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.494.0"
|
||||
version = "1.491.5"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -49,7 +49,6 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
@@ -60,7 +59,7 @@ embedding = ["windmill-api/embedding"]
|
||||
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "dep:object_store"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
|
||||
flow_testing = ["windmill-worker/flow_testing"]
|
||||
openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect"]
|
||||
openidconnect = ["windmill-api/openidconnect"]
|
||||
cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud", "windmill-api/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", "windmill-common/tantivy", "enterprise", "parquet"]
|
||||
@@ -84,18 +83,17 @@ zip = ["windmill-api/zip"]
|
||||
static_frontend = ["windmill-api/static_frontend"]
|
||||
scoped_cache = ["windmill-common/scoped_cache"]
|
||||
# Languages
|
||||
python = ["windmill-worker/python", "windmill-api/python"]
|
||||
python = ["windmill-worker/python"]
|
||||
rust = ["windmill-worker/rust"]
|
||||
mysql = ["windmill-worker/mysql"]
|
||||
oracledb = ["windmill-worker/oracledb"]
|
||||
duckdb = ["windmill-worker/duckdb"]
|
||||
mssql = ["windmill-worker/mssql"]
|
||||
bigquery = ["windmill-worker/bigquery"]
|
||||
php = ["windmill-worker/php"]
|
||||
csharp = ["windmill-worker/csharp"]
|
||||
nu = ["windmill-worker/nu"]
|
||||
java = ["windmill-worker/java"]
|
||||
all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
all_languages = [ "python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
|
||||
|
||||
[patch.crates-io]
|
||||
@@ -137,12 +135,10 @@ quote.workspace = true
|
||||
memchr.workspace = true
|
||||
v8 = { workspace = true, optional = true }
|
||||
rustls.workspace = true
|
||||
pep440_rs.workspace = true
|
||||
systemstat.workspace = true
|
||||
size.workspace = true
|
||||
strum.workspace = true
|
||||
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { optional = true, workspace = true }
|
||||
tikv-jemalloc-sys = { optional = true, workspace = true }
|
||||
@@ -223,7 +219,6 @@ git-version = "^0"
|
||||
malachite = "=0.4.18"
|
||||
malachite-bigint = "=0.2.0"
|
||||
rustpython-parser = "^0"
|
||||
pep440_rs = "0.7.3"
|
||||
php-parser-rs = { git = "https://github.com/php-rust-tools/parser", rev = "ec4cb411dec09450946ef57920b7ffced7f6495d" }
|
||||
cron = "^0"
|
||||
mail-send = { version = "0.4.0", features = ["builder"], default-features=false }
|
||||
@@ -240,7 +235,6 @@ json-pointer = "^0"
|
||||
itertools = "^0"
|
||||
regex = "^1"
|
||||
semver = "^1"
|
||||
duckdb = { version = "1.2.2", features = ["bundled"] }
|
||||
|
||||
v8 = "=130.0.7" # Exact version NOTE: Do not forget to update version and hash in flake.nix
|
||||
deno_fetch = "0.214.0"
|
||||
@@ -349,7 +343,7 @@ openidconnect = { version = "4.0.0-rc.1" }
|
||||
aws-config = "^1"
|
||||
aws-sdk-sqs = "1.57.0"
|
||||
aws-sdk-sts = "^1"
|
||||
aws-smithy-types-convert = { version = "^0", features = ["convert-chrono"] }
|
||||
|
||||
crc = "^3"
|
||||
tar = "^0"
|
||||
http = "^1"
|
||||
@@ -396,5 +390,5 @@ tree-sitter-c-sharp = "0.23.0"
|
||||
tree-sitter-java = "0.23.0"
|
||||
oracle = { version = "0.6.3", features = ["chrono"] }
|
||||
rumqttc = { version = "0.24.0", features = ["use-native-tls"]}
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
strum = "^0"
|
||||
strum_macros = "^0"
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# This script outputs all features except private. Usage :
|
||||
# > cargo build --features $(./all_features_oss.sh)
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Path to the Cargo.toml file
|
||||
CARGO_TOML_PATH="./Cargo.toml"
|
||||
|
||||
# Extract features from Cargo.toml and output them separated by commas
|
||||
if [[ -f "$CARGO_TOML_PATH" ]]; then
|
||||
grep -A 100 '\[features\]' "$CARGO_TOML_PATH" | \
|
||||
sed -n '/\[features\]/,/^\[/p' | \
|
||||
grep -E '^[a-zA-Z0-9_-]+' | \
|
||||
grep -v 'private' | \
|
||||
cut -d' ' -f1 | \
|
||||
paste -sd ',' -
|
||||
else
|
||||
echo "Cargo.toml not found at $CARGO_TOML_PATH"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +1 @@
|
||||
70895a4a8f8891032c5b478a37ab6fafd0d4a9d0
|
||||
bea87fa885dc041fba83b2491609a4a2cdbbfa6f
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'duckdb';
|
||||
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["duckdb"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp", "nu", "java"]}'::jsonb AND NOT config->'worker_tags' @> '"duckdb"'::jsonb;
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Add up migration script here
|
||||
GRANT ALL ON SEQUENCE http_trigger_version_seq TO windmill_user;
|
||||
GRANT ALL ON SEQUENCE http_trigger_version_seq TO windmill_admin;
|
||||
@@ -27,6 +27,3 @@ anyhow.workspace = true
|
||||
lazy_static.workspace = true
|
||||
sqlx.workspace = true
|
||||
async-recursion.workspace = true
|
||||
toml.workspace = true
|
||||
serde.workspace = true
|
||||
pep440_rs.workspace = true
|
||||
|
||||
@@ -11,7 +11,7 @@ mod mapping;
|
||||
use async_recursion::async_recursion;
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mapping::{FULL_IMPORTS_MAP, SHORT_IMPORTS_MAP};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -25,10 +25,7 @@ use rustpython_parser::{
|
||||
Parse,
|
||||
};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::{
|
||||
error::{self, to_anyhow},
|
||||
worker::PythonAnnotations,
|
||||
};
|
||||
use windmill_common::{error, worker::PythonAnnotations};
|
||||
|
||||
const DEF_MAIN: &str = "def main(";
|
||||
|
||||
@@ -245,7 +242,8 @@ pub async fn parse_python_imports(
|
||||
w_id: &str,
|
||||
path: &str,
|
||||
db: &Pool<Postgres>,
|
||||
version_specifiers: &mut Vec<pep440_rs::VersionSpecifier>,
|
||||
already_visited: &mut Vec<String>,
|
||||
annotated_pyv_numeric: &mut Option<u32>,
|
||||
) -> error::Result<(Vec<String>, Option<String>)> {
|
||||
let mut compile_error_hint: Option<String> = None;
|
||||
let mut imports = parse_python_imports_inner(
|
||||
@@ -253,10 +251,9 @@ pub async fn parse_python_imports(
|
||||
w_id,
|
||||
path,
|
||||
db,
|
||||
&mut vec![],
|
||||
version_specifiers,
|
||||
// &mut version_specifier.and_then(|_| Some(path.to_owned())),
|
||||
&mut None
|
||||
already_visited,
|
||||
annotated_pyv_numeric,
|
||||
&mut annotated_pyv_numeric.and_then(|_| Some(path.to_owned())),
|
||||
)
|
||||
.await?
|
||||
.into_values()
|
||||
@@ -282,7 +279,6 @@ pub async fn parse_python_imports(
|
||||
.flatten()
|
||||
.collect::<error::Result<Vec<String>>>()?
|
||||
.into_iter()
|
||||
.filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty())
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
@@ -308,34 +304,11 @@ async fn parse_python_imports_inner(
|
||||
path: &str,
|
||||
db: &Pool<Postgres>,
|
||||
already_visited: &mut Vec<String>,
|
||||
version_specifiers: &mut Vec<pep440_rs::VersionSpecifier>,
|
||||
annotated_pyv_numeric: &mut Option<u32>,
|
||||
path_where_annotated_pyv: &mut Option<String>,
|
||||
) -> error::Result<HashMap<String, NImportResolved>> {
|
||||
let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code);
|
||||
|
||||
let mut push_version_specifiers = |perform, unparsed: String| -> error::Result<()> {
|
||||
if perform {
|
||||
pep440_rs::VersionSpecifiers::from_str(unparsed.as_str())
|
||||
.ok()
|
||||
.map(|vs| version_specifiers.extend(vs.to_vec()));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
push_version_specifiers(py310, "==3.10.*".to_owned())?;
|
||||
push_version_specifiers(py311, "==3.11.*".to_owned())?;
|
||||
push_version_specifiers(py312, "==3.12.*".to_owned())?;
|
||||
push_version_specifiers(py313, "==3.13.*".to_owned())?;
|
||||
|
||||
for x in code.lines() {
|
||||
if x.starts_with("# py:") || x.starts_with("#py:") {
|
||||
push_version_specifiers(
|
||||
true,
|
||||
x.replace('#', "").replace("py:", "").trim().to_owned(),
|
||||
)?;
|
||||
} else if !x.starts_with('#') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// we pass only if there is none or only one annotation
|
||||
|
||||
// Naive:
|
||||
@@ -350,48 +323,39 @@ async fn parse_python_imports_inner(
|
||||
// This way we make sure there is no multiple annotations for same script
|
||||
// and we get detailed span on conflicting versions
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct InlineMetadata {
|
||||
requires_python: String,
|
||||
dependencies: Vec<String>,
|
||||
}
|
||||
|
||||
let find_requirements = code.lines().find_position(|x| {
|
||||
x.starts_with("#requirements:")
|
||||
|| x.starts_with("# requirements:")
|
||||
|| x.starts_with("# /// script")
|
||||
});
|
||||
if let Some((pos, item)) = find_requirements {
|
||||
let mut requirements = HashMap::new();
|
||||
if item.starts_with("# /// script") {
|
||||
let mut incorrect = false;
|
||||
let metadata = code
|
||||
.lines()
|
||||
.skip(pos + 1)
|
||||
.map_while(|x| {
|
||||
incorrect = !x.starts_with('#');
|
||||
if incorrect || x.starts_with("# ///") {
|
||||
None
|
||||
} else {
|
||||
x.get(1..)
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
.parse::<toml::Table>()
|
||||
.map_err(to_anyhow)?;
|
||||
|
||||
{
|
||||
if let Some(v) = metadata.get("requires-python").and_then(|v| v.as_str()) {
|
||||
push_version_specifiers(true, v.to_owned())?;
|
||||
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(())
|
||||
};
|
||||
|
||||
metadata
|
||||
.get("dependencies")
|
||||
.and_then(|dependencies| dependencies.as_array())
|
||||
.inspect(|list| {
|
||||
for dependency_v in list.into_iter() {
|
||||
let requirement = dependency_v.as_str().unwrap_or("ERROR").to_owned();
|
||||
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:"));
|
||||
if let Some((pos, _)) = find_requirements {
|
||||
let mut requirements = HashMap::new();
|
||||
code.lines()
|
||||
.skip(pos + 1)
|
||||
.map_while(|x| {
|
||||
RE.captures(x).and_then(|x| {
|
||||
x.get(1).map(|m| {
|
||||
let requirement = m.as_str().to_string();
|
||||
let key = extract_pkg_name(&requirement);
|
||||
requirements.insert(
|
||||
key.clone(),
|
||||
@@ -403,31 +367,11 @@ async fn parse_python_imports_inner(
|
||||
key,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
code.lines()
|
||||
.skip(pos + 1)
|
||||
.map_while(|x| {
|
||||
RE.captures(x).and_then(|x| {
|
||||
x.get(1).map(|m| {
|
||||
let requirement = m.as_str().to_string();
|
||||
let key = extract_pkg_name(&requirement);
|
||||
requirements.insert(
|
||||
key.clone(),
|
||||
NImportResolved::Pin {
|
||||
pins: vec![ImportPin {
|
||||
pkg: requirement.clone(),
|
||||
path: Default::default(),
|
||||
}],
|
||||
key,
|
||||
},
|
||||
);
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect_vec();
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
Ok(requirements)
|
||||
} else {
|
||||
let find_extra_requirements = code.lines().find_position(|x| {
|
||||
@@ -498,7 +442,7 @@ async fn parse_python_imports_inner(
|
||||
&rpath,
|
||||
db,
|
||||
already_visited,
|
||||
version_specifiers,
|
||||
annotated_pyv_numeric,
|
||||
path_where_annotated_pyv,
|
||||
)
|
||||
.await?
|
||||
|
||||
@@ -18,8 +18,16 @@ def main():
|
||||
pass
|
||||
|
||||
";
|
||||
let (r, ..) =
|
||||
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
|
||||
let mut already_visited = vec![];
|
||||
let (r, ..) = parse_python_imports(
|
||||
code,
|
||||
"test-workspace",
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
// println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(
|
||||
r,
|
||||
@@ -51,8 +59,16 @@ def main():
|
||||
pass
|
||||
|
||||
";
|
||||
let (r, ..) =
|
||||
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
|
||||
let mut already_visited = vec![];
|
||||
let (r, ..) = parse_python_imports(
|
||||
code,
|
||||
"test-workspace",
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(r, vec!["burkina=0.4", "nigeria"]);
|
||||
|
||||
@@ -73,9 +89,17 @@ def main():
|
||||
pass
|
||||
|
||||
";
|
||||
let mut already_visited = vec![];
|
||||
|
||||
let (r, ..) =
|
||||
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
|
||||
let (r, ..) = parse_python_imports(
|
||||
code,
|
||||
"test-workspace",
|
||||
"f/foo/bar",
|
||||
&db,
|
||||
&mut already_visited,
|
||||
&mut None,
|
||||
)
|
||||
.await?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(
|
||||
r,
|
||||
|
||||
@@ -83,21 +83,6 @@ pub fn parse_bigquery_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duckdb_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
let parsed = parse_duckdb_file(&code)?;
|
||||
if let Some(args) = parsed {
|
||||
Ok(MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args,
|
||||
no_main_func: None,
|
||||
has_preprocessor: None,
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("Error parsing sql".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_snowflake_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
let parsed = parse_snowflake_file(&code)?;
|
||||
if let Some(x) = parsed {
|
||||
@@ -227,9 +212,6 @@ lazy_static::lazy_static! {
|
||||
// -- @name (type) = default
|
||||
static ref RE_ARG_BIGQUERY: Regex = Regex::new(r#"(?m)^-- @(\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
// -- $name (type) = default
|
||||
static ref RE_ARG_DUCKDB: Regex = Regex::new(r#"(?m)^-- \$(\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
|
||||
@@ -595,35 +577,6 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
fn parse_duckdb_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args: Vec<Arg> = vec![];
|
||||
|
||||
for cap in RE_ARG_DUCKDB.captures_iter(code) {
|
||||
let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap();
|
||||
let typ = cap
|
||||
.get(2)
|
||||
.map(|x| x.as_str().to_string().to_lowercase())
|
||||
.unwrap();
|
||||
let default = cap.get(3).map(|x| x.as_str().to_string());
|
||||
let has_default = default.is_some();
|
||||
let parsed_typ = parse_duckdb_typ(typ.as_str());
|
||||
|
||||
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
|
||||
|
||||
args.push(Arg {
|
||||
name,
|
||||
typ: parsed_typ,
|
||||
default: parsed_default,
|
||||
otyp: Some(typ),
|
||||
has_default,
|
||||
oidx: None,
|
||||
});
|
||||
}
|
||||
|
||||
args.append(&mut parse_sql_sanitized_interpolation(code));
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args: Vec<Arg> = vec![];
|
||||
|
||||
@@ -776,33 +729,6 @@ pub fn parse_bigquery_typ(typ: &str) -> Typ {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duckdb_typ(typ: &str) -> Typ {
|
||||
if typ.ends_with("[]") {
|
||||
let base_typ = parse_duckdb_typ(typ.strip_suffix("[]").unwrap());
|
||||
Typ::List(Box::new(base_typ))
|
||||
} else {
|
||||
match typ {
|
||||
"varchar" | "char" | "bpchar" | "text" | "string" => Typ::Str(None),
|
||||
"blob" | "bytea" | "binary" | "varbinary" | "bitstring" => Typ::Bytes,
|
||||
"boolean" | "bool" | "bit" | "logical" => Typ::Bool,
|
||||
"bigint" | "int8" | "long" | "integer" | "int4" | "int" | "smallint" | "int2"
|
||||
| "short" | "tinyint" | "int1" | "signed" | "ubigint" | "uhugeint" | "uinteger"
|
||||
| "usmallint" | "utinyint" => Typ::Int,
|
||||
"decimal" | "numeric" | "double" | "float8" | "float" | "float4" | "real" => Typ::Float,
|
||||
"date"
|
||||
| "time"
|
||||
| "timestamp with time zone"
|
||||
| "timestamptz"
|
||||
| "timestamp"
|
||||
| "datetime" => Typ::Datetime,
|
||||
"uuid" | "json" => Typ::Str(None),
|
||||
"interval" | "hugeint" => Typ::Str(None),
|
||||
"s3object" => Typ::Resource("S3Object".to_string()),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_snowflake_typ(typ: &str) -> Typ {
|
||||
match typ {
|
||||
"varchar" => Typ::Str(None),
|
||||
|
||||
@@ -96,12 +96,6 @@ pub fn parse_oracledb(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_sql::parse_oracledb_sig(code))
|
||||
}
|
||||
|
||||
#[cfg(feature = "sql-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_duckdb(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_sql::parse_duckdb_sig(code))
|
||||
}
|
||||
|
||||
#[cfg(feature = "sql-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_bigquery(code: &str) -> String {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn set_license_key(_license_key: String) -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn verify_license_key() -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
@@ -28,9 +28,7 @@ use uuid::Uuid;
|
||||
use windmill_api::HTTP_CLIENT;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{
|
||||
maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID,
|
||||
};
|
||||
use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID};
|
||||
|
||||
use windmill_common::{
|
||||
agent_workers::build_agent_http_client,
|
||||
@@ -51,7 +49,7 @@ use windmill_common::{
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_oss::schedule_stats,
|
||||
stats_ee::schedule_stats,
|
||||
triggers::TriggerKind,
|
||||
utils::{hostname, rd_string, Mode, GIT_VERSION, MODE_AND_ADDONS},
|
||||
worker::{
|
||||
@@ -71,7 +69,7 @@ use tikv_jemallocator::Jemalloc;
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::global_settings::OBJECT_STORE_CONFIG_SETTING;
|
||||
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
|
||||
|
||||
use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR,
|
||||
@@ -94,15 +92,13 @@ use crate::monitor::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::reload_object_store_setting;
|
||||
use crate::monitor::reload_s3_cache_setting;
|
||||
|
||||
const DEFAULT_NUM_WORKERS: usize = 1;
|
||||
const DEFAULT_PORT: u16 = 8000;
|
||||
const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
mod ee_oss;
|
||||
mod ee;
|
||||
mod monitor;
|
||||
|
||||
pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
@@ -556,7 +552,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
_ = indexer_rx.recv() => {
|
||||
tracing::info!("Received killpill, aborting index initialization");
|
||||
},
|
||||
res = windmill_indexer::completed_runs_oss::init_index(&db) => {
|
||||
res = windmill_indexer::completed_runs_ee::init_index(&db) => {
|
||||
let res = res?;
|
||||
reader = Some(res.0);
|
||||
writer = Some(res.1);
|
||||
@@ -578,7 +574,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
async {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if let Some(index_writer) = index_writer2 {
|
||||
windmill_indexer::completed_runs_oss::run_indexer(
|
||||
windmill_indexer::completed_runs_ee::run_indexer(
|
||||
db.clone(),
|
||||
index_writer,
|
||||
indexer_rx,
|
||||
@@ -600,7 +596,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
_ = indexer_rx.recv() => {
|
||||
tracing::info!("Received killpill, aborting index initialization");
|
||||
},
|
||||
res = windmill_indexer::service_logs_oss::init_index(&db, killpill_tx.clone()) => {
|
||||
res = windmill_indexer::service_logs_ee::init_index(&db, killpill_tx.clone()) => {
|
||||
let res = res?;
|
||||
reader = Some(res.0);
|
||||
writer = Some(res.1);
|
||||
@@ -622,7 +618,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
async {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
if let Some(log_index_writer) = log_index_writer2 {
|
||||
windmill_indexer::service_logs_oss::run_indexer(
|
||||
windmill_indexer::service_logs_ee::run_indexer(
|
||||
db.clone(),
|
||||
log_index_writer,
|
||||
log_indexer_rx,
|
||||
@@ -911,9 +907,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
reload_job_default_timeout_setting(&conn).await
|
||||
},
|
||||
#[cfg(feature = "parquet")]
|
||||
OBJECT_STORE_CONFIG_SETTING => {
|
||||
OBJECT_STORE_CACHE_CONFIG_SETTING => {
|
||||
if !disable_s3_store {
|
||||
reload_object_store_setting(&db).await;
|
||||
reload_s3_cache_setting(&db).await
|
||||
}
|
||||
},
|
||||
SCIM_TOKEN_SETTING => {
|
||||
@@ -1090,7 +1086,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::info!("Reloading config after 12 hours");
|
||||
initial_load(&conn, tx.clone(), worker_mode, server_mode, #[cfg(feature = "parquet")] disable_s3_store).await;
|
||||
#[cfg(feature = "enterprise")]
|
||||
ee_oss::verify_license_key().await;
|
||||
ee::verify_license_key().await;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -29,19 +29,16 @@ use windmill_api::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::low_disk_alerts;
|
||||
use windmill_common::ee::low_disk_alerts;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
|
||||
use windmill_common::client::AuthedClient;
|
||||
#[cfg(feature = "oauth2")]
|
||||
use windmill_common::global_settings::OAUTH_SETTING;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::reload_object_store_setting;
|
||||
use windmill_common::{
|
||||
agent_workers::DECODED_AGENT_TOKEN,
|
||||
auth::create_token_for_owner,
|
||||
ee_oss::CriticalErrorChannel,
|
||||
ee::CriticalErrorChannel,
|
||||
error,
|
||||
flow_status::{FlowStatus, FlowStatusModule},
|
||||
global_settings::{
|
||||
@@ -78,18 +75,24 @@ use windmill_common::{
|
||||
};
|
||||
use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
|
||||
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::ObjectStoreReload;
|
||||
use windmill_common::s3_helpers::{
|
||||
build_object_store_from_settings, build_s3_client_from_settings, S3Settings,
|
||||
OBJECT_STORE_CACHE_SETTINGS,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::verify_license_key;
|
||||
use crate::ee::verify_license_key;
|
||||
|
||||
use crate::ee_oss::set_license_key;
|
||||
use crate::ee::set_license_key;
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
lazy_static::lazy_static! {
|
||||
@@ -238,23 +241,7 @@ pub async fn initial_load(
|
||||
#[cfg(feature = "parquet")]
|
||||
if !disable_s3_store {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
let db2 = db.clone();
|
||||
match reload_object_store_setting(db).await {
|
||||
ObjectStoreReload::Later => {
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
match reload_object_store_setting(&db2).await {
|
||||
ObjectStoreReload::Later => {
|
||||
tracing::error!("Giving up on loading object store setting");
|
||||
}
|
||||
ObjectStoreReload::Never => {
|
||||
tracing::info!("Object store setting successfully loaded");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ObjectStoreReload::Never => (),
|
||||
}
|
||||
reload_s3_cache_setting(db).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,7 +631,7 @@ async fn send_log_file_to_object_store(
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
let s3_client = windmill_common::s3_helpers::get_object_store().await;
|
||||
let s3_client = OBJECT_STORE_CACHE_SETTINGS.read().await.clone();
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = std::path::Path::new(TMP_WINDMILL_LOGS_SERVICE)
|
||||
@@ -930,7 +917,10 @@ async fn delete_log_files_from_disk_and_store(
|
||||
_s3_prefix: &str,
|
||||
) {
|
||||
#[cfg(feature = "parquet")]
|
||||
let os = windmill_common::s3_helpers::get_object_store().await;
|
||||
let os = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone();
|
||||
#[cfg(not(feature = "parquet"))]
|
||||
let os: Option<()> = None;
|
||||
|
||||
@@ -1111,6 +1101,64 @@ pub async fn reload_delete_logs_periodically_setting(conn: &Connection) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn reload_s3_cache_setting(db: &DB) {
|
||||
use windmill_common::{
|
||||
ee::{get_license_plan, LicensePlan},
|
||||
s3_helpers::ObjectSettings,
|
||||
};
|
||||
|
||||
let s3_config = load_value_from_global_settings(db, OBJECT_STORE_CACHE_CONFIG_SETTING).await;
|
||||
if let Err(e) = s3_config {
|
||||
tracing::error!("Error reloading s3 cache config: {:?}", e)
|
||||
} else {
|
||||
if let Some(v) = s3_config.unwrap() {
|
||||
if matches!(get_license_plan().await, LicensePlan::Pro) {
|
||||
tracing::error!("S3 cache is not available for pro plan");
|
||||
return;
|
||||
}
|
||||
let mut s3_cache_settings = OBJECT_STORE_CACHE_SETTINGS.write().await;
|
||||
let setting = serde_json::from_value::<ObjectSettings>(v);
|
||||
if let Err(e) = setting {
|
||||
tracing::error!("Error parsing s3 cache config: {:?}", e)
|
||||
} else {
|
||||
let setting = setting.unwrap();
|
||||
let bucket = setting.get_bucket().map(|b| b.to_string());
|
||||
let s3_client = build_object_store_from_settings(setting).await;
|
||||
if let Err(e) = s3_client {
|
||||
tracing::error!("Error building s3 client from settings: {:?}", e)
|
||||
} else {
|
||||
tracing::info!("Loaded object store {:?}", bucket);
|
||||
*s3_cache_settings = Some(s3_client.unwrap());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut s3_cache_settings = OBJECT_STORE_CACHE_SETTINGS.write().await;
|
||||
if std::env::var("S3_CACHE_BUCKET").is_ok() {
|
||||
if matches!(get_license_plan().await, LicensePlan::Pro) {
|
||||
tracing::error!("S3 cache is not available for pro plan");
|
||||
return;
|
||||
}
|
||||
*s3_cache_settings = build_s3_client_from_settings(S3Settings {
|
||||
bucket: None,
|
||||
region: None,
|
||||
access_key: None,
|
||||
secret_key: None,
|
||||
endpoint: None,
|
||||
store_logs: None,
|
||||
path_style: None,
|
||||
allow_http: None,
|
||||
port: None,
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
} else {
|
||||
*s3_cache_settings = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reload_job_default_timeout_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
@@ -1609,7 +1657,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
|
||||
|
||||
if let Some(q) = q_oauth {
|
||||
if let Ok(v) = serde_json::from_value::<
|
||||
Option<HashMap<String, windmill_api::oauth2_oss::OAuthClient>>,
|
||||
Option<HashMap<String, windmill_api::oauth2_ee::OAuthClient>>,
|
||||
>(q.clone())
|
||||
{
|
||||
v
|
||||
@@ -1630,7 +1678,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
|
||||
{
|
||||
if let Some(db) = conn.as_sql() {
|
||||
let mut l = windmill_api::OAUTH_CLIENTS.write().await;
|
||||
*l = windmill_api::oauth2_oss::build_oauth_clients(&base_url, oauths, db).await
|
||||
*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();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
|
||||
|
||||
REVERT="NO"
|
||||
REVERT_PREVIOUS="NO"
|
||||
COPY="NO"
|
||||
MOVE_NEW_FILES="NO"
|
||||
EE_CODE_DIR="../windmill-ee-private/"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -16,7 +16,13 @@ while [[ $# -gt 0 ]]; do
|
||||
# this to work (commit hooks should prevent this from happening, as well as the fact
|
||||
# that we're using symlinks by default).
|
||||
REVERT="YES"
|
||||
MOVE_NEW_FILES="YES"
|
||||
shift
|
||||
;;
|
||||
--revert-previous)
|
||||
# This is a special case of --revert that will revert to the previous commit.
|
||||
REVERT="YES"
|
||||
REVERT_PREVIOUS="YES"
|
||||
echo "Reverting to previous commit"
|
||||
shift
|
||||
;;
|
||||
-c|--copy)
|
||||
@@ -27,11 +33,6 @@ while [[ $# -gt 0 ]]; do
|
||||
COPY="YES"
|
||||
shift # past argument
|
||||
;;
|
||||
-m|--move-new-files)
|
||||
# This moves all new EE files from the public repository to the private repository.
|
||||
MOVE_NEW_FILES="YES"
|
||||
shift # past argument
|
||||
;;
|
||||
-d|--dir)
|
||||
# Path to the local directory of the windmill-ee-private repository. By defaults, it
|
||||
# assumes it is cloned next to the Windmill OSS repo.
|
||||
@@ -69,34 +70,29 @@ if [ "$REVERT" == "YES" ]; then
|
||||
for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
rm ${ce_file}
|
||||
if [ "$REVERT_PREVIOUS" == "YES" ]; then
|
||||
git checkout HEAD@{3} ${ce_file} || true
|
||||
else
|
||||
git restore --staged ${ce_file} || true
|
||||
git restore ${ce_file} || true
|
||||
fi
|
||||
done
|
||||
elif [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
else
|
||||
# This replaces all files in current repo with alternative EE files in windmill-ee-private
|
||||
for ee_file in $(find "${EE_CODE_DIR}" -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
if [ "$COPY" == "YES" ]; then
|
||||
cp "${ee_file}" "${ce_file}"
|
||||
echo "File copied '${ee_file}' -->> '${ce_file}'"
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
if [[ -f "${ce_file}" ]]; then
|
||||
rm "${ce_file}"
|
||||
if [ "$COPY" == "YES" ]; then
|
||||
cp "${ee_file}" "${ce_file}"
|
||||
echo "File copied '${ee_file}' -->> '${ce_file}'"
|
||||
else
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
|
||||
fi
|
||||
else
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
|
||||
echo "File ${ce_file} is not a file, ignoring"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$MOVE_NEW_FILES" == "YES" ]; then
|
||||
for ce_file in $(find "${root_dirpath}"/backend/windmill-*/src/ -name "*ee.rs"); do
|
||||
backend_dirpath="${root_dirpath}/backend/"
|
||||
ee_file="${ce_file/${backend_dirpath}/}"
|
||||
ee_file="${EE_CODE_DIR}${ee_file}"
|
||||
if [ ! -f "${ee_file}" ]; then
|
||||
mv "${ce_file}" "${ee_file}"
|
||||
if [ ! "$REVERT" == "YES" ]; then
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
fi
|
||||
echo "File moved '${ce_file}' -->> '${ee_file}'"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
20
backend/tests/fixtures/multipython.sql
vendored
20
backend/tests/fixtures/multipython.sql
vendored
@@ -1,20 +0,0 @@
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'# py312
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/multipython/aliases', 2468135790, 'python3', '');
|
||||
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'# py: >=3.9,!=3.12.2
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/multipython/script1', 2345678901, 'python3', '');
|
||||
|
||||
@@ -3970,7 +3970,7 @@ async fn assert_lockfile(
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_requirements_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
# py: 3.11.11
|
||||
# py311
|
||||
# requirements:
|
||||
# tiny==0.1.3
|
||||
|
||||
@@ -3988,7 +3988,7 @@ def main():
|
||||
&db,
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec!["# py: 3.11.11", "tiny==0.1.3"],
|
||||
vec!["# py311", "tiny==0.1.3"],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -3998,7 +3998,7 @@ def main():
|
||||
async fn test_extra_requirements_python(db: Pool<Postgres>) {
|
||||
{
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# py311
|
||||
# extra_requirements:
|
||||
# tiny
|
||||
|
||||
@@ -4016,7 +4016,7 @@ def main():
|
||||
&db,
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec!["# py: 3.11.11", "bottle==0.13.2", "tiny==0.1.2"],
|
||||
vec!["# py311", "bottle==0.13.2", "tiny==0.1.2"],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -4026,7 +4026,7 @@ def main():
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# py311
|
||||
# extra_requirements:
|
||||
# tiny==0.1.3
|
||||
|
||||
@@ -4040,7 +4040,7 @@ def main():
|
||||
&db,
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec!["# py: 3.11.11", "simplejson==3.20.1", "tiny==0.1.3"],
|
||||
vec!["# py311", "simplejson==3.20.1", "tiny==0.1.3"],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -4049,7 +4049,7 @@ def main():
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
async fn test_pins_python(db: Pool<Postgres>) {
|
||||
let content = r#"
|
||||
# py: ==3.11.11
|
||||
# py311
|
||||
# extra_requirements:
|
||||
# tiny==0.1.3
|
||||
# bottle==0.13.2
|
||||
@@ -4069,7 +4069,7 @@ def main():
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec![
|
||||
"# py: 3.11.11",
|
||||
"# py311",
|
||||
"bottle==0.13.2",
|
||||
"microdot==2.2.0",
|
||||
"simplejson==3.19.3",
|
||||
@@ -4078,39 +4078,6 @@ def main():
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "multipython"))]
|
||||
async fn test_multipython_python(db: Pool<Postgres>) {
|
||||
let content = r#"# py: <=3.12.2, >=3.12.0
|
||||
import f.multipython.script1
|
||||
import f.multipython.aliases
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
assert_lockfile(&db, content, ScriptLang::Python3, vec!["# py: 3.12.1\n"]).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "multipython"))]
|
||||
async fn test_inline_script_metadata_python(db: Pool<Postgres>) {
|
||||
let content = r#"# py_select_latest
|
||||
# /// script
|
||||
# requires-python = ">3.11,<3.12.3,!=3.12.2"
|
||||
# dependencies = [
|
||||
# "tiny==0.1.3",
|
||||
# ]
|
||||
# ///
|
||||
"#
|
||||
.to_string();
|
||||
|
||||
assert_lockfile(
|
||||
&db,
|
||||
content,
|
||||
ScriptLang::Python3,
|
||||
vec!["# py: 3.12.1", "tiny==0.1.3"],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[sqlx::test(fixtures("base", "result_format"))]
|
||||
async fn test_result_format(db: Pool<Postgres>) {
|
||||
let ordered_result_job_id = "1eecb96a-c8b0-4a3d-b1b6-087878c55e41";
|
||||
|
||||
@@ -9,7 +9,7 @@ if [[ "$(uname)" == "Darwin" ]]; then
|
||||
sed -i '' 's/^# \(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/\1/' Cargo.toml
|
||||
fi
|
||||
|
||||
cargo sqlx prepare --workspace -- --all-targets --features $(./all_features_oss.sh)
|
||||
cargo sqlx prepare --workspace -- --all-targets --all-features
|
||||
./substitute_ee_code.sh -r --dir ../windmill-ee-private
|
||||
|
||||
# Undo the samael changes on macOS
|
||||
|
||||
@@ -10,7 +10,6 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-audit/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker/enterprise"]
|
||||
stripe = []
|
||||
agent_worker_server = []
|
||||
@@ -19,7 +18,7 @@ benchmark = []
|
||||
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn"]
|
||||
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet", "windmill-worker/parquet"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker/prometheus"]
|
||||
openidconnect = ["dep:openidconnect", "windmill-common/openidconnect"]
|
||||
openidconnect = ["dep:openidconnect"]
|
||||
tantivy = ["dep:windmill-indexer"]
|
||||
kafka = ["dep:rdkafka"]
|
||||
nats = ["dep:async-nats", "dep:nkeys"]
|
||||
@@ -37,7 +36,6 @@ deno_core = ["dep:deno_core", "dep:deno_error"]
|
||||
gcp_trigger = ["dep:thiserror", "dep:google-cloud-pubsub", "dep:google-cloud-googleapis", "dep:tonic"]
|
||||
cloud = ["windmill-common/cloud"]
|
||||
mcp = ["dep:rmcp"]
|
||||
python = []
|
||||
|
||||
[dependencies]
|
||||
rmcp = { git = "https://github.com/windmill-labs/rust-sdk", features = ["transport-sse-server"], optional = true }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.494.0
|
||||
version: 1.491.5
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -377,9 +377,6 @@ paths:
|
||||
type: string
|
||||
company:
|
||||
type: string
|
||||
skip_email:
|
||||
type: boolean
|
||||
description: Skip sending email notifications to the user
|
||||
required:
|
||||
- email
|
||||
- password
|
||||
@@ -4896,6 +4893,8 @@ paths:
|
||||
description: Script version/hash
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptHistory"
|
||||
|
||||
@@ -5457,6 +5456,8 @@ paths:
|
||||
description: Flow version
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
|
||||
schema:
|
||||
$ref: "#/components/schemas/FlowVersion"
|
||||
|
||||
@@ -5486,7 +5487,8 @@ paths:
|
||||
operationId: getFlowVersion
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
- type: string
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -5508,7 +5510,8 @@ paths:
|
||||
operationId: updateFlowHistory
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
- type: string
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -6136,8 +6139,10 @@ paths:
|
||||
description: App version
|
||||
content:
|
||||
application/json:
|
||||
required: false
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppHistory"
|
||||
|
||||
/w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}:
|
||||
get:
|
||||
summary: list app paths from workspace runnable
|
||||
@@ -6726,6 +6731,11 @@ paths:
|
||||
responses:
|
||||
"201":
|
||||
description: stream of created job uuids separated by \n. Lines may start with 'Error:'
|
||||
example: |
|
||||
a1a74c0d-708e-4539-9768-e8b3d37996bd
|
||||
f0949132-5b30-48fe-bac8-873f047df810
|
||||
Error: Could not re-run 0b885808-ae89-4458-af95-c1ca3a13b0a5
|
||||
52b9c01d-1125-4bbb-8bee-d41f26b70066
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
@@ -7582,8 +7592,7 @@ paths:
|
||||
description: job log
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs_u/get_flow_debug_info/{id}:
|
||||
get:
|
||||
@@ -9821,23 +9830,6 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/postgres_triggers/postgres/version/{path}:
|
||||
get:
|
||||
summary: get postgres version
|
||||
operationId: getPostgresVersion
|
||||
tags:
|
||||
- postgres_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: postgres version
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}:
|
||||
get:
|
||||
summary: check if postgres configuration is set to logical
|
||||
@@ -11113,23 +11105,6 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/AutoscalingEvent"
|
||||
|
||||
/configs/list_available_python_versions:
|
||||
get:
|
||||
summary: Get currently available python versions provided by UV.
|
||||
operationId: listAvailablePythonVersions
|
||||
tags:
|
||||
- config
|
||||
# parameters:
|
||||
responses:
|
||||
"200":
|
||||
description: List of python versions
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
/agent_workers/create_agent_token:
|
||||
post:
|
||||
summary: create agent token
|
||||
@@ -12477,6 +12452,7 @@ paths:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/concurrency_groups/list:
|
||||
get:
|
||||
summary: List all concurrency groups
|
||||
@@ -13182,51 +13158,7 @@ components:
|
||||
enum: [script, flow]
|
||||
|
||||
schemas:
|
||||
# NOTE: Not so many generators and validators support this format:
|
||||
# $ref: "../../openflow.openapi.yaml#/components/schemas"
|
||||
# This is why it is better to inline each of schemas for better compat
|
||||
# Do not change next line. It is used by python-client for pre-processing
|
||||
# -- INLINE START --
|
||||
OpenFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
|
||||
FlowValue:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowValue"
|
||||
Retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
StopAfterIf:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/StopAfterIf"
|
||||
FlowModule:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModule"
|
||||
InputTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/InputTransform"
|
||||
StaticTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/StaticTransform"
|
||||
JavascriptTransform:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/JavascriptTransform"
|
||||
FlowModuleValue:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModuleValue"
|
||||
RawScript:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/RawScript"
|
||||
PathScript:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/PathScript"
|
||||
PathFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/PathFlow"
|
||||
ForloopFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/ForloopFlow"
|
||||
WhileloopFlow:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/WhileloopFlow"
|
||||
BranchOne:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchOne"
|
||||
BranchAll:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchAll"
|
||||
Identity:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Identity"
|
||||
FlowStatus:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus"
|
||||
FlowStatusModule:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatusModule"
|
||||
# -- INLINE END --
|
||||
# Do not change line above
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas"
|
||||
|
||||
AIProvider:
|
||||
type: string
|
||||
@@ -14293,8 +14225,7 @@ components:
|
||||
ansible,
|
||||
csharp,
|
||||
nu,
|
||||
java,
|
||||
duckdb
|
||||
java
|
||||
# for related places search: ADD_NEW_LANG
|
||||
]
|
||||
|
||||
@@ -16276,8 +16207,10 @@ components:
|
||||
- access_token
|
||||
|
||||
HubScriptKind:
|
||||
type: string
|
||||
enum: [script, failure, trigger, approval]
|
||||
name: kind
|
||||
schema:
|
||||
type: string
|
||||
enum: [script, failure, trigger, approval]
|
||||
|
||||
PolarsClientKwargs:
|
||||
type: object
|
||||
@@ -16869,6 +16802,7 @@ components:
|
||||
type: string
|
||||
required:
|
||||
- s3
|
||||
|
||||
TeamsChannel:
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::agent_workers_ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2042
|
||||
@@ -10,21 +6,16 @@ pub use crate::agent_workers_ee::*;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service(
|
||||
db: DB,
|
||||
_base_internal_url: String,
|
||||
@@ -45,7 +36,6 @@ pub fn workspaced_service(
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct AgentAuth {
|
||||
pub worker_group: String,
|
||||
pub suffix: Option<String>,
|
||||
@@ -53,10 +43,8 @@ pub struct AgentAuth {
|
||||
pub exp: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct AgentCache {}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl AgentCache {
|
||||
pub fn new() -> Self {
|
||||
AgentCache {}
|
||||
@@ -10,7 +10,7 @@ use reqwest::{Client, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use std::collections::HashMap;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{
|
||||
job_helpers_oss::{
|
||||
job_helpers_ee::{
|
||||
download_s3_file_internal, get_random_file_name, get_s3_resource,
|
||||
get_workspace_s3_resource, upload_file_from_req, DownloadFileQuery,
|
||||
},
|
||||
@@ -48,7 +48,7 @@ use sha2::{Digest, Sha256};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{types::Uuid, FromRow};
|
||||
use std::str;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
apps::{AppScriptId, ListAppQuery},
|
||||
|
||||
5
backend/windmill-api/src/apps_ee.rs
Normal file
5
backend/windmill-api/src/apps_ee.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::apps_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl RawWebhookArgs {
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> Result<HashMap<String, Box<RawValue>>, Error> {
|
||||
use crate::job_helpers_oss::{
|
||||
use crate::job_helpers_ee::{
|
||||
get_random_file_name, get_workspace_s3_resource, upload_file_internal,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
|
||||
@@ -28,7 +28,7 @@ async fn get_audit(
|
||||
Path((w_id, id)): Path<(String, i32)>,
|
||||
) -> JsonResult<AuditLog> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let audit = windmill_audit::audit_oss::get_audit(tx, id, &w_id).await?;
|
||||
let audit = windmill_audit::audit_ee::get_audit(tx, id, &w_id).await?;
|
||||
Ok(Json(audit))
|
||||
}
|
||||
async fn list_audit(
|
||||
@@ -39,6 +39,6 @@ async fn list_audit(
|
||||
Query(lq): Query<ListAuditLogQuery>,
|
||||
) -> JsonResult<Vec<AuditLog>> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let rows = windmill_audit::audit_oss::list_audit(tx, w_id, pagination, lq).await?;
|
||||
let rows = windmill_audit::audit_ee::list_audit(tx, w_id, pagination, lq).await?;
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::ExternalJwks;
|
||||
use crate::ee::ExternalJwks;
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequestParts, OriginalUri, Query},
|
||||
@@ -71,7 +71,7 @@ impl AuthCache {
|
||||
}
|
||||
#[cfg(feature = "enterprise")]
|
||||
_ if token.starts_with("jwt_ext_") => {
|
||||
let authed_and_exp = match crate::ee_oss::jwt_ext_auth(
|
||||
let authed_and_exp = match crate::ee::jwt_ext_auth(
|
||||
w_id.as_ref(),
|
||||
token.trim_start_matches("jwt_ext_"),
|
||||
self.ext_jwks.clone(),
|
||||
|
||||
@@ -15,7 +15,7 @@ use {
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
use {
|
||||
crate::gcp_triggers_oss::{
|
||||
crate::gcp_triggers_ee::{
|
||||
manage_google_subscription, process_google_push_request, validate_jwt_token,
|
||||
CreateUpdateConfig, SubscriptionMode,
|
||||
},
|
||||
@@ -23,10 +23,7 @@ use {
|
||||
http::HeaderMap,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "enterprise", feature = "gcp_trigger"),
|
||||
feature = "postgres_trigger"
|
||||
))]
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
use windmill_common::utils::empty_as_none;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
@@ -36,31 +33,25 @@ use windmill_common::auth::aws::AwsAuthResourceType;
|
||||
feature = "http_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
))]
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "http_trigger",
|
||||
feature = "postgres_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
))]
|
||||
use windmill_common::error::Error;
|
||||
use {serde::de::DeserializeOwned, windmill_common::error::Error};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
use crate::kafka_triggers_oss::KafkaTriggerConfigConnection;
|
||||
use crate::kafka_triggers_ee::KafkaTriggerConfigConnection;
|
||||
|
||||
#[cfg(feature = "mqtt_trigger")]
|
||||
use crate::mqtt_triggers::{MqttClientVersion, MqttV3Config, MqttV5Config, SubscribeTopic};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
use crate::nats_triggers_oss::NatsTriggerConfigConnection;
|
||||
use crate::nats_triggers_ee::NatsTriggerConfigConnection;
|
||||
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
use {
|
||||
crate::postgres_triggers::{
|
||||
create_logical_replication_slot, create_pg_publication, generate_random_string,
|
||||
get_pg_connection, PublicationData,
|
||||
create_logical_replication_slot_query, create_publication_query, drop_publication_query,
|
||||
generate_random_string, get_database_connection, PublicationData,
|
||||
},
|
||||
sqlx::Connection,
|
||||
itertools::Itertools,
|
||||
pg_escape::quote_literal,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -209,12 +200,9 @@ pub struct MqttTriggerConfig {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PostgresTriggerConfig {
|
||||
pub postgres_resource_path: String,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub publication_name: Option<String>,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub replication_slot_name: Option<String>,
|
||||
pub publication: PublicationData,
|
||||
pub basic_mode: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
@@ -304,49 +292,57 @@ async fn set_postgres_trigger_config(
|
||||
user_db: UserDB,
|
||||
mut capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
let Some(TriggerConfig::Postgres(postgres_config)) = capture_config.trigger_config.as_mut()
|
||||
else {
|
||||
return Err(Error::BadRequest("Invalid postgres config".to_string()));
|
||||
let Some(TriggerConfig::Postgres(mut postgres_config)) = capture_config.trigger_config else {
|
||||
return Err(windmill_common::error::Error::BadRequest(
|
||||
"Invalid postgres config".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
if postgres_config.basic_mode.unwrap_or(false) {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_config.postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
let mut connection = get_database_connection(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_config.postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx = pg_connection.begin().await?;
|
||||
let publication_name = postgres_config
|
||||
.publication_name
|
||||
.get_or_insert(format!("windmill_capture_{}", generate_random_string()));
|
||||
let replication_slot_name = postgres_config
|
||||
.replication_slot_name
|
||||
.get_or_insert(publication_name.clone());
|
||||
|
||||
let publication_name = format!("windmill_capture_{}", generate_random_string());
|
||||
let replication_slot_name = publication_name.clone();
|
||||
let query = drop_publication_query(&publication_name);
|
||||
|
||||
create_logical_replication_slot(&mut tx, &replication_slot_name).await?;
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
create_pg_publication(
|
||||
&mut tx,
|
||||
&publication_name,
|
||||
postgres_config.publication.table_to_track.as_deref(),
|
||||
&postgres_config.publication.transaction_to_track,
|
||||
)
|
||||
.await?;
|
||||
let query = create_publication_query(
|
||||
&publication_name,
|
||||
postgres_config.publication.table_to_track.as_deref(),
|
||||
&postgres_config
|
||||
.publication
|
||||
.transaction_to_track
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.collect_vec(),
|
||||
);
|
||||
|
||||
tx.commit().await?;
|
||||
postgres_config.publication_name = Some(publication_name);
|
||||
postgres_config.replication_slot_name = Some(replication_slot_name);
|
||||
} else {
|
||||
if postgres_config.publication_name.is_none()
|
||||
|| postgres_config.replication_slot_name.is_none()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
"Publication name and slot name required in advanced mode".to_string(),
|
||||
));
|
||||
}
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
let query = format!(
|
||||
"SELECT 1 from pg_replication_slots WHERE slot_name = {}",
|
||||
quote_literal(replication_slot_name)
|
||||
);
|
||||
|
||||
let row = sqlx::query(&query).fetch_optional(&mut connection).await?;
|
||||
|
||||
if row.is_none() {
|
||||
let query = create_logical_replication_slot_query(&replication_slot_name);
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
}
|
||||
|
||||
capture_config.trigger_config = Some(TriggerConfig::Postgres(postgres_config));
|
||||
Ok(capture_config)
|
||||
}
|
||||
|
||||
@@ -370,7 +366,9 @@ async fn set_gcp_trigger_config(
|
||||
mut capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
let Some(TriggerConfig::Gcp(mut gcp_config)) = capture_config.trigger_config else {
|
||||
return Err(Error::BadRequest("Invalid GCP Pub/Sub config".to_string()));
|
||||
return Err(windmill_common::error::Error::BadRequest(
|
||||
"Invalid GCP Pub/Sub config".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let config = manage_google_subscription(
|
||||
@@ -905,7 +903,7 @@ async fn gcp_payload(
|
||||
headers: HeaderMap,
|
||||
request: Request,
|
||||
) -> Result<StatusCode> {
|
||||
use crate::{gcp_triggers_oss::GcpTrigger, trigger_helpers::TriggerJobArgs};
|
||||
use crate::{gcp_triggers_ee::GcpTrigger, trigger_helpers::TriggerJobArgs};
|
||||
|
||||
let is_flow = matches!(runnable_kind, RunnableKind::Flow);
|
||||
let (gcp_trigger_config, owner, email): (GcpTriggerConfig, _, _) =
|
||||
|
||||
@@ -14,7 +14,7 @@ use axum::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
error::{self},
|
||||
@@ -33,10 +33,6 @@ pub fn global_service() -> Router {
|
||||
"/list_autoscaling_events/:worker_group",
|
||||
get(list_autoscaling_events),
|
||||
)
|
||||
.route(
|
||||
"/list_available_python_versions",
|
||||
get(list_available_python_versions),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRow)]
|
||||
@@ -209,24 +205,6 @@ async fn list_autoscaling_events(
|
||||
Ok(Json(events))
|
||||
}
|
||||
|
||||
async fn list_available_python_versions() -> error::JsonResult<Vec<String>> {
|
||||
#[cfg(not(feature = "python"))]
|
||||
return Err(error::Error::BadRequest(
|
||||
"Python listing available only with 'python' feature enabled".to_string(),
|
||||
));
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use itertools::Itertools;
|
||||
#[cfg(feature = "python")]
|
||||
return Ok(Json(
|
||||
windmill_worker::PyV::list_available_python_versions()
|
||||
.await
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect_vec(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn list_configs(
|
||||
authed: ApiAuthed,
|
||||
|
||||
@@ -16,7 +16,7 @@ use sqlx::{
|
||||
};
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use windmill_audit::audit_oss::{AuditAuthor, AuditAuthorable};
|
||||
use windmill_audit::audit_ee::{AuditAuthor, AuditAuthorable};
|
||||
use windmill_common::{
|
||||
db::{Authable, Authed},
|
||||
error::Error,
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use anyhow::anyhow;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn jwt_ext_auth(
|
||||
_w_id: Option<&String>,
|
||||
_token: &str,
|
||||
@@ -26,10 +20,10 @@ pub async fn jwt_ext_auth(
|
||||
Err(anyhow!("External JWT auth is not open source"))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub struct ExternalJwks;
|
||||
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
#[cfg(feature = "enterprise")]
|
||||
impl ExternalJwks {
|
||||
pub async fn load() -> Option<Arc<RwLock<Self>>> {
|
||||
// Implementation is not open source
|
||||
@@ -31,7 +31,7 @@ use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::utils::query_elems_from_hub;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
|
||||
@@ -23,7 +23,7 @@ use axum::{
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
|
||||
@@ -1,38 +1,29 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::gcp_triggers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use {
|
||||
crate::db::{ApiAuthed, DB},
|
||||
crate::trigger_helpers::TriggerJobArgs,
|
||||
axum::{extract::Request, Router},
|
||||
http::HeaderMap,
|
||||
serde::{Deserialize, Serialize},
|
||||
serde_json::value::RawValue,
|
||||
sqlx::prelude::FromRow,
|
||||
sqlx::types::Json as SqlxJson,
|
||||
std::collections::HashMap,
|
||||
windmill_common::db::UserDB,
|
||||
windmill_common::worker::to_raw_value,
|
||||
windmill_common::{
|
||||
error::{Error as WindmillError, Result as WindmillResult},
|
||||
triggers::TriggerKind,
|
||||
utils::empty_as_none,
|
||||
},
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use crate::trigger_helpers::TriggerJobArgs;
|
||||
use axum::{extract::Request, Router};
|
||||
use http::HeaderMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::prelude::FromRow;
|
||||
use sqlx::types::Json as SqlxJson;
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
use windmill_common::{
|
||||
error::{Error as WindmillError, Result as WindmillResult},
|
||||
triggers::TriggerKind,
|
||||
utils::empty_as_none,
|
||||
};
|
||||
|
||||
#[derive(sqlx::Type, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
#[sqlx(type_name = "DELIVERY_MODE", rename_all = "lowercase")]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum DeliveryType {
|
||||
Pull,
|
||||
Push,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl Default for DeliveryType {
|
||||
fn default() -> Self {
|
||||
Self::Pull
|
||||
@@ -41,7 +32,6 @@ impl Default for DeliveryType {
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct PushConfig {
|
||||
#[serde(deserialize_with = "empty_as_none")]
|
||||
route_path: Option<String>,
|
||||
@@ -52,7 +42,6 @@ pub struct PushConfig {
|
||||
}
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct CreateUpdateConfig {
|
||||
pub delivery_type: DeliveryType,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
@@ -61,7 +50,6 @@ pub struct CreateUpdateConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct ExistingGcpSubscription {
|
||||
pub subscription_id: String,
|
||||
pub base_endpoint: String,
|
||||
@@ -70,18 +58,15 @@ pub struct ExistingGcpSubscription {
|
||||
#[derive(Debug, Deserialize, Serialize, sqlx::Type)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[sqlx(type_name = "GCP_SUBSCRIPTION_MODE", rename_all = "snake_case")]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum SubscriptionMode {
|
||||
Existing,
|
||||
CreateUpdate,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn start_consuming_gcp_pubsub_event(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
@@ -89,7 +74,6 @@ pub fn start_consuming_gcp_pubsub_event(
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn manage_google_subscription(
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
@@ -107,7 +91,6 @@ pub async fn manage_google_subscription(
|
||||
Ok(CreateUpdateConfig::default())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn process_google_push_request(
|
||||
_headers: HeaderMap,
|
||||
_request: Request,
|
||||
@@ -115,7 +98,6 @@ pub async fn process_google_push_request(
|
||||
Ok((String::new(), HashMap::new()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_jwt_token(
|
||||
_db: &DB,
|
||||
_user_db: UserDB,
|
||||
@@ -128,13 +110,11 @@ pub async fn validate_jwt_token(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn gcp_push_route_handler() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize, Debug)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct GcpTrigger {
|
||||
pub gcp_resource_path: String,
|
||||
pub subscription_id: String,
|
||||
@@ -155,7 +135,7 @@ pub struct GcpTrigger {
|
||||
pub last_server_ping: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
|
||||
impl TriggerJobArgs<String> for GcpTrigger {
|
||||
fn v1_payload_fn(payload: String) -> HashMap<String, Box<RawValue>> {
|
||||
HashMap::from([("payload".to_string(), to_raw_value(&payload))])
|
||||
9
backend/windmill-api/src/git_sync_ee.rs
Normal file
9
backend/windmill-api/src/git_sync_ee.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use axum::routing::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::git_sync_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::routing::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -14,7 +14,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "http_trigger")]
|
||||
use crate::http_trigger_args::{HttpMethod, RawHttpTriggerArgs};
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::job_helpers_oss::get_workspace_s3_resource;
|
||||
use crate::job_helpers_ee::get_workspace_s3_resource;
|
||||
use crate::resources::try_get_resource_from_db_as;
|
||||
use crate::trigger_helpers::{get_runnable_format, RunnableId};
|
||||
use crate::utils::{non_empty_str, ExpiringCacheEntry};
|
||||
@@ -33,7 +33,7 @@ use std::borrow::Cow;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::Error;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
|
||||
9
backend/windmill-api/src/indexer_ee.rs
Normal file
9
backend/windmill-api/src/indexer_ee.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::indexer_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::inkeep_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -1,45 +1,34 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::job_helpers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::Serialize;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use uuid::Uuid;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::s3_helpers::StorageResourceType;
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{ObjectStore, PutMultipartOpts};
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::error;
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource};
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use bytes::Bytes;
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use futures::Stream;
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use axum::response::Response;
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct UploadFileResponse {
|
||||
pub file_key: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct LoadImagePreviewQuery {
|
||||
#[allow(dead_code)]
|
||||
pub file_key: String,
|
||||
@@ -48,7 +37,6 @@ pub struct LoadImagePreviewQuery {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct DownloadFileQuery {
|
||||
#[allow(dead_code)]
|
||||
pub file_key: String,
|
||||
@@ -58,12 +46,11 @@ pub struct DownloadFileQuery {
|
||||
pub s3_resource_path: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn get_workspace_s3_resource<'c>(
|
||||
_authed: &ApiAuthed,
|
||||
_db: &DB,
|
||||
@@ -76,12 +63,10 @@ pub async fn get_workspace_s3_resource<'c>(
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn get_random_file_name(_file_extension: Option<String>) -> String {
|
||||
unimplemented!("Not implemented in Windmill's Open Source repository")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn get_s3_resource<'c>(
|
||||
_authed: &ApiAuthed,
|
||||
_db: &DB,
|
||||
@@ -97,7 +82,7 @@ pub async fn get_s3_resource<'c>(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn upload_file_from_req(
|
||||
_s3_client: Arc<dyn ObjectStore>,
|
||||
_file_key: &str,
|
||||
@@ -109,7 +94,7 @@ pub async fn upload_file_from_req(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn upload_file_internal(
|
||||
_s3_client: Arc<dyn ObjectStore>,
|
||||
_file_key: &str,
|
||||
@@ -121,7 +106,7 @@ pub async fn upload_file_internal(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn download_s3_file_internal(
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
@@ -64,7 +64,7 @@ use sqlx::types::JsonRawValue;
|
||||
use sqlx::{types::Uuid, FromRow, Postgres, Transaction};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use urlencoding::encode;
|
||||
use windmill_audit::audit_oss::{audit_log, AuditAuthor};
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE};
|
||||
use windmill_common::{
|
||||
@@ -83,6 +83,8 @@ use windmill_common::{
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS;
|
||||
#[cfg(feature = "prometheus")]
|
||||
use windmill_common::{METRICS_DEBUG_ENABLED, METRICS_ENABLED};
|
||||
|
||||
@@ -1056,7 +1058,7 @@ async fn get_logs_from_store(
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
tracing::debug!("Getting logs from store: {file_index:?}");
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
tracing::debug!("object store client present, streaming from there");
|
||||
|
||||
let logs = logs.to_string();
|
||||
@@ -3183,7 +3185,7 @@ async fn check_tag_available_for_workspace(
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn check_license_key_valid() -> error::Result<()> {
|
||||
use windmill_common::ee_oss::LICENSE_KEY_VALID;
|
||||
use windmill_common::ee::LICENSE_KEY_VALID;
|
||||
|
||||
let valid = *LICENSE_KEY_VALID.read().await;
|
||||
if !valid {
|
||||
@@ -4960,7 +4962,10 @@ async fn run_bundle_preview_script(
|
||||
uploaded = true;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
let object_store = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone();
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
@@ -5658,7 +5663,7 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
let file = os
|
||||
.get(&object_store::path::Path::from(format!("logs/{file_p}")))
|
||||
.await;
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::kafka_triggers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct KafkaResourceSecurity {}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn start_kafka_consumers(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
@@ -27,11 +17,9 @@ pub fn start_kafka_consumers(
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum KafkaTriggerConfigConnection {}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct KafkaTrigger {
|
||||
pub workspace_id: String,
|
||||
pub path: String,
|
||||
@@ -51,4 +39,4 @@ pub struct KafkaTrigger {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,15 @@
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::ExternalJwks;
|
||||
use crate::ee::ExternalJwks;
|
||||
#[cfg(feature = "embedding")]
|
||||
use crate::embeddings::load_embeddings_db;
|
||||
#[cfg(feature = "oauth2")]
|
||||
use crate::oauth2_oss::AllClients;
|
||||
use crate::oauth2_ee::AllClients;
|
||||
#[cfg(feature = "oauth2")]
|
||||
use crate::oauth2_oss::SlackVerifier;
|
||||
use crate::oauth2_ee::SlackVerifier;
|
||||
#[cfg(feature = "smtp")]
|
||||
use crate::smtp_server_oss::SmtpServer;
|
||||
use crate::smtp_server_ee::SmtpServer;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
use crate::mcp::{setup_mcp_server, Runner as McpRunner};
|
||||
@@ -28,7 +28,7 @@ use crate::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
use agent_workers_oss::AgentCache;
|
||||
use agent_workers_ee::AgentCache;
|
||||
|
||||
use anyhow::Context;
|
||||
use argon2::Argon2;
|
||||
@@ -58,13 +58,11 @@ use windmill_common::db::UserDB;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{utils::GIT_VERSION, BASE_URL, INSTANCE_NAME};
|
||||
|
||||
use crate::scim_oss::has_scim_token;
|
||||
use crate::scim_ee::has_scim_token;
|
||||
use windmill_common::error::AppError;
|
||||
|
||||
#[cfg(all(feature = "agent_worker_server", feature = "private"))]
|
||||
pub mod agent_workers_ee;
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
mod agent_workers_oss;
|
||||
mod agent_workers_ee;
|
||||
mod ai;
|
||||
mod apps;
|
||||
pub mod args;
|
||||
@@ -75,9 +73,7 @@ mod concurrency_groups;
|
||||
mod configs;
|
||||
mod db;
|
||||
mod drafts;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
pub mod ee_oss;
|
||||
pub mod embeddings;
|
||||
mod favorite;
|
||||
mod flows;
|
||||
@@ -90,93 +86,56 @@ mod http_trigger_args;
|
||||
mod http_trigger_auth;
|
||||
#[cfg(feature = "http_trigger")]
|
||||
pub mod http_triggers;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod indexer_ee;
|
||||
mod indexer_oss;
|
||||
#[cfg(feature = "private")]
|
||||
mod inkeep_ee;
|
||||
mod inkeep_oss;
|
||||
mod indexer_ee;
|
||||
mod inputs;
|
||||
mod integration;
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
mod postgres_triggers;
|
||||
|
||||
mod approvals;
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
pub mod apps_ee;
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod apps_oss;
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))]
|
||||
pub mod gcp_triggers_ee;
|
||||
mod apps_ee;
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
mod gcp_triggers_oss;
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
pub mod git_sync_ee;
|
||||
mod gcp_triggers_ee;
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod git_sync_oss;
|
||||
#[cfg(all(feature = "parquet", feature = "private"))]
|
||||
pub mod job_helpers_ee;
|
||||
mod git_sync_ee;
|
||||
#[cfg(feature = "parquet")]
|
||||
mod job_helpers_oss;
|
||||
mod job_helpers_ee;
|
||||
pub mod job_metrics;
|
||||
pub mod jobs;
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka", feature = "private"))]
|
||||
pub mod kafka_triggers_ee;
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
mod kafka_triggers_oss;
|
||||
mod kafka_triggers_ee;
|
||||
#[cfg(feature = "mqtt_trigger")]
|
||||
mod mqtt_triggers;
|
||||
#[cfg(all(feature = "enterprise", feature = "nats", feature = "private"))]
|
||||
pub mod nats_triggers_ee;
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
mod nats_triggers_oss;
|
||||
#[cfg(all(feature = "oauth2", feature = "private"))]
|
||||
pub mod oauth2_ee;
|
||||
mod nats_triggers_ee;
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub mod oauth2_oss;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod oidc_ee;
|
||||
mod oidc_oss;
|
||||
pub mod oauth2_ee;
|
||||
mod oidc_ee;
|
||||
mod raw_apps;
|
||||
mod resources;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod saml_ee;
|
||||
mod saml_oss;
|
||||
mod saml_ee;
|
||||
mod schedule;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod scim_ee;
|
||||
mod scim_oss;
|
||||
mod scim_ee;
|
||||
mod scripts;
|
||||
mod service_logs;
|
||||
mod settings;
|
||||
mod slack_approvals;
|
||||
#[cfg(all(feature = "smtp", feature = "private"))]
|
||||
pub mod smtp_server_ee;
|
||||
#[cfg(feature = "smtp")]
|
||||
mod smtp_server_oss;
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger", feature = "private"))]
|
||||
pub mod sqs_triggers_ee;
|
||||
mod smtp_server_ee;
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
mod sqs_triggers_oss;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod teams_approvals_ee;
|
||||
mod teams_approvals_oss;
|
||||
mod sqs_triggers_ee;
|
||||
mod teams_approvals_ee;
|
||||
mod trigger_helpers;
|
||||
|
||||
mod static_assets;
|
||||
#[cfg(all(feature = "stripe", feature = "enterprise", feature = "private"))]
|
||||
pub mod stripe_ee;
|
||||
#[cfg(all(feature = "stripe", feature = "enterprise"))]
|
||||
mod stripe_oss;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod teams_ee;
|
||||
mod teams_oss;
|
||||
mod stripe_ee;
|
||||
mod teams_ee;
|
||||
mod tracing_init;
|
||||
mod triggers;
|
||||
mod users;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod users_ee;
|
||||
mod users_oss;
|
||||
mod users_ee;
|
||||
mod utils;
|
||||
mod variables;
|
||||
pub mod webhook_util;
|
||||
@@ -184,11 +143,9 @@ pub mod webhook_util;
|
||||
mod websocket_triggers;
|
||||
mod workers;
|
||||
mod workspaces;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod workspaces_ee;
|
||||
mod workspaces_ee;
|
||||
mod workspaces_export;
|
||||
mod workspaces_extra;
|
||||
mod workspaces_oss;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
mod mcp;
|
||||
@@ -261,9 +218,9 @@ type IndexReader = ();
|
||||
type ServiceLogIndexReader = ();
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
type IndexReader = windmill_indexer::completed_runs_oss::IndexReader;
|
||||
type IndexReader = windmill_indexer::completed_runs_ee::IndexReader;
|
||||
#[cfg(feature = "tantivy")]
|
||||
type ServiceLogIndexReader = windmill_indexer::service_logs_oss::ServiceLogIndexReader;
|
||||
type ServiceLogIndexReader = windmill_indexer::service_logs_ee::ServiceLogIndexReader;
|
||||
|
||||
pub async fn run_server(
|
||||
db: DB,
|
||||
@@ -321,7 +278,7 @@ pub async fn run_server(
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
let sp_extension = Arc::new(saml_oss::build_sp_extension().await?);
|
||||
let sp_extension = Arc::new(saml_ee::build_sp_extension().await?);
|
||||
|
||||
if server_mode {
|
||||
#[cfg(feature = "embedding")]
|
||||
@@ -360,7 +317,7 @@ pub async fn run_server(
|
||||
let job_helpers_service = {
|
||||
#[cfg(feature = "parquet")]
|
||||
{
|
||||
job_helpers_oss::workspaced_service()
|
||||
job_helpers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parquet"))]
|
||||
@@ -372,7 +329,7 @@ pub async fn run_server(
|
||||
let kafka_triggers_service = {
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
kafka_triggers_oss::workspaced_service()
|
||||
kafka_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "kafka")))]
|
||||
@@ -384,7 +341,7 @@ pub async fn run_server(
|
||||
let nats_triggers_service = {
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
{
|
||||
nats_triggers_oss::workspaced_service()
|
||||
nats_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "nats")))]
|
||||
@@ -408,7 +365,7 @@ pub async fn run_server(
|
||||
let gcp_triggers_service = {
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
{
|
||||
gcp_triggers_oss::workspaced_service()
|
||||
gcp_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "gcp_trigger")))]
|
||||
@@ -420,7 +377,7 @@ pub async fn run_server(
|
||||
let sqs_triggers_service = {
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
{
|
||||
sqs_triggers_oss::workspaced_service()
|
||||
sqs_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "sqs_trigger")))]
|
||||
@@ -475,13 +432,13 @@ pub async fn run_server(
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
let kafka_killpill_rx = killpill_rx.resubscribe();
|
||||
kafka_triggers_oss::start_kafka_consumers(db.clone(), kafka_killpill_rx);
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
{
|
||||
let nats_killpill_rx = killpill_rx.resubscribe();
|
||||
nats_triggers_oss::start_nats_consumers(db.clone(), nats_killpill_rx);
|
||||
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
@@ -499,13 +456,13 @@ pub async fn run_server(
|
||||
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
|
||||
{
|
||||
let sqs_killpill_rx = killpill_rx.resubscribe();
|
||||
sqs_triggers_oss::start_sqs(db.clone(), sqs_killpill_rx);
|
||||
sqs_triggers_ee::start_sqs(db.clone(), sqs_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
{
|
||||
let gcp_killpill_rx = killpill_rx.resubscribe();
|
||||
gcp_triggers_oss::start_consuming_gcp_pubsub_event(db.clone(), gcp_killpill_rx);
|
||||
gcp_triggers_ee::start_consuming_gcp_pubsub_event(db.clone(), gcp_killpill_rx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +497,7 @@ pub async fn run_server(
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) =
|
||||
if server_mode {
|
||||
agent_workers_oss::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
agent_workers_ee::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
} else {
|
||||
(Router::new(), vec![], None)
|
||||
};
|
||||
@@ -578,7 +535,7 @@ pub async fn run_server(
|
||||
.nest("/oauth", {
|
||||
#[cfg(feature = "oauth2")]
|
||||
{
|
||||
oauth2_oss::workspaced_service()
|
||||
oauth2_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
@@ -595,7 +552,7 @@ pub async fn run_server(
|
||||
)
|
||||
.nest("/variables", variables::workspaced_service())
|
||||
.nest("/workspaces", workspaces::workspaced_service())
|
||||
.nest("/oidc", oidc_oss::workspaced_service())
|
||||
.nest("/oidc", oidc_ee::workspaced_service())
|
||||
.nest("/http_triggers", http_triggers_service)
|
||||
.nest("/websocket_triggers", websocket_triggers_service)
|
||||
.nest("/kafka_triggers", kafka_triggers_service)
|
||||
@@ -622,23 +579,22 @@ pub async fn run_server(
|
||||
.nest("/schedules", schedule::global_service())
|
||||
.nest("/embeddings", embeddings::global_service())
|
||||
.nest("/ai", ai::global_service())
|
||||
.nest("/inkeep", inkeep_oss::global_service())
|
||||
.route_layer(from_extractor::<ApiAuthed>())
|
||||
.route_layer(from_extractor::<users::Tokened>())
|
||||
.nest("/jobs", jobs::global_root_service())
|
||||
.nest(
|
||||
"/srch/w/:workspace_id/index",
|
||||
indexer_oss::workspaced_service(),
|
||||
indexer_ee::workspaced_service(),
|
||||
)
|
||||
.nest("/srch/index", indexer_oss::global_service())
|
||||
.nest("/oidc", oidc_oss::global_service())
|
||||
.nest("/srch/index", indexer_ee::global_service())
|
||||
.nest("/oidc", oidc_ee::global_service())
|
||||
.nest(
|
||||
"/saml",
|
||||
saml_oss::global_service().layer(Extension(Arc::clone(&sp_extension))),
|
||||
saml_ee::global_service().layer(Extension(Arc::clone(&sp_extension))),
|
||||
)
|
||||
.nest(
|
||||
"/scim",
|
||||
scim_oss::global_service()
|
||||
scim_ee::global_service()
|
||||
.route_layer(axum::middleware::from_fn(has_scim_token)),
|
||||
)
|
||||
.nest("/concurrency_groups", concurrency_groups::global_service())
|
||||
@@ -646,7 +602,7 @@ pub async fn run_server(
|
||||
.nest("/apps_u", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
apps_oss::global_unauthed_service()
|
||||
apps_ee::global_unauthed_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -665,7 +621,7 @@ pub async fn run_server(
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_oss::global_service().layer(Extension(agent_cache.clone()))
|
||||
agent_workers_ee::global_service().layer(Extension(agent_cache.clone()))
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
@@ -690,7 +646,7 @@ pub async fn run_server(
|
||||
.nest("/teams", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
teams_oss::teams_service()
|
||||
teams_ee::teams_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -704,12 +660,12 @@ pub async fn run_server(
|
||||
)
|
||||
.route(
|
||||
"/w/:workspace_id/jobs/teams_approval/:job_id",
|
||||
get(teams_approvals_oss::request_teams_approval),
|
||||
get(teams_approvals_ee::request_teams_approval),
|
||||
)
|
||||
.nest("/w/:workspace_id/github_app", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
git_sync_oss::workspaced_service()
|
||||
git_sync_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -718,7 +674,7 @@ pub async fn run_server(
|
||||
.nest("/github_app", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
git_sync_oss::global_service()
|
||||
git_sync_ee::global_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -739,7 +695,7 @@ pub async fn run_server(
|
||||
.nest("/oauth", {
|
||||
#[cfg(feature = "oauth2")]
|
||||
{
|
||||
oauth2_oss::global_service().layer(Extension(Arc::clone(&sp_extension)))
|
||||
oauth2_ee::global_service().layer(Extension(Arc::clone(&sp_extension)))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
@@ -765,7 +721,7 @@ pub async fn run_server(
|
||||
{
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
|
||||
{
|
||||
gcp_triggers_oss::gcp_push_route_handler()
|
||||
gcp_triggers_ee::gcp_push_route_handler()
|
||||
}
|
||||
#[cfg(not(all(feature = "enterprise", feature = "gcp_trigger")))]
|
||||
{
|
||||
@@ -886,7 +842,7 @@ async fn ee_license() -> &'static str {
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn ee_license() -> String {
|
||||
use windmill_common::ee_oss::{LICENSE_KEY_ID, LICENSE_KEY_VALID};
|
||||
use windmill_common::ee::{LICENSE_KEY_ID, LICENSE_KEY_VALID};
|
||||
|
||||
if *LICENSE_KEY_VALID.read().await {
|
||||
LICENSE_KEY_ID.read().await.clone()
|
||||
|
||||
@@ -39,7 +39,7 @@ use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{FromRow, Type};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{self, JsonResult},
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::nats_triggers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NatsResourceAuth {}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum NatsTriggerConfigConnection {}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct NatsTrigger {
|
||||
pub workspace_id: String,
|
||||
pub path: String,
|
||||
@@ -52,4 +40,4 @@ pub struct NatsTrigger {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::oauth2_ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
@@ -10,50 +6,39 @@ pub use crate::oauth2_ee::*;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::{routing::get, Json, Router};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use hmac::Mac;
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
use itertools::Itertools;
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
use oauth2::{Client as OClient, *};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use sqlx::{Postgres, Transaction};
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
use windmill_common::more_serde::maybe_number_opt;
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
use crate::OAUTH_CLIENTS;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::error;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::oauth2::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use std::str;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list_logins", get(list_logins))
|
||||
.route("/list_connects", get(list_connects))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientWithScopes {
|
||||
_client: OClient,
|
||||
@@ -63,10 +48,9 @@ pub struct ClientWithScopes {
|
||||
_allowed_domains: Option<Vec<String>>,
|
||||
_userinfo_url: Option<String>,
|
||||
}
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub type BasicClientsMap = HashMap<String, ClientWithScopes>;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct OAuthConfig {
|
||||
auth_url: String,
|
||||
@@ -78,7 +62,6 @@ pub struct OAuthConfig {
|
||||
req_body_auth: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct OAuthClient {
|
||||
id: String,
|
||||
@@ -88,7 +71,7 @@ pub struct OAuthClient {
|
||||
login_config: Option<OAuthConfig>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
#[derive(Debug)]
|
||||
pub struct AllClients {
|
||||
pub logins: BasicClientsMap,
|
||||
@@ -96,7 +79,7 @@ pub struct AllClients {
|
||||
pub slack: Option<OClient>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub async fn build_oauth_clients(
|
||||
_base_url: &str,
|
||||
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
|
||||
@@ -110,7 +93,7 @@ pub async fn build_oauth_clients(
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct TokenResponse {
|
||||
access_token: AccessToken,
|
||||
@@ -124,20 +107,17 @@ pub struct TokenResponse {
|
||||
scope: Option<Vec<Scope>>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[derive(Serialize)]
|
||||
struct Logins {
|
||||
oauth: Vec<String>,
|
||||
saml: Option<String>,
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
async fn list_logins() -> error::JsonResult<Logins> {
|
||||
// Implementation is not open source
|
||||
return Ok(Json(Logins { oauth: vec![], saml: None }));
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(all(feature = "oauth2", not(feature = "private")))]
|
||||
#[cfg(feature = "oauth2")]
|
||||
async fn list_connects() -> error::JsonResult<Vec<String>> {
|
||||
Ok(Json(
|
||||
(&OAUTH_CLIENTS.read().await.connects)
|
||||
@@ -147,14 +127,12 @@ async fn list_connects() -> error::JsonResult<Vec<String>> {
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(not(all(feature = "oauth2", not(feature = "private"))))]
|
||||
async fn list_connects() -> windmill_common::error::JsonResult<Vec<String>> {
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
async fn list_connects() -> error::JsonResult<Vec<String>> {
|
||||
// Implementation is not open source
|
||||
return Ok(axum::Json(vec![]));
|
||||
return Ok(Json(vec![]));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn _refresh_token<'c>(
|
||||
_tx: Transaction<'c, Postgres>,
|
||||
_path: &str,
|
||||
@@ -168,7 +146,6 @@ pub async fn _refresh_token<'c>(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn check_nb_of_user(db: &DB) -> error::Result<()> {
|
||||
let nb_users_sso =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",)
|
||||
@@ -194,11 +171,10 @@ pub async fn check_nb_of_user(db: &DB) -> error::Result<()> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct SlackVerifier {
|
||||
_mac: HmacSha256,
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
|
||||
impl SlackVerifier {
|
||||
pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
|
||||
HmacSha256::new_from_slice(secret.as_ref())
|
||||
@@ -1,7 +1,3 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::oidc_ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2023
|
||||
@@ -10,15 +6,12 @@ pub use crate::oidc_ee::*;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use std::collections::{
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
postgres_triggers::mapper::{Mapper, MappingInfo},
|
||||
resources::try_get_resource_from_db_as,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
@@ -18,20 +19,21 @@ use quick_cache::sync::Cache;
|
||||
use rust_postgres::types::Type;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{postgres::types::Oid, Connection, FromRow, PgConnection};
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use sqlx::{postgres::types::Oid, FromRow, PgConnection};
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::Error;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{self, Error, JsonResult, Result},
|
||||
utils::{empty_as_none, not_found_if_none, paginate, Pagination, StripPath},
|
||||
error::{self, JsonResult, Result},
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath, empty_as_none},
|
||||
worker::CLOUD_HOSTED,
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
|
||||
use super::{
|
||||
check_if_valid_publication_for_postgres_version, create_logical_replication_slot,
|
||||
create_pg_publication, drop_publication, generate_random_string, get_pg_connection,
|
||||
ERROR_PUBLICATION_NAME_NOT_EXISTS,
|
||||
create_logical_replication_slot_query, create_publication_query, drop_publication_query,
|
||||
generate_random_string, get_database_connection, get_raw_postgres_connection,
|
||||
ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
@@ -51,17 +53,15 @@ pub struct Postgres {
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct TableToTrack {
|
||||
pub table_name: String,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub where_clause: Option<String>,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub columns_name: Option<Vec<String>>,
|
||||
pub columns_name: Vec<String>,
|
||||
}
|
||||
|
||||
impl TableToTrack {
|
||||
fn new(
|
||||
table_name: String,
|
||||
where_clause: Option<String>,
|
||||
columns_name: Option<Vec<String>>,
|
||||
columns_name: Vec<String>,
|
||||
) -> TableToTrack {
|
||||
TableToTrack { table_name, where_clause, columns_name }
|
||||
}
|
||||
@@ -99,6 +99,7 @@ pub struct EditPostgresTrigger {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
|
||||
pub struct NewPostgresTrigger {
|
||||
path: String,
|
||||
script_path: String,
|
||||
@@ -123,7 +124,7 @@ pub async fn test_postgres_connection(
|
||||
Json(test_postgres): Json<TestPostgres>,
|
||||
) -> Result<()> {
|
||||
let connect_f = async {
|
||||
get_pg_connection(
|
||||
get_database_connection(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
@@ -188,7 +189,7 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
if !track_specific_columns_in_table && table_to_track.columns_name.is_some() {
|
||||
if !track_specific_columns_in_table && !table_to_track.columns_name.is_empty() {
|
||||
track_specific_columns_in_table = true;
|
||||
}
|
||||
}
|
||||
@@ -266,16 +267,39 @@ impl PostgresPublicationReplication {
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_if_publication_exist(
|
||||
connection: &mut PgConnection,
|
||||
publication_name: &str,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"SELECT pubname FROM pg_publication WHERE pubname = $1",
|
||||
publication_name
|
||||
)
|
||||
.fetch_one(connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
sqlx::Error::RowNotFound => {
|
||||
Error::BadRequest(ERROR_PUBLICATION_NAME_NOT_EXISTS.to_string())
|
||||
}
|
||||
err => Error::SqlErr { error: err, location: "pg_trigger".to_string() },
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_if_logical_replication_slot_exist(
|
||||
pg_connection: &mut PgConnection,
|
||||
connection: &mut PgConnection,
|
||||
replication_slot_name: &str,
|
||||
) -> Result<bool> {
|
||||
let exists = sqlx::query("SELECT slot_name FROM pg_replication_slots where slot_name = $1")
|
||||
.bind(&replication_slot_name)
|
||||
.fetch_optional(pg_connection)
|
||||
.await?
|
||||
.is_some();
|
||||
Ok(exists)
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"SELECT slot_name FROM pg_replication_slots where slot_name = $1",
|
||||
&replication_slot_name
|
||||
)
|
||||
.fetch_one(connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
_ => Error::BadRequest(ERROR_REPLICATION_SLOT_NOT_EXISTS.to_string()),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_custom_slot_and_publication_inner(
|
||||
@@ -286,30 +310,33 @@ async fn create_custom_slot_and_publication_inner(
|
||||
w_id: &str,
|
||||
publication: &PublicationData,
|
||||
) -> Result<PostgresPublicationReplication> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let publication_name = format!("windmill_trigger_{}", generate_random_string());
|
||||
let replication_slot_name = publication_name.clone();
|
||||
|
||||
let query = create_publication_query(
|
||||
&publication_name,
|
||||
publication.table_to_track.as_deref(),
|
||||
&publication
|
||||
.transaction_to_track
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.collect_vec(),
|
||||
);
|
||||
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
&postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx = pg_connection.begin().await?;
|
||||
let publication_name = format!("windmill_trigger_{}", generate_random_string());
|
||||
let replication_slot_name = publication_name.clone();
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
create_logical_replication_slot(&mut tx, &replication_slot_name).await?;
|
||||
let query = create_logical_replication_slot_query(&replication_slot_name);
|
||||
|
||||
create_pg_publication(
|
||||
&mut tx,
|
||||
&publication_name,
|
||||
publication.table_to_track.as_deref(),
|
||||
&publication.transaction_to_track,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
Ok(PostgresPublicationReplication::new(
|
||||
publication_name,
|
||||
@@ -317,38 +344,6 @@ async fn create_custom_slot_and_publication_inner(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_postgres_version_internal(pg_connection: &mut PgConnection) -> Result<String> {
|
||||
let postgres_version: String = sqlx::query_scalar("SHOW server_version;")
|
||||
.fetch_one(&mut *pg_connection)
|
||||
.await
|
||||
.map_err(|e| Error::Anyhow {
|
||||
error: anyhow::anyhow!("Failed to retrieve PostgreSQL version: {}", e),
|
||||
location: "postgres_triggers/handler.rs@379".to_string(),
|
||||
})?;
|
||||
|
||||
Ok(postgres_version)
|
||||
}
|
||||
|
||||
pub async fn get_postgres_version(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
authed.clone(),
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let postgres_version = get_postgres_version_internal(&mut pg_connection).await?;
|
||||
|
||||
Ok(postgres_version)
|
||||
}
|
||||
|
||||
pub async fn create_postgres_trigger(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -382,7 +377,6 @@ pub async fn create_postgres_trigger(
|
||||
if publication.is_none() {
|
||||
return Err(Error::BadRequest("publication must be set".to_string()));
|
||||
}
|
||||
|
||||
let PostgresPublicationReplication { publication_name, replication_slot_name } =
|
||||
create_custom_slot_and_publication_inner(
|
||||
authed.clone(),
|
||||
@@ -583,7 +577,7 @@ impl PublicationData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Debug, Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SlotList {
|
||||
slot_name: Option<String>,
|
||||
active: Option<bool>,
|
||||
@@ -595,7 +589,7 @@ pub async fn list_slot_name(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
) -> Result<Json<Vec<SlotList>>> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -604,7 +598,8 @@ pub async fn list_slot_name(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let slots: Vec<SlotList> = sqlx::query_as(
|
||||
let slots = sqlx::query_as!(
|
||||
SlotList,
|
||||
r#"
|
||||
SELECT
|
||||
slot_name,
|
||||
@@ -614,9 +609,9 @@ pub async fn list_slot_name(
|
||||
WHERE
|
||||
plugin = 'pgoutput' AND
|
||||
slot_type = 'logical';
|
||||
"#,
|
||||
"#
|
||||
)
|
||||
.fetch_all(&mut pg_connection)
|
||||
.fetch_all(&mut connection)
|
||||
.await?;
|
||||
|
||||
Ok(Json(slots))
|
||||
@@ -634,7 +629,7 @@ pub async fn create_slot(
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
Json(Slot { name }): Json<Slot>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -643,42 +638,13 @@ pub async fn create_slot(
|
||||
)
|
||||
.await?;
|
||||
|
||||
create_logical_replication_slot(&mut pg_connection, &name).await?;
|
||||
let query = create_logical_replication_slot_query(&name);
|
||||
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
Ok(format!("Replication slot {} created!", name))
|
||||
}
|
||||
|
||||
pub async fn drop_logical_replication_slot(
|
||||
pg_connection: &mut PgConnection,
|
||||
slot_name: &str,
|
||||
) -> Result<()> {
|
||||
let active_pid: Option<i32> = sqlx::query_scalar(
|
||||
r#"SELECT
|
||||
active_pid
|
||||
FROM
|
||||
pg_replication_slots
|
||||
WHERE
|
||||
slot_name = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&slot_name)
|
||||
.fetch_optional(&mut *pg_connection)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if let Some(pid) = active_pid {
|
||||
sqlx::query("SELECT pg_terminate_backend($1)")
|
||||
.bind(pid)
|
||||
.execute(&mut *pg_connection)
|
||||
.await?;
|
||||
}
|
||||
sqlx::query("SELECT pg_drop_replication_slot($1)")
|
||||
.bind(&slot_name)
|
||||
.execute(pg_connection)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn drop_slot_name(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -686,14 +652,45 @@ pub async fn drop_slot_name(
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
Json(Slot { name }): Json<Slot>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection =
|
||||
get_pg_connection(authed, Some(user_db), &db, &postgres_resource_path, &w_id).await?;
|
||||
let database = try_get_resource_from_db_as::<Postgres>(
|
||||
authed,
|
||||
Some(user_db),
|
||||
&db,
|
||||
&postgres_resource_path,
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop_logical_replication_slot(&mut pg_connection, &name).await?;
|
||||
let mut connection = get_raw_postgres_connection(&database).await?;
|
||||
|
||||
let active_pid = sqlx::query_scalar!(
|
||||
r#"SELECT
|
||||
active_pid
|
||||
FROM
|
||||
pg_replication_slots
|
||||
WHERE
|
||||
slot_name = $1
|
||||
"#,
|
||||
&name
|
||||
)
|
||||
.fetch_optional(&mut connection)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if let Some(pid) = active_pid {
|
||||
sqlx::query("SELECT pg_terminate_backend($1)")
|
||||
.bind(pid)
|
||||
.execute(&mut connection)
|
||||
.await?;
|
||||
}
|
||||
sqlx::query("SELECT pg_drop_replication_slot($1)")
|
||||
.bind(&name)
|
||||
.execute(&mut connection)
|
||||
.await?;
|
||||
|
||||
Ok(format!("Replication slot {} deleted!", name))
|
||||
}
|
||||
#[derive(FromRow, Debug, Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PublicationName {
|
||||
publication_name: String,
|
||||
}
|
||||
@@ -704,7 +701,7 @@ pub async fn list_database_publication(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
) -> Result<Json<Vec<String>>> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -713,10 +710,12 @@ pub async fn list_database_publication(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let publication_names: Vec<PublicationName> =
|
||||
sqlx::query_as("SELECT pubname AS publication_name FROM pg_publication;")
|
||||
.fetch_all(&mut pg_connection)
|
||||
.await?;
|
||||
let publication_names = sqlx::query_as!(
|
||||
PublicationName,
|
||||
"SELECT pubname AS publication_name FROM pg_publication;"
|
||||
)
|
||||
.fetch_all(&mut connection)
|
||||
.await?;
|
||||
|
||||
let publications = publication_names
|
||||
.iter()
|
||||
@@ -732,7 +731,7 @@ pub async fn get_publication_info(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>,
|
||||
) -> Result<Json<PublicationData>> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -742,11 +741,11 @@ pub async fn get_publication_info(
|
||||
.await?;
|
||||
|
||||
let publication_data =
|
||||
get_publication_scope_and_transaction(&mut pg_connection, &publication_name).await;
|
||||
get_publication_scope_and_transaction(&mut connection, &publication_name).await;
|
||||
|
||||
let (all_table, transaction_to_track) = match publication_data {
|
||||
Ok(Some(pub_data)) => pub_data,
|
||||
Ok(None) => {
|
||||
Ok(pub_data) => pub_data,
|
||||
Err(Error::SqlErr { error: sqlx::Error::RowNotFound, .. }) => {
|
||||
return Err(Error::NotFound(
|
||||
ERROR_PUBLICATION_NAME_NOT_EXISTS.to_string(),
|
||||
))
|
||||
@@ -755,7 +754,7 @@ pub async fn get_publication_info(
|
||||
};
|
||||
|
||||
let table_to_track = if !all_table {
|
||||
Some(get_tracked_relations(&mut pg_connection, &publication_name).await?)
|
||||
Some(get_tracked_relations(&mut connection, &publication_name).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -772,7 +771,9 @@ pub async fn create_publication(
|
||||
Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>,
|
||||
Json(publication_data): Json<PublicationData>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let PublicationData { table_to_track, transaction_to_track } = publication_data;
|
||||
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -781,19 +782,13 @@ pub async fn create_publication(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let PublicationData { table_to_track, transaction_to_track } = publication_data;
|
||||
|
||||
let mut tx = pg_connection.begin().await?;
|
||||
|
||||
create_pg_publication(
|
||||
&mut tx,
|
||||
let query = create_publication_query(
|
||||
&publication_name,
|
||||
table_to_track.as_deref(),
|
||||
&transaction_to_track,
|
||||
)
|
||||
.await?;
|
||||
&transaction_to_track.iter().map(AsRef::as_ref).collect_vec(),
|
||||
);
|
||||
|
||||
tx.commit().await?;
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
Ok(format!(
|
||||
"Publication {} successfully created!",
|
||||
@@ -807,7 +802,7 @@ pub async fn delete_publication(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -816,7 +811,9 @@ pub async fn delete_publication(
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop_publication(&mut pg_connection, &publication_name).await?;
|
||||
let query = drop_publication_query(&publication_name);
|
||||
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
|
||||
Ok(format!(
|
||||
"Publication {} successfully deleted!",
|
||||
@@ -824,37 +821,27 @@ pub async fn delete_publication(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn update_pg_publication(
|
||||
pg_connection: &mut PgConnection,
|
||||
pub fn get_update_publication_query(
|
||||
publication_name: &str,
|
||||
PublicationData { table_to_track, transaction_to_track }: PublicationData,
|
||||
all_table: Option<bool>,
|
||||
) -> Result<()> {
|
||||
all_table: bool,
|
||||
) -> Vec<String> {
|
||||
let quoted_publication_name = quote_identifier(&publication_name);
|
||||
|
||||
let transaction_to_track_as_str = transaction_to_track.iter().join(",");
|
||||
let mut queries = Vec::with_capacity(2);
|
||||
match table_to_track {
|
||||
Some(ref relations) if !relations.is_empty() => {
|
||||
//if all table is none it means that the publication do not exist in the database
|
||||
if all_table.unwrap_or(true) {
|
||||
if all_table.is_some() {
|
||||
drop_publication(pg_connection, &publication_name).await?;
|
||||
}
|
||||
create_pg_publication(
|
||||
pg_connection,
|
||||
if all_table {
|
||||
queries.push(drop_publication_query(&publication_name));
|
||||
queries.push(create_publication_query(
|
||||
&publication_name,
|
||||
table_to_track.as_deref(),
|
||||
&transaction_to_track,
|
||||
)
|
||||
.await?;
|
||||
&transaction_to_track.iter().map(AsRef::as_ref).collect_vec(),
|
||||
));
|
||||
} else {
|
||||
let pg_14 = check_if_valid_publication_for_postgres_version(
|
||||
pg_connection,
|
||||
table_to_track.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut query = String::from("");
|
||||
let mut first = true;
|
||||
|
||||
query.push_str("ALTER PUBLICATION ");
|
||||
query.push_str("ed_publication_name);
|
||||
query.push_str(" SET");
|
||||
@@ -864,20 +851,16 @@ pub async fn update_pg_publication(
|
||||
let quoted_schema = quote_identifier(&schema.schema_name);
|
||||
query.push_str("ed_schema);
|
||||
} else {
|
||||
if pg_14 && first {
|
||||
query.push_str(" TABLE ONLY ");
|
||||
first = false
|
||||
} else if !pg_14 {
|
||||
query.push_str(" TABLE ONLY ");
|
||||
}
|
||||
query.push_str(" TABLE ONLY ");
|
||||
for (j, table) in schema.table_to_track.iter().enumerate() {
|
||||
let table_name = quote_identifier(&table.table_name);
|
||||
let schema_name = quote_identifier(&schema.schema_name);
|
||||
let full_name = format!("{}.{}", &schema_name, &table_name);
|
||||
query.push_str(&full_name);
|
||||
if let Some(columns) = table.columns_name.as_ref() {
|
||||
if !table.columns_name.is_empty() {
|
||||
query.push_str(" (");
|
||||
let columns = columns
|
||||
let columns = table
|
||||
.columns_name
|
||||
.iter()
|
||||
.map(|column| quote_identifier(column))
|
||||
.join(", ");
|
||||
@@ -900,8 +883,9 @@ pub async fn update_pg_publication(
|
||||
query.push(',');
|
||||
}
|
||||
}
|
||||
query.push(';');
|
||||
|
||||
sqlx::query(&query).execute(&mut *pg_connection).await?;
|
||||
queries.push(query);
|
||||
|
||||
let mut query = String::new();
|
||||
|
||||
@@ -911,25 +895,23 @@ pub async fn update_pg_publication(
|
||||
" SET (publish = '{}');",
|
||||
transaction_to_track_as_str
|
||||
));
|
||||
|
||||
sqlx::query(&query).execute(pg_connection).await?;
|
||||
queries.push(query);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
drop_publication(pg_connection, &publication_name).await?;
|
||||
let query_to_execute = format!(
|
||||
queries.push(drop_publication_query(&publication_name));
|
||||
let to_execute = format!(
|
||||
r#"
|
||||
CREATE
|
||||
PUBLICATION {} FOR ALL TABLES WITH (publish = '{}');
|
||||
"#,
|
||||
quoted_publication_name, transaction_to_track_as_str
|
||||
);
|
||||
sqlx::query(&query_to_execute)
|
||||
.execute(pg_connection)
|
||||
.await?;
|
||||
queries.push(to_execute);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
|
||||
queries
|
||||
}
|
||||
|
||||
pub async fn alter_publication(
|
||||
@@ -939,7 +921,7 @@ pub async fn alter_publication(
|
||||
Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>,
|
||||
Json(publication_data): Json<PublicationData>,
|
||||
) -> Result<String> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -948,19 +930,16 @@ pub async fn alter_publication(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx = pg_connection.begin().await?;
|
||||
check_if_publication_exist(&mut connection, &publication_name).await?;
|
||||
|
||||
let publication = get_publication_scope_and_transaction(&mut tx, &publication_name).await?;
|
||||
let (all_table, _) =
|
||||
get_publication_scope_and_transaction(&mut connection, &publication_name).await?;
|
||||
|
||||
update_pg_publication(
|
||||
&mut tx,
|
||||
&publication_name,
|
||||
publication_data,
|
||||
publication.map(|publication| publication.0),
|
||||
)
|
||||
.await?;
|
||||
let queries = get_update_publication_query(&publication_name, publication_data, all_table);
|
||||
|
||||
tx.commit().await?;
|
||||
for query in queries {
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"Publication {} updated with success",
|
||||
@@ -969,9 +948,9 @@ pub async fn alter_publication(
|
||||
}
|
||||
|
||||
async fn get_publication_scope_and_transaction(
|
||||
pg_connection: &mut PgConnection,
|
||||
connection: &mut PgConnection,
|
||||
publication_name: &str,
|
||||
) -> Result<Option<(bool, Vec<String>)>> {
|
||||
) -> std::result::Result<(bool, Vec<String>), Error> {
|
||||
#[derive(Debug, Deserialize, FromRow)]
|
||||
struct PublicationTransaction {
|
||||
all_table: bool,
|
||||
@@ -980,7 +959,8 @@ async fn get_publication_scope_and_transaction(
|
||||
delete: bool,
|
||||
}
|
||||
|
||||
let publication: Option<PublicationTransaction> = sqlx::query_as(
|
||||
let transaction = sqlx::query_as!(
|
||||
PublicationTransaction,
|
||||
r#"
|
||||
SELECT
|
||||
puballtables AS all_table,
|
||||
@@ -992,100 +972,70 @@ async fn get_publication_scope_and_transaction(
|
||||
WHERE
|
||||
pubname = $1
|
||||
"#,
|
||||
publication_name
|
||||
)
|
||||
.bind(publication_name)
|
||||
.fetch_optional(&mut *pg_connection)
|
||||
.fetch_one(&mut *connection)
|
||||
.await?;
|
||||
|
||||
if publication.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut transaction_to_track = Vec::with_capacity(3);
|
||||
|
||||
let publication = publication.unwrap();
|
||||
if publication.insert {
|
||||
if transaction.insert {
|
||||
transaction_to_track.push("insert".to_string());
|
||||
}
|
||||
if publication.update {
|
||||
if transaction.update {
|
||||
transaction_to_track.push("update".to_string());
|
||||
}
|
||||
if publication.delete {
|
||||
if transaction.delete {
|
||||
transaction_to_track.push("delete".to_string());
|
||||
}
|
||||
|
||||
Ok(Some((publication.all_table, transaction_to_track)))
|
||||
Ok((transaction.all_table, transaction_to_track))
|
||||
}
|
||||
|
||||
async fn get_tracked_relations(
|
||||
pg_connection: &mut PgConnection,
|
||||
connection: &mut PgConnection,
|
||||
publication_name: &str,
|
||||
) -> Result<Vec<Relations>> {
|
||||
#[derive(Debug, Deserialize, FromRow)]
|
||||
struct PublicationData {
|
||||
schema_name: Option<String>,
|
||||
table_name: Option<String>,
|
||||
#[serde(default)]
|
||||
columns: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
where_clause: Option<String>,
|
||||
}
|
||||
|
||||
let pg_version = get_postgres_version_internal(pg_connection).await?;
|
||||
let query = if pg_version.starts_with("14") {
|
||||
let publications = sqlx::query_as!(
|
||||
PublicationData,
|
||||
r#"
|
||||
SELECT
|
||||
schemaname AS schema_name,
|
||||
tablename AS table_name,
|
||||
NULL::text[] AS columns,
|
||||
NULL::text AS where_clause
|
||||
FROM
|
||||
pg_publication_tables
|
||||
WHERE
|
||||
pubname = $1;
|
||||
"#
|
||||
} else {
|
||||
r#"
|
||||
SELECT
|
||||
schemaname AS schema_name,
|
||||
tablename AS table_name,
|
||||
attnames AS columns,
|
||||
CASE
|
||||
WHEN array_length(attnames, 1) = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = pg_publication_tables.schemaname AND table_name = pg_publication_tables.tablename)
|
||||
THEN NULL
|
||||
ELSE attnames
|
||||
END AS columns,
|
||||
rowfilter AS where_clause
|
||||
FROM
|
||||
pg_publication_tables
|
||||
WHERE
|
||||
pubname = $1;
|
||||
"#
|
||||
};
|
||||
|
||||
let publications: Vec<PublicationData> = sqlx::query_as(query)
|
||||
.bind(publication_name)
|
||||
.fetch_all(&mut *pg_connection)
|
||||
.await?;
|
||||
"#,
|
||||
publication_name
|
||||
)
|
||||
.fetch_all(&mut *connection)
|
||||
.await?;
|
||||
|
||||
let mut table_to_track: HashMap<String, Relations> = HashMap::new();
|
||||
|
||||
for publication in publications {
|
||||
let schema_name = publication.schema_name.ok_or_else(|| Error::Anyhow {
|
||||
error: anyhow::anyhow!(
|
||||
"Unexpected NULL `schema_name` in publication entry (pubname: `{}`). This should never happen unless PostgreSQL internals are corrupted.",
|
||||
publication_name,
|
||||
),
|
||||
location: "postgres_triggers/handler.rs@1093".to_string(),
|
||||
})?;
|
||||
|
||||
let table_name = publication.table_name.ok_or_else(|| Error::Anyhow {
|
||||
error: anyhow::anyhow!(
|
||||
"Unexpected NULL `table_name` for schema `{}` in publication `{}`. This should never happen unless PostgreSQL internals are corrupted.",
|
||||
schema_name,
|
||||
publication_name,
|
||||
),
|
||||
location: "postgres_triggers/handler.rs@1102".to_string(),
|
||||
})?;
|
||||
|
||||
let schema_name = publication.schema_name.unwrap();
|
||||
let entry = table_to_track.entry(schema_name.clone());
|
||||
let table_to_track =
|
||||
TableToTrack::new(table_name, publication.where_clause, publication.columns);
|
||||
let table_to_track = TableToTrack::new(
|
||||
publication.table_name.unwrap(),
|
||||
publication.where_clause,
|
||||
publication.columns.unwrap_or_default(),
|
||||
);
|
||||
match entry {
|
||||
Occupied(mut occuped) => {
|
||||
occuped.get_mut().add_new_table(table_to_track);
|
||||
@@ -1161,7 +1111,7 @@ pub async fn update_postgres_trigger(
|
||||
publication,
|
||||
} = postgres_trigger;
|
||||
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -1170,33 +1120,18 @@ pub async fn update_postgres_trigger(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let exists =
|
||||
check_if_logical_replication_slot_exist(&mut pg_connection, &replication_slot_name).await?;
|
||||
|
||||
let mut tx = pg_connection.begin().await?;
|
||||
|
||||
if !exists {
|
||||
tracing::debug!(
|
||||
"Logical replication slot named: {} does not exists creating it...",
|
||||
&replication_slot_name
|
||||
);
|
||||
create_logical_replication_slot(&mut tx, &replication_slot_name).await?;
|
||||
}
|
||||
check_if_logical_replication_slot_exist(&mut connection, &replication_slot_name).await?;
|
||||
|
||||
if let Some(publication) = publication {
|
||||
let publication_data =
|
||||
get_publication_scope_and_transaction(&mut tx, &publication_name).await?;
|
||||
check_if_publication_exist(&mut connection, &publication_name).await?;
|
||||
let (all_table, _) =
|
||||
get_publication_scope_and_transaction(&mut connection, &publication_name).await?;
|
||||
|
||||
update_pg_publication(
|
||||
&mut tx,
|
||||
&publication_name,
|
||||
publication,
|
||||
publication_data.map(|publication| publication.0),
|
||||
)
|
||||
.await?;
|
||||
let queries = get_update_publication_query(&publication_name, publication, all_table);
|
||||
for query in queries {
|
||||
sqlx::query(&query).execute(&mut connection).await?;
|
||||
}
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -1421,7 +1356,7 @@ pub async fn create_template_script(
|
||||
));
|
||||
}
|
||||
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -1449,12 +1384,11 @@ pub async fn create_template_script(
|
||||
format!("{}.{}", &relation.schema_name, table_to_track.table_name);
|
||||
schema_or_fully_qualified_name.push(quote_literal(&fully_qualified_name));
|
||||
|
||||
let columns = table_to_track
|
||||
.columns_name
|
||||
.map(|columns| quote_literal(&columns.join(",")))
|
||||
.or_else(|| Some("''".to_string()))
|
||||
.unwrap();
|
||||
|
||||
let columns = if !table_to_track.columns_name.is_empty() {
|
||||
quote_literal(&table_to_track.columns_name.join(","))
|
||||
} else {
|
||||
"''".to_string()
|
||||
};
|
||||
columns_list.push(columns);
|
||||
}
|
||||
continue;
|
||||
@@ -1514,7 +1448,11 @@ pub async fn create_template_script(
|
||||
tables_name, columns_list
|
||||
);
|
||||
|
||||
let rows: Vec<ColumnInfo> = sqlx::query_as(&query).fetch_all(&mut pg_connection).await?;
|
||||
let rows: Vec<ColumnInfo> = sqlx::query_as(&query)
|
||||
.fetch_all(&mut connection)
|
||||
.await
|
||||
.map_err(|e| error::Error::SqlErr { error: e, location: "pg_trigger".to_string() })?;
|
||||
|
||||
let mut mapper: HashMap<String, HashMap<String, Vec<MappingInfo>>> = HashMap::new();
|
||||
|
||||
for row in rows {
|
||||
@@ -1571,7 +1509,7 @@ pub async fn is_database_in_logical_level(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, postgres_resource_path)): Path<(String, String)>,
|
||||
) -> error::JsonResult<bool> {
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -1580,8 +1518,8 @@ pub async fn is_database_in_logical_level(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let wal_level: Option<String> = sqlx::query_scalar("SHOW WAL_LEVEL;")
|
||||
.fetch_optional(&mut pg_connection)
|
||||
let wal_level = sqlx::query_scalar!("SHOW WAL_LEVEL;")
|
||||
.fetch_optional(&mut connection)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
|
||||
@@ -25,17 +25,12 @@ pub use handler::PostgresTrigger;
|
||||
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_postgres_version,
|
||||
get_postgres_version_internal, get_publication_info, get_template_script,
|
||||
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, test_postgres_connection, update_postgres_trigger, Postgres,
|
||||
Relations,
|
||||
};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, Result},
|
||||
utils::StripPath,
|
||||
};
|
||||
use windmill_common::{db::UserDB, error::Error, utils::StripPath};
|
||||
mod bool;
|
||||
mod converter;
|
||||
mod handler;
|
||||
@@ -52,13 +47,13 @@ const ERROR_REPLICATION_SLOT_NOT_EXISTS: &str = r#"The replication slot associat
|
||||
|
||||
const ERROR_PUBLICATION_NAME_NOT_EXISTS: &str = r#"The publication associated with this trigger no longer exists. Recreate a new publication or select an existing one in the advanced tab, or delete and recreate a new trigger"#;
|
||||
|
||||
pub async fn get_pg_connection(
|
||||
pub async fn get_database_connection(
|
||||
authed: ApiAuthed,
|
||||
user_db: Option<UserDB>,
|
||||
db: &DB,
|
||||
postgres_resource_path: &str,
|
||||
w_id: &str,
|
||||
) -> Result<PgConnection> {
|
||||
) -> std::result::Result<PgConnection, windmill_common::error::Error> {
|
||||
let database =
|
||||
try_get_resource_from_db_as::<Postgres>(authed, user_db, db, postgres_resource_path, w_id)
|
||||
.await?;
|
||||
@@ -66,7 +61,9 @@ pub async fn get_pg_connection(
|
||||
Ok(get_raw_postgres_connection(&database).await?)
|
||||
}
|
||||
|
||||
pub async fn get_raw_postgres_connection(db: &Postgres) -> Result<PgConnection> {
|
||||
pub async fn get_raw_postgres_connection(
|
||||
db: &Postgres,
|
||||
) -> std::result::Result<PgConnection, Error> {
|
||||
let options = {
|
||||
let sslmode = if !db.sslmode.is_empty() {
|
||||
PgSslMode::from_str(&db.sslmode)?
|
||||
@@ -102,10 +99,7 @@ pub async fn get_raw_postgres_connection(db: &Postgres) -> Result<PgConnection>
|
||||
Ok(PgConnection::connect_with(&options).await?)
|
||||
}
|
||||
|
||||
pub async fn create_logical_replication_slot(
|
||||
pg_connection: &mut PgConnection,
|
||||
name: &str,
|
||||
) -> Result<()> {
|
||||
pub fn create_logical_replication_slot_query(name: &str) -> String {
|
||||
let query = format!(
|
||||
r#"
|
||||
SELECT
|
||||
@@ -115,53 +109,14 @@ pub async fn create_logical_replication_slot(
|
||||
quote_literal(&name)
|
||||
);
|
||||
|
||||
sqlx::query(&query).execute(pg_connection).await?;
|
||||
|
||||
Ok(())
|
||||
query
|
||||
}
|
||||
|
||||
async fn check_if_valid_publication_for_postgres_version(
|
||||
pg_connection: &mut PgConnection,
|
||||
table_to_track: Option<&[Relations]>,
|
||||
) -> Result<bool> {
|
||||
let postgres_version = get_postgres_version_internal(pg_connection).await?;
|
||||
|
||||
let pg_14 = postgres_version.starts_with("14");
|
||||
if pg_14 {
|
||||
let unsupported_publication = table_to_track
|
||||
.and_then(|relations| {
|
||||
relations.iter().find(|relation| {
|
||||
let invalid_relation = relation.table_to_track.iter().find(|table_to_track| {
|
||||
table_to_track.where_clause.is_some()
|
||||
|| table_to_track.columns_name.is_some()
|
||||
});
|
||||
|
||||
relation.table_to_track.is_empty() || invalid_relation.is_some()
|
||||
})
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if unsupported_publication {
|
||||
return Err(Error::BadRequest(
|
||||
"Your PostgreSQL database is running version 14, which does not support the following publication features: \
|
||||
- WHERE clause filtering, \
|
||||
- selective column tracking, and \
|
||||
- tracking all tables within a schema.\n\
|
||||
These features are only available in PostgreSQL 15 and above.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(pg_14)
|
||||
}
|
||||
|
||||
pub async fn create_pg_publication(
|
||||
pg_connection: &mut PgConnection,
|
||||
pub fn create_publication_query(
|
||||
publication_name: &str,
|
||||
table_to_track: Option<&[Relations]>,
|
||||
transaction_to_track: &[String],
|
||||
) -> Result<()> {
|
||||
let pg_14 =
|
||||
check_if_valid_publication_for_postgres_version(pg_connection, table_to_track).await?;
|
||||
transaction_to_track: &[&str],
|
||||
) -> String {
|
||||
let mut query = String::from("CREATE PUBLICATION ");
|
||||
|
||||
query.push_str("e_identifier(publication_name));
|
||||
@@ -169,26 +124,21 @@ pub async fn create_pg_publication(
|
||||
match table_to_track {
|
||||
Some(database_component) if !database_component.is_empty() => {
|
||||
query.push_str(" FOR");
|
||||
let mut first = true;
|
||||
for (i, schema) in database_component.iter().enumerate() {
|
||||
if schema.table_to_track.is_empty() {
|
||||
query.push_str(" TABLES IN SCHEMA ");
|
||||
query.push_str("e_identifier(&schema.schema_name));
|
||||
} else {
|
||||
if pg_14 && first {
|
||||
query.push_str(" TABLE ONLY ");
|
||||
first = false;
|
||||
} else if !pg_14 {
|
||||
query.push_str(" TABLE ONLY ");
|
||||
}
|
||||
query.push_str(" TABLE ONLY ");
|
||||
for (j, table) in schema.table_to_track.iter().enumerate() {
|
||||
let table_name = quote_identifier(&table.table_name);
|
||||
let schema_name = quote_identifier(&schema.schema_name);
|
||||
let full_name = format!("{}.{}", &schema_name, &table_name);
|
||||
query.push_str(&full_name);
|
||||
if let Some(columns) = table.columns_name.as_ref() {
|
||||
if !table.columns_name.is_empty() {
|
||||
query.push_str(" (");
|
||||
let columns = columns
|
||||
let columns = table
|
||||
.columns_name
|
||||
.iter()
|
||||
.map(|column| quote_identifier(column))
|
||||
.join(", ");
|
||||
@@ -224,22 +174,22 @@ pub async fn create_pg_publication(
|
||||
query.push_str("');");
|
||||
}
|
||||
|
||||
sqlx::query(&query).execute(pg_connection).await?;
|
||||
|
||||
Ok(())
|
||||
query
|
||||
}
|
||||
|
||||
pub async fn drop_publication(
|
||||
pg_connection: &mut PgConnection,
|
||||
publication_name: &str,
|
||||
) -> Result<()> {
|
||||
pub fn drop_publication_query(publication_name: &str) -> String {
|
||||
let mut query = String::from("DROP PUBLICATION IF EXISTS ");
|
||||
let quoted_publication_name = quote_identifier(publication_name);
|
||||
query.push_str("ed_publication_name);
|
||||
query.push_str(";");
|
||||
query
|
||||
}
|
||||
|
||||
sqlx::query(&query).execute(pg_connection).await?;
|
||||
|
||||
Ok(())
|
||||
pub fn drop_logical_replication_slot_query(replication_slot_name: &str) -> String {
|
||||
format!(
|
||||
"SELECT pg_drop_replication_slot({});",
|
||||
quote_literal(&replication_slot_name)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_random_string() -> String {
|
||||
@@ -278,10 +228,6 @@ fn slot_service() -> Router {
|
||||
.route("/delete/*path", delete(drop_slot_name))
|
||||
}
|
||||
|
||||
fn postgres_service() -> Router {
|
||||
Router::new().route("/version/*path", get(get_postgres_version))
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/test", post(test_postgres_connection))
|
||||
@@ -300,7 +246,6 @@ pub fn workspaced_service() -> Router {
|
||||
)
|
||||
.nest("/publication", publication_service())
|
||||
.nest("/slot", slot_service())
|
||||
.nest("/postgres", postgres_service())
|
||||
}
|
||||
|
||||
async fn run_job(
|
||||
|
||||
@@ -34,8 +34,8 @@ use windmill_common::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
drop_publication, get_pg_connection,
|
||||
handler::{drop_logical_replication_slot, Postgres, PostgresTrigger},
|
||||
drop_logical_replication_slot_query, drop_publication_query, get_database_connection,
|
||||
handler::{Postgres, PostgresTrigger},
|
||||
replication_message::PrimaryKeepAliveBody,
|
||||
ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS,
|
||||
};
|
||||
@@ -585,7 +585,7 @@ impl PostgresConfig {
|
||||
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
let mut pg_connection = get_pg_connection(
|
||||
let mut connection = get_database_connection(
|
||||
authed.clone(),
|
||||
Some(user_db.clone()),
|
||||
&db,
|
||||
@@ -594,12 +594,13 @@ impl PostgresConfig {
|
||||
)
|
||||
.await?;
|
||||
|
||||
if capture.trigger_config.basic_mode.unwrap_or(false) {
|
||||
drop_logical_replication_slot(&mut pg_connection, replication_slot_name)
|
||||
.await?;
|
||||
let query = drop_logical_replication_slot_query(replication_slot_name);
|
||||
|
||||
drop_publication(&mut pg_connection, publication_name).await?;
|
||||
}
|
||||
let _ = sqlx::query(&query).execute(&mut connection).await;
|
||||
|
||||
let query = drop_publication_query(publication_name);
|
||||
|
||||
let _ = sqlx::query(&query).execute(&mut connection).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::FromRow;
|
||||
use std::str;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
apps::ListAppQuery,
|
||||
|
||||
@@ -26,7 +26,7 @@ use serde_json::{value::RawValue, Value};
|
||||
use sql_builder::{bind::Bind, quote, SqlBuilder};
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::audit_oss::{audit_log, AuditAuthor};
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
|
||||
@@ -7,27 +7,18 @@
|
||||
*/
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::saml_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::{routing::post, Router};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct ServiceProviderExt();
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn build_sp_extension() -> anyhow::Result<ServiceProviderExt> {
|
||||
return Ok(ServiceProviderExt());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new().route("/acs", post(acs))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn acs() -> String {
|
||||
// Implementation is not open source as it is a Windmill Enterprise Edition feature
|
||||
"SAML available only in enterprise version".to_string()
|
||||
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sql_builder::{prelude::Bind, SqlBuilder};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use std::str::FromStr;
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::scim_ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2023
|
||||
@@ -10,22 +6,17 @@ pub use crate::scim_ee::*;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::{middleware::Next, response::Response, routing::get, Router};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use hyper::Request;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
Router::new().route("/ee", get(ee))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn ee() -> String {
|
||||
return "Enterprise Edition".to_string();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn has_scim_token<B>(_request: Request<B>, _next: Next) -> Response {
|
||||
//Not implemented in open-source version
|
||||
todo!()
|
||||
@@ -38,7 +38,7 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_worker::process_relative_imports;
|
||||
|
||||
@@ -410,7 +410,10 @@ async fn create_snapshot_script(
|
||||
uploaded = true;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
let object_store = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone();
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
@@ -1324,12 +1327,10 @@ async fn raw_script_by_path_internal(
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if exists {
|
||||
.await?;
|
||||
if exists.unwrap_or(false) {
|
||||
return Err(Error::NotFound(format!(
|
||||
"Script {path} exists but {} does not have permissions to access it",
|
||||
"Script {path} not visible to {} but exists",
|
||||
authed.username
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -98,7 +98,10 @@ async fn get_log_file(
|
||||
require_devops_role(&db, &email).await?;
|
||||
let path = path.to_path();
|
||||
#[cfg(feature = "parquet")]
|
||||
let s3_client = windmill_common::s3_helpers::get_object_store().await;
|
||||
let s3_client = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone();
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = format!("{}{}", windmill_common::tracing_init::LOGS_SERVICE, path);
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
ee_oss::validate_license_key,
|
||||
ee::validate_license_key,
|
||||
utils::{generate_instance_username_for_all_users, require_super_admin},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
@@ -29,9 +29,9 @@ use crate::utils::require_devops_role;
|
||||
|
||||
use serde::Deserialize;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel};
|
||||
use windmill_common::ee::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel};
|
||||
use windmill_common::{
|
||||
email_oss::send_email,
|
||||
email_ee::send_email,
|
||||
error::{self, JsonResult, Result},
|
||||
global_settings::{
|
||||
AUTOMATE_USERNAME_CREATION_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, EMAIL_DOMAIN_SETTING,
|
||||
@@ -120,15 +120,12 @@ use windmill_common::s3_helpers::build_object_store_from_settings;
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn test_s3_bucket(
|
||||
_authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(test_s3_bucket): Json<ObjectSettings>,
|
||||
) -> error::Result<String> {
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
|
||||
let client = build_object_store_from_settings(test_s3_bucket, Some(&db))
|
||||
.await?
|
||||
.store;
|
||||
let client = build_object_store_from_settings(test_s3_bucket).await?;
|
||||
|
||||
let mut list = client.list(Some(&object_store::path::Path::from("".to_string())));
|
||||
let first_file = list.next().await;
|
||||
@@ -326,10 +323,10 @@ async fn list_global_settings() -> JsonResult<String> {
|
||||
|
||||
pub async fn send_stats(Extension(db): Extension<DB>, authed: ApiAuthed) -> Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
windmill_common::stats_oss::send_stats(
|
||||
windmill_common::stats_ee::send_stats(
|
||||
&HTTP_CLIENT,
|
||||
&db,
|
||||
windmill_common::stats_oss::SendStatsReason::Manual,
|
||||
windmill_common::stats_ee::SendStatsReason::Manual,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -390,11 +387,11 @@ pub async fn renew_license_key(
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let result = windmill_common::ee_oss::renew_license_key(
|
||||
let result = windmill_common::ee::renew_license_key(
|
||||
&HTTP_CLIENT,
|
||||
&db,
|
||||
license_key,
|
||||
windmill_common::ee_oss::RenewReason::Manual,
|
||||
windmill_common::ee::RenewReason::Manual,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -424,7 +421,7 @@ pub async fn create_customer_portal_session(
|
||||
Query(LicenseQuery { license_key }): Query<LicenseQuery>,
|
||||
) -> Result<String> {
|
||||
let url =
|
||||
windmill_common::ee_oss::create_customer_portal_session(&HTTP_CLIENT, license_key).await?;
|
||||
windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, license_key).await?;
|
||||
|
||||
return Ok(url);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::smtp_server_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::{auth::AuthCache, db::DB};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::db::UserDB;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct SmtpServer {
|
||||
pub auth_cache: Arc<AuthCache>,
|
||||
pub db: DB,
|
||||
@@ -17,7 +9,6 @@ pub struct SmtpServer {
|
||||
pub base_internal_url: String,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl SmtpServer {
|
||||
pub async fn start_listener_thread(self: Arc<Self>, _addr: SocketAddr) -> anyhow::Result<()> {
|
||||
let _ = self.auth_cache;
|
||||
@@ -1,28 +1,18 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::sqs_triggers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::auth::aws::AwsAuthResourceType;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn start_sqs(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct SqsTrigger {
|
||||
pub queue_url: String,
|
||||
pub aws_auth_resource_type: AwsAuthResourceType,
|
||||
@@ -40,4 +30,4 @@ pub struct SqsTrigger {
|
||||
pub server_id: Option<String>,
|
||||
pub last_server_ping: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user