diff --git a/.aiderignore b/.aiderignore new file mode 100644 index 0000000000..0dca6eac77 --- /dev/null +++ b/.aiderignore @@ -0,0 +1,3 @@ +/* +!/backend/ +!/frontend/ \ No newline at end of file diff --git a/.env b/.env index da41f78d5c..ad887513cd 100644 --- a/.env +++ b/.env @@ -10,4 +10,4 @@ WM_IMAGE=ghcr.io/windmill-labs/windmill:main # To rotate logs, set the following variables: #LOG_MAX_SIZE=10m -#LOG_MAX_FILE=3 +#LOG_MAX_FILE=3 \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aba00bfed5..282cba946a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -* @rubenfiszel +* @rubenfiszel @HugoCasa @alpetric -/community/ @fatonramadani @rubenfiszel -/frontend/ @fatonramadani @rubenfiszel +/community/ @rubenfiszel @HugoCasa @alpetric +/frontend/ @rubenfiszel @HugoCasa @alpetric diff --git a/.github/DockerfileBackendTests b/.github/DockerfileBackendTests index 0eb2149feb..7fda0e025f 100644 --- a/.github/DockerfileBackendTests +++ b/.github/DockerfileBackendTests @@ -27,31 +27,38 @@ RUN wget https://golang.org/dl/go1.21.5.linux-amd64.tar.gz && tar -C /usr/local ENV PATH="${PATH}:/usr/local/go/bin" ENV GO_PATH=/usr/local/go/bin/go -# Install UV +# UV RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /usr/local/cargo/bin/uv /usr/local/bin/uv ENV TZ=Etc/UTC ENV PYTHON_VERSION 3.11.4 +# Python RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz \ && tar -xf Python-${PYTHON_VERSION}.tgz && cd Python-${PYTHON_VERSION}/ && ./configure --enable-optimizations \ && make -j 4 && make install RUN /usr/local/bin/python3 -m pip install pip-tools -COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun +# Bun +COPY --from=oven/bun:1.2.4 /usr/local/bin/bun /usr/bin/bun ARG TARGETPLATFORM +# Deno RUN curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.2/deno-x86_64-unknown-linux-gnu.zip -o deno.zip # RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.0/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true - RUN unzip deno.zip && rm deno.zip && mv deno /usr/bin/deno RUN apt-get update \ && apt-get install -y postgresql-client --allow-unauthenticated RUN rustup component add rustfmt + +# C# COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet + +# Nushell +COPY --from=ghcr.io/nushell/nushell:0.101.0-bookworm /usr/bin/nu /usr/bin/nu diff --git a/.github/change-versions.sh b/.github/change-versions.sh index 699b87b0d7..454f373038 100755 --- a/.github/change-versions.sh +++ b/.github/change-versions.sh @@ -24,4 +24,4 @@ sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock -cd ${root_dirpath}/frontend && npm i --package-lock-only +cd ${root_dirpath}/frontend && npm i --package-lock-only --ignore-scripts diff --git a/.github/workflows/aider-after-review.yaml.archived b/.github/workflows/aider-after-review.yaml.archived new file mode 100644 index 0000000000..2db321a7a8 --- /dev/null +++ b/.github/workflows/aider-after-review.yaml.archived @@ -0,0 +1,94 @@ +name: Aider Auto-fix PR Review Change Requests + +on: + pull_request_review: + types: [submitted] + +jobs: + check-membership: + if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]') + runs-on: ubicloud-standard-2 + outputs: + is_member: ${{ steps.check-membership.outputs.is_member }} + steps: + - name: Check organization membership + id: check-membership + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + REVIEWER: ${{ github.event.review.user.login }} + ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }} + run: | + ORG="windmill-labs" + STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $ORG_ACCESS_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/$ORG/members/$REVIEWER") + + if [ "$STATUS" -eq 204 ]; then + echo "is_member=true" >> $GITHUB_OUTPUT + else + echo "is_member=false" >> $GITHUB_OUTPUT + fi + + check-and-prepare: + needs: check-membership + if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]') && needs.check-membership.outputs.is_member == 'true' + runs-on: ubicloud-standard-2 + permissions: + contents: write + pull-requests: write + outputs: + prompt_content: ${{ steps.prepare_prompt.outputs.prompt_content }} + env: + GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }} + + steps: + - name: Acknowledge Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + echo "Commenting on PR #${{ github.event.pull_request.number }} to acknowledge the /aider command." + gh pr comment ${{ github.event.pull_request.number }} --body "🤖 Aider is starting to work on your request. Please be patient, this might take a few minutes." --repo $GITHUB_REPOSITORY + + - name: Prepare prompt for Aider + id: prepare_prompt + shell: bash + env: + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REVIEW_BODY: ${{ github.event.review.body }} + run: | + REVIEW_BODY_ESCAPED="${REVIEW_BODY//\\/\\\\}" + REVIEW_BODY_ESCAPED="${REVIEW_BODY_ESCAPED//\"/\\\"}" + + ALL_REVIEW_COMMENTS=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments) + + FORMATTED_COMMENTS=$(jq -r '[.[] | {diff_hunk: .diff_hunk, path: .path, body: .body}]' <<< "$ALL_REVIEW_COMMENTS") + + BASE_PROMPT="Fix the following issues in the PR based on the review feedback. The review body is prepended with REVIEW. The review comments are prepended with REVIEW_COMMENTS. The review body and comments are separated by a blank line." + + COMPLETE_PROMPT="${BASE_PROMPT}"$'\n'"REVIEW:"$'\n'"${REVIEW_BODY_ESCAPED}"$'\n'"REVIEW_COMMENTS:"$'\n'"${FORMATTED_COMMENTS}" + + echo "prompt_content<> $GITHUB_OUTPUT + echo "$COMPLETE_PROMPT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + run-aider: + needs: [check-membership, check-and-prepare] + if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]') && needs.check-membership.outputs.is_member == 'true' + uses: ./.github/workflows/aider-common.yml + 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 diff --git a/.github/workflows/aider-common.yml.archived b/.github/workflows/aider-common.yml.archived new file mode 100644 index 0000000000..935c4224f2 --- /dev/null +++ b/.github/workflows/aider-common.yml.archived @@ -0,0 +1,522 @@ +name: Aider Common Steps + +on: + workflow_call: + inputs: + issue_title: + description: "Title of the issue or PR" + required: false + type: string + issue_body: + description: "Body of the issue or PR" + required: false + type: string + instruction: + description: "Instruction for Aider" + required: false + type: string + issue_id: + description: "ID of the issue or PR" + required: false + type: string + needs_processing: + description: "Whether the issue needs to be processed by the external API" + required: false + type: boolean + default: true + base_prompt: + description: "Base prompt for Aider" + required: false + type: string + default: "Try to fix the following issue based on the instruction given by the user. The issue is prepended with the word ISSUE. The instruction is prepended with the word INSTRUCTION. The issue and instruction are separated by a blank line." + probe_prompt: + 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 + outputs: + files_to_edit: + description: "Files identified by probe-chat for editing" + value: ${{ jobs.common-steps.outputs.files_to_edit }} + final_prompt: + description: "Final prompt for Aider" + value: ${{ jobs.common-steps.outputs.final_prompt }} + pr_branch_name: + description: "Name of the branch used for PR" + value: ${{ jobs.common-steps.outputs.pr_branch_name }} + changes_applied_message: + description: "Message indicating changes were applied" + value: ${{ jobs.common-steps.outputs.changes_applied_message }} + changes_applied: + description: "Boolean indicating if changes were successfully applied" + value: ${{ jobs.common-steps.outputs.changes_applied }} + +jobs: + common-steps: + runs-on: ubicloud-standard-8 + outputs: + files_to_edit: ${{ steps.probe_files.outputs.files_to_edit }} + final_prompt: ${{ steps.create_prompt.outputs.final_prompt }} + pr_branch_name: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }} + changes_applied_message: ${{ steps.commit_and_push.outputs.CHANGES_APPLIED_MESSAGE }} + changes_applied: ${{ steps.commit_and_push.outputs.CHANGES_APPLIED }} + env: + GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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 + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout PR Branch + id: checkout_pr + if: (github.event_name == 'issue_comment' && github.event.issue.pull_request) || (github.event_name == 'pull_request_review') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Issue comment trigger: Checking out PR branch..." + PR_NUMBER="" + if [ -n "${{ github.event.issue.number }}" ]; then + PR_NUMBER="${{ github.event.issue.number }}" + elif [ -n "${{ github.event.pull_request.number }}" ]; then + PR_NUMBER="${{ github.event.pull_request.number }}" + else + echo "::error::Could not determine PR number." + exit 1 + fi + PR_HEAD_REF=$(gh pr view $PR_NUMBER --json headRefName -q .headRefName --repo $GITHUB_REPOSITORY) + if [[ -z "$PR_HEAD_REF" || "$PR_HEAD_REF" == "null" ]]; then + echo "::error::Could not determine PR head branch for PR #$PR_NUMBER via gh CLI." + exit 1 + fi + echo "Checking out PR head branch: $PR_HEAD_REF for PR #$PR_NUMBER" + git fetch origin "refs/heads/${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF}" --no-tags + git checkout "$PR_HEAD_REF" + echo "Successfully checked out branch $(git rev-parse --abbrev-ref HEAD)" + echo "PR_BRANCH=$PR_HEAD_REF" >> $GITHUB_OUTPUT + + - name: Configure Git User + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Cache Python dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - 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 + 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 + shell: bash + env: + BASE_PROMPT_ENV: ${{ inputs.base_prompt }} + ISSUE_TITLE_ENV: ${{ inputs.issue_title }} + ISSUE_BODY_ENV: ${{ inputs.issue_body }} + INSTRUCTION_ENV: ${{ inputs.instruction }} + NEEDS_PROCESSING_ENV: ${{ inputs.needs_processing }} + WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }} + run: | + set -e + FINAL_PROMPT_CONTENT="" + + if [[ "$ISSUE_TITLE_ENV" != "" && "$ISSUE_BODY_ENV" != "" ]]; then + echo "Processing issue with title: $ISSUE_TITLE_ENV" + if [[ "$NEEDS_PROCESSING_ENV" == "true" ]]; then + echo "Needs processing is true. Calling Windmill API..." + JSON_PAYLOAD=$(jq -n \ + --arg title "$ISSUE_TITLE_ENV" \ + --arg body "$ISSUE_BODY_ENV" \ + '{"body":{"issue_title":$title,"issue_body":$body}}') + + echo "Windmill JSON Payload: $JSON_PAYLOAD" + + API_RESULT_FILE=$(mktemp) + HTTP_CODE=$(curl -s -o "$API_RESULT_FILE" -w "%{http_code}" \ + -X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/ai/quiet_script" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $WINDMILL_TOKEN" \ + --data-binary "$JSON_PAYLOAD" \ + --max-time 90) + + BODY_CONTENT=$(cat "$API_RESULT_FILE") + rm -f "$API_RESULT_FILE" # Clean up temp file + + echo "Windmill API HTTP Code: $HTTP_CODE" + if [[ "$HTTP_CODE" -eq 200 ]]; then + PROCESSED_ISSUE_PROMPT=$(echo "$BODY_CONTENT" | jq -r '.effective_body // empty') + if [[ -z "$PROCESSED_ISSUE_PROMPT" || "$PROCESSED_ISSUE_PROMPT" == "null" ]]; then + echo "::warning::Windmill API returned 200 but effective_body was empty or null." + EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT="$ISSUE_BODY_ENV" + else + echo "Successfully processed issue via Windmill API." + EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT="$PROCESSED_ISSUE_PROMPT" + fi + FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \ + "$BASE_PROMPT_ENV" "$EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT" "$INSTRUCTION_ENV") + else + echo "::error::Windmill API call failed (HTTP $HTTP_CODE). Using raw issue content for prompt." + FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \ + "$BASE_PROMPT_ENV" "$ISSUE_BODY_ENV" "$INSTRUCTION_ENV") + fi + else + echo "Needs processing is false. Using raw issue content for prompt." + FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \ + "$BASE_PROMPT_ENV" "$ISSUE_BODY_ENV" "$INSTRUCTION_ENV") + 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") + fi + + echo "Final prompt: $FINAL_PROMPT_CONTENT" + echo "final_prompt<> "$GITHUB_OUTPUT" + echo "$FINAL_PROMPT_CONTENT" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_PROMPT" >> "$GITHUB_OUTPUT" + + - name: Probe Chat for Relevant Files + id: probe_files + 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") + + 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") || { + echo "::error::probe-chat command failed. Output:" + echo "$PROBE_OUTPUT" + exit 1 + } + set +o pipefail + echo "Probe-chat raw output:" + echo "$PROBE_OUTPUT" + + JSON_FILES=$(echo "$PROBE_OUTPUT" | sed -n '/^\s*\[/,$p' | sed '/^\s*\]/q') + echo "Extracted JSON block:" + echo "$JSON_FILES" + + FILES_LIST=$(echo "$JSON_FILES" | jq -e -r '[.[] | select(type == "string" and . != "" and . != null and (endswith("/") | not))] | join(" ")' || echo "") + + if [[ -z "$FILES_LIST" ]]; then + echo "::warning::probe-chat did not identify any relevant files." + fi + + echo "Formatted files list for aider: $FILES_LIST" + echo "files_to_edit=$FILES_LIST" >> $GITHUB_OUTPUT + + - name: Cache Aider tags + uses: actions/cache@v3 + with: + path: .aider.tags.cache.v4 + key: ${{ runner.os }}-aider-${{ github.sha }} + 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 \ + $FILES_TO_EDIT \ + --model gemini/gemini-2.5-pro-preview-05-06 \ + --message-file .aider_final_prompt.txt \ + --yes \ + --no-check-update \ + --auto-commits \ + --no-analytics \ + --no-gitignore \ + | tee .aider_output.txt || true + + echo "Aider command completed. Output saved to .aider_output.txt" + + - name: Cache Node.js dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Commit and Push Changes + 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" + 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 + else + # We're in a pull_request_review event + PR_HEAD_REF="${{ github.event.pull_request.head.ref }}" + echo "Attempting to push changes to PR branch $PR_HEAD_REF" + 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 + echo "PR_BRANCH_NAME=$PR_HEAD_REF" >> $GITHUB_OUTPUT + echo "CHANGES_APPLIED=true" >> $GITHUB_OUTPUT + else + echo "::warning::Push to PR branch $PR_HEAD_REF failed." + echo "CHANGES_APPLIED_MESSAGE=Aider ran, but failed to push changes to PR branch $PR_HEAD_REF." >> $GITHUB_OUTPUT + echo "CHANGES_APPLIED=false" >> $GITHUB_OUTPUT + fi + fi + + - name: Create Pull Request + if: always() && (github.event_name == 'issue_comment' || github.event_name == 'repository_dispatch') && !github.event.issue.pull_request && steps.commit_and_push.outputs.PR_BRANCH_NAME != '' + id: create_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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 + fi + cat > /tmp/pr-description.md << EOL | head -c 40000 + $HEADER + + ## Aider Output + \`\`\` + $(cat .aider_output.txt || echo "No output available") + \`\`\` + EOL + + # 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" \ + --body-file /tmp/pr-description.md \ + --head "$PR_BRANCH" \ + --base main \ + --draft + PR_CREATE_EXIT_CODE=$? + set -e # Re-enable exit on error + + if [ $PR_CREATE_EXIT_CODE -eq 0 ]; then + echo "PR created successfully" + PR_URL=$(gh pr view $PR_BRANCH --json url --jq .url) + echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT + echo "PR_CREATED=true" >> $GITHUB_OUTPUT + else + echo "Warning: Failed to create PR. Exit code: $PR_CREATE_EXIT_CODE" + echo "PR_CREATED=false" >> $GITHUB_OUTPUT + # Continue workflow despite PR creation failure + fi + + - name: Comment on PR with Aider Output + if: always() && github.event_name == 'pull_request_review' && steps.commit_and_push.outputs.CHANGES_APPLIED != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUM: ${{ github.event.pull_request.number }} + JOB_STATUS: ${{ job.status }} + run: | + # Create comment body in a temporary file to avoid command line length limits + if [[ "${{ steps.commit_and_push.outputs.CHANGES_APPLIED }}" == "true" ]]; then + if [[ "$JOB_STATUS" == "success" ]]; then + STATUS_PREFIX="🤖 I've automatically addressed the feedback based on the review." + else + STATUS_PREFIX="⚠️ I attempted to address the feedback, but encountered some issues." + fi + else + if [[ "$JOB_STATUS" == "success" ]]; then + STATUS_PREFIX="🤖 I attempted to address the review feedback, but no modifications were made." + else + STATUS_PREFIX="⚠️ I encountered issues while attempting to address the feedback, and no modifications were made." + fi + fi + + cat > /tmp/pr-comment.md << EOL + ${STATUS_PREFIX} + + ## Aider Output + \`\`\` + $(cat .aider_output.txt || echo 'No output available') + \`\`\` + + Please review the output and provide additional guidance if needed. + EOL + + # Use the file for comment body + gh pr comment $PR_NUM --body-file /tmp/pr-comment.md + + - name: Comment on issue/PR to let the user know Aider has finished working on the request + if: always() && github.event_name == 'issue_comment' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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" + else + COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR." + fi + else + COMMENT_BODY="⚠️ Aider encountered issues while working on your request. Please check the workflow logs for details." + fi + + gh issue comment ${{ github.event.issue.number }} --body "$COMMENT_BODY" --repo $GITHUB_REPOSITORY + + - name: Comment on linear issue to let the user know Aider has finished working on the request + if: always() && github.event_name == 'repository_dispatch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + 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 }}" + 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" + else + COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR." + fi + else + 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 diff --git a/.github/workflows/aider-external.yaml.archived b/.github/workflows/aider-external.yaml.archived new file mode 100644 index 0000000000..c01152e3ce --- /dev/null +++ b/.github/workflows/aider-external.yaml.archived @@ -0,0 +1,80 @@ +name: External Aider Issue Fix + +on: + repository_dispatch: + types: [external_issue_fix] + +jobs: + check-and-prepare: + runs-on: ubicloud-standard-2 + permissions: + contents: write + pull-requests: write + outputs: + issue_title: ${{ steps.determine_inputs.outputs.ISSUE_TITLE }} + issue_body: ${{ steps.determine_inputs.outputs.ISSUE_BODY }} + instruction: ${{ steps.determine_inputs.outputs.INSTRUCTION }} + env: + GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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 + + - name: Determine inputs for Aider + id: determine_inputs + shell: bash + env: + ISSUE_TITLE: ${{ github.event.client_payload.issue_title }} + ISSUE_BODY: ${{ github.event.client_payload.issue_body }} + INSTRUCTION: ${{ github.event.client_payload.instruction }} + run: | + echo "Determining inputs for Aider..." + + echo "ISSUE_TITLE<> "$GITHUB_OUTPUT" + echo "$ISSUE_TITLE" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT" + + echo "ISSUE_BODY<> "$GITHUB_OUTPUT" + echo "$ISSUE_BODY" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_BODY" >> "$GITHUB_OUTPUT" + + echo "INSTRUCTION<> "$GITHUB_OUTPUT" + echo "$INSTRUCTION" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_INSTRUCTION" >> "$GITHUB_OUTPUT" + echo "Finished determining inputs." + + run-aider: + needs: check-and-prepare + uses: ./.github/workflows/aider-common.yml + with: + issue_title: ${{ needs.check-and-prepare.outputs.issue_title }} + 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 diff --git a/.github/workflows/aider.yaml.archived b/.github/workflows/aider.yaml.archived new file mode 100644 index 0000000000..2e943234b9 --- /dev/null +++ b/.github/workflows/aider.yaml.archived @@ -0,0 +1,165 @@ +name: Aider Auto-fix issues and PR comments via external prompt + +on: + issue_comment: + types: [created] + +jobs: + check-membership: + runs-on: ubicloud-standard-2 + if: | + github.event_name == 'issue_comment' && + contains(github.event.comment.body, '/aider') && + !contains(github.event.comment.user.login, '[bot]') + outputs: + is_member: ${{ steps.check-membership.outputs.is_member }} + steps: + - name: Check organization membership + id: check-membership + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + COMMENTER: ${{ github.event.comment.user.login }} + ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }} + run: | + ORG="windmill-labs" + STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $ORG_ACCESS_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/$ORG/members/$COMMENTER") + + if [ "$STATUS" -eq 204 ]; then + echo "is_member=true" >> $GITHUB_OUTPUT + else + echo "is_member=false" >> $GITHUB_OUTPUT + fi + + check-and-prepare: + needs: check-membership + runs-on: ubicloud-standard-2 + if: needs.check-membership.outputs.is_member == 'true' + permissions: + contents: write + pull-requests: write + issues: write + env: + GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }} + outputs: + issue_title: ${{ steps.determine_inputs.outputs.ISSUE_TITLE }} + issue_body: ${{ steps.determine_inputs.outputs.ISSUE_BODY }} + comment_content: ${{ steps.determine_inputs.outputs.COMMENT_CONTENT }} + pr_branch: ${{ steps.checkout_pr.outputs.PR_BRANCH }} + + steps: + - name: Acknowledge Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + echo "Commenting on issue/PR #${{ github.event.issue.number }} to acknowledge the /aider command." + gh issue comment ${{ github.event.issue.number }} --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." --repo $GITHUB_REPOSITORY + + - name: Determine inputs for Aider + id: determine_inputs + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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="" + ISSUE_BODY_VAL="" + + if [[ ! -z "${{ github.event.issue.pull_request }}" ]]; then + echo "This is a comment on a Pull Request" + PR_NUMBER="$ISSUE_NUMBER" + + PR_BODY_JSON=$(gh pr view "$PR_NUMBER" --json body --repo "$GITHUB_REPOSITORY") + if [[ $? -ne 0 ]]; then + echo "Error fetching PR body for PR #$PR_NUMBER" + PR_BODY_VAL="" + else + PR_BODY_VAL=$(jq -r '.body // ""' <<< "$PR_BODY_JSON") + fi + + if [[ ! -z "$PR_BODY_VAL" ]]; then + REFERENCED_ISSUE="" + if [[ "$PR_BODY_VAL" =~ \#linear:([a-f0-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" + + ISSUE_DETAILS_JSON=$(gh issue view "$REFERENCED_ISSUE" --json title,body --repo "$GITHUB_REPOSITORY") + if [[ $? -ne 0 ]]; then + echo "Error fetching issue details for #$REFERENCED_ISSUE" + else + ISSUE_TITLE_VAL=$(jq -r '.title // ""' <<< "$ISSUE_DETAILS_JSON") + ISSUE_BODY_VAL=$(jq -r '.body // ""' <<< "$ISSUE_DETAILS_JSON") + fi + fi + else + echo "PR body is empty or could not be fetched." + fi + else + echo "This is a comment on a regular issue" + + ISSUE_DETAILS_JSON=$(gh issue view "$ISSUE_NUMBER" --json title,body --repo "$GITHUB_REPOSITORY") + if [[ $? -ne 0 ]]; then + echo "Error fetching issue details for #$ISSUE_NUMBER" + else + ISSUE_TITLE_VAL=$(jq -r '.title // ""' <<< "$ISSUE_DETAILS_JSON") + ISSUE_BODY_VAL=$(jq -r '.body // ""' <<< "$ISSUE_DETAILS_JSON") + fi + fi + + echo "ISSUE_TITLE<> "$GITHUB_OUTPUT" + echo "$ISSUE_TITLE_VAL" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT" + + echo "ISSUE_BODY<> "$GITHUB_OUTPUT" + echo "$ISSUE_BODY_VAL" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_BODY" >> "$GITHUB_OUTPUT" + + CLEAN_COMMENT="${COMMENT_BODY/\/aider/}" + CLEAN_COMMENT="${CLEAN_COMMENT#"${CLEAN_COMMENT%%[![:space:]]*}"}" + CLEAN_COMMENT="${CLEAN_COMMENT%"${CLEAN_COMMENT##*[![:space:]]}"}" + + echo "COMMENT_CONTENT<> "$GITHUB_OUTPUT" + echo "$CLEAN_COMMENT" >> "$GITHUB_OUTPUT" + echo "EOF_AIDER_COMMENT" >> "$GITHUB_OUTPUT" + echo "Finished determining inputs." + + run-aider: + needs: [check-membership, check-and-prepare] + if: needs.check-membership.outputs.is_member == 'true' + uses: ./.github/workflows/aider-common.yml + with: + issue_title: ${{ needs.check-and-prepare.outputs.issue_title }} + 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 diff --git a/.github/workflows/backend-check.yml b/.github/workflows/backend-check.yml index 38f6f6df21..7fc0874564 100644 --- a/.github/workflows/backend-check.yml +++ b/.github/workflows/backend-check.yml @@ -1,5 +1,9 @@ name: Backend check on: + workflow_run: + workflows: ["Change versions"] + types: + - completed push: paths: - "backend/**" @@ -49,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 --all-features + FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh) check_ee: runs-on: ubicloud-standard-8 diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 6d12e4f69c..1ddc3a7781 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -45,9 +45,9 @@ jobs: - uses: oven-sh/setup-bun@v2 with: bun-version: 1.1.43 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v6.2.1 with: - version: "0.4.18" + version: "0.6.2" - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b48ad1c990..195821b2dd 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -37,10 +37,10 @@ jobs: steps: - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: benchmark timeout-minutes: 30 - run: deno run --unstable -A -r + run: deno run -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json @@ -79,10 +79,10 @@ jobs: steps: - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: benchmark timeout-minutes: 20 - run: deno run --unstable -A -r + run: deno run -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts --no-warm-up -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_dedicated.json @@ -154,10 +154,10 @@ jobs: steps: - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: benchmark timeout-minutes: 20 - run: deno run --unstable -A -r + run: deno run -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json @@ -274,10 +274,10 @@ jobs: steps: - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: benchmark timeout-minutes: 20 - run: deno run --unstable -A -r + run: deno run -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json @@ -300,7 +300,7 @@ jobs: steps: - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - uses: actions/checkout@v4 with: ref: benchmarks @@ -309,7 +309,7 @@ jobs: with: merge-multiple: true - name: graphs - run: deno run --unstable -A -r + run: deno run -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_graphs.ts -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/graphs_config.json diff --git a/.github/workflows/build-publish-rh-image.yml b/.github/workflows/build-publish-rh-image.yml index 26b4bd6896..848c30c7f6 100644 --- a/.github/workflows/build-publish-rh-image.yml +++ b/.github/workflows/build-publish-rh-image.yml @@ -64,7 +64,7 @@ jobs: platforms: linux/amd64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust + 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 secrets: | rh_username=${{ secrets.RH_USERNAME }} rh_password=${{ secrets.RH_PASSWORD }} @@ -81,7 +81,7 @@ jobs: platforms: linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust + 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 secrets: | rh_username=${{ secrets.RH_USERNAME }} rh_password=${{ secrets.RH_PASSWORD }} @@ -111,8 +111,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: RHEL9-amd64 build - path: - ${{ steps.extract-ee-amd64.outputs.destination + path: ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel9 # - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build_windows_worker_.yml b/.github/workflows/build_windows_worker_.yml index c108f8a0d1..738b2a9224 100644 --- a/.github/workflows/build_windows_worker_.yml +++ b/.github/workflows/build_windows_worker_.yml @@ -51,8 +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,deno_core,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust - + 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_windows,mcp,private - name: Rename binary with corresponding architecture run: | Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe" diff --git a/.github/workflows/change-versions.yml b/.github/workflows/change-versions.yml index 034b022f06..eea62bf6b7 100644 --- a/.github/workflows/change-versions.yml +++ b/.github/workflows/change-versions.yml @@ -9,7 +9,14 @@ jobs: runs-on: ubicloud container: node:18 steps: + - uses: actions/create-github-app-token@v2 + id: app + with: + app-id: ${{ vars.INTERNAL_APP_ID }} + private-key: ${{ secrets.INTERNAL_APP_KEY }} - uses: actions/checkout@v4 + with: + token: ${{ steps.app.outputs.token }} - run: git config --system --add safe.directory /__w/windmill/windmill - name: Change versions run: ./.github/change-versions.sh "$(cat version.txt)" @@ -21,3 +28,8 @@ jobs: cd backend cargo generate-lockfile - uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_user_name: windmill-internal-app[bot] + commit_user_email: windmill-internal-app[bot]@users.noreply.github.com + env: + GITHUB_TOKEN: ${{ steps.app.outputs.token }} diff --git a/.github/workflows/check-org-membership.yml b/.github/workflows/check-org-membership.yml new file mode 100644 index 0000000000..910c39c7ba --- /dev/null +++ b/.github/workflows/check-org-membership.yml @@ -0,0 +1,60 @@ +name: Check Organization Membership + +on: + workflow_call: + inputs: + commenter: + required: true + type: string + description: 'The username to check for organization membership' + organization: + required: false + type: string + default: 'windmill-labs' + description: 'The organization to check membership for' + trusted_bot: + required: false + type: string + default: 'windmill-internal-app[bot]' + description: 'The trusted bot username to allow' + secrets: + access_token: + required: true + description: 'The access token to use for org membership check' + outputs: + is_member: + description: 'Whether the user is an organization member or trusted bot' + value: ${{ jobs.check-membership.outputs.is_member }} + +jobs: + check-membership: + runs-on: ubicloud-standard-2 + outputs: + is_member: ${{ steps.check-membership.outputs.is_member }} + steps: + - name: Check organization membership + id: check-membership + env: + ORG_ACCESS_TOKEN: ${{ secrets.access_token }} + COMMENTER: ${{ inputs.commenter }} + ORG: ${{ inputs.organization }} + TRUSTED_BOT: ${{ inputs.trusted_bot }} + run: | + # 1. Allow the trusted bot straight away + if [[ "$COMMENTER" == "$TRUSTED_BOT" ]]; then + echo "is_member=true" >> $GITHUB_OUTPUT + exit 0 + fi + + # 2. Otherwise fall back to the org-membership check + STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $ORG_ACCESS_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/$ORG/members/$COMMENTER") + + if [ "$STATUS" -eq 204 ]; then + echo "is_member=true" >> $GITHUB_OUTPUT + else + echo "is_member=false" >> $GITHUB_OUTPUT + fi \ No newline at end of file diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000000..0c5d6d59d4 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,127 @@ +name: Claude PR Assistant + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + determine-commenter: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai')) || + (github.event_name == 'issues' && contains(github.event.issue.body, '/ai')) + runs-on: ubicloud-standard-2 + outputs: + commenter: ${{ steps.determine-commenter.outputs.commenter }} + steps: + - name: Determine commenter + id: determine-commenter + run: | + # Work out who wrote the comment / review + if [[ "${{ github.event_name }}" == "issue_comment" || \ + "${{ github.event_name }}" == "pull_request_review_comment" ]]; then + COMMENTER="${{ github.event.comment.user.login }}" + elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then + COMMENTER="${{ github.event.review.user.login }}" + else + COMMENTER="${{ github.event.issue.user.login }}" + fi + echo "commenter=$COMMENTER" >> $GITHUB_OUTPUT + + check-membership: + needs: determine-commenter + uses: ./.github/workflows/check-org-membership.yml + with: + commenter: ${{ needs.determine-commenter.outputs.commenter }} + secrets: + access_token: ${{ secrets.ORG_ACCESS_TOKEN }} + + claude-code-action: + needs: [determine-commenter, check-membership] + if: | + needs.check-membership.outputs.is_member == 'true' + runs-on: ubicloud-standard-8 + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Run npm install and generate-backend-client + working-directory: ./frontend + run: | + # add a build directory for cargo check + mkdir -p build + npm install + npm run generate-backend-client + + - name: install xmlsec1 + run: | + sudo apt-get update + sudo apt-get install -y libxml2-dev libxmlsec1-dev + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + cache-workspaces: backend + toolchain: 1.85.0 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: backend + + - name: cargo check + working-directory: ./backend + timeout-minutes: 16 + run: | + SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh) + + - name: Run Claude PR Action + uses: anthropics/claude-code-action@beta + env: + SQLX_OFFLINE: true + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + timeout_minutes: "60" + allowed_tools: "mcp__github__create_pull_request,Bash" + 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. + + ## Code Quality Requirements + + After making any code changes, you MUST run the appropriate validation commands: + + **Frontend Changes:** + - Run: `npm run check` in the frontend directory + - Fix all warnings and errors before proceeding + + **Backend Changes:** + - Run: `cargo check --features $(./all_features_oss.sh)` in the backend directory + - Fix all warnings and errors before proceeding + + **Pull Request Creation:** + - 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 PRs from branches + - Bash: Full access to run validation commands and git operations + trigger_phrase: "/ai" diff --git a/.github/workflows/create-docs.yml b/.github/workflows/create-docs.yml new file mode 100644 index 0000000000..e6b313617c --- /dev/null +++ b/.github/workflows/create-docs.yml @@ -0,0 +1,39 @@ +on: + issue_comment: + types: [created] + +jobs: + check-membership: + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') }} + uses: ./.github/workflows/check-org-membership.yml + with: + commenter: ${{ github.event.comment.user.login }} + secrets: + access_token: ${{ secrets.ORG_ACCESS_TOKEN }} + + generate-token: + needs: check-membership + if: ${{ needs.check-membership.outputs.is_member == 'true' }} + runs-on: ubicloud-standard-2 + outputs: + app_token: ${{ steps.app.outputs.token }} + steps: + - name: Generate an installation token + id: app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.INTERNAL_APP_ID }} + private-key: ${{ secrets.INTERNAL_APP_KEY }} + owner: windmill-labs + + trigger-docs: + needs: [generate-token, check-membership] + if: ${{ needs.check-membership.outputs.is_member == 'true' }} + uses: windmill-labs/windmilldocs/.github/workflows/create-docs.yml@main + with: + pr_number: ${{ github.event.issue.number }} + repo: ${{ github.event.repository.name }} + comment_text: ${{ github.event.comment.body }} + secrets: + DOCS_TOKEN: ${{ needs.generate-token.outputs.app_token }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} diff --git a/.github/workflows/discord-notification.yml b/.github/workflows/discord-notification.yml new file mode 100644 index 0000000000..525c343fe6 --- /dev/null +++ b/.github/workflows/discord-notification.yml @@ -0,0 +1,35 @@ +name: Create discord thread when a PR is opened, react with green checkmark when PR is merged + +on: + pull_request: + types: + - opened + - ready_for_review + - closed + +jobs: + notify_discord_when_pr_opened: + if: (github.event.pull_request.draft == false) && (github.event.action == 'opened' || github.event.action == 'ready_for_review') + uses: ./.github/workflows/shareable-discord-notification.yml + with: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_URL: ${{ github.event.pull_request.html_url }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + PR_STATUS: "opened" + PR_NUMBER: ${{ github.event.pull_request.number }} + DISCORD_CHANNEL_ID: "1372204995868491786" + DISCORD_GUILD_ID: "930051556043276338" + secrets: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_PR_REVIEWS_WEBHOOK }} + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }} + + merge_success_emoji: + if: github.event.action == 'closed' + uses: ./.github/workflows/shareable-discord-notification.yml + with: + PR_STATUS: "merged" + DISCORD_CHANNEL_ID: "1372204995868491786" + DISCORD_GUILD_ID: "930051556043276338" + PR_NUMBER: ${{ github.event.pull_request.number }} + secrets: + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }} diff --git a/.github/workflows/docker-image-rpi4.yml b/.github/workflows/docker-image-rpi4.yml index c0fc7ad576..bb270aac8a 100644 --- a/.github/workflows/docker-image-rpi4.yml +++ b/.github/workflows/docker-image-rpi4.yml @@ -67,7 +67,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust + features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev ${{ steps.meta-public.outputs.tags }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 3b3e417f3d..c630422f86 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,10 +1,8 @@ env: REGISTRY: ghcr.io - IMAGE_NAME: - ${{ github.event_name != 'pull_request' && github.event_name != + IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.event_name != 'workflow_dispatch' && github.repository || 'windmill-labs/windmill-test' }} - DEV_SHA: - ${{ github.event_name != 'pull_request' && github.event_name != + DEV_SHA: ${{ github.event_name != 'pull_request' && github.event_name != 'workflow_dispatch' && 'dev' || github.event.inputs.tag || github.sha }} name: Build windmill:main on: @@ -33,7 +31,7 @@ on: type: boolean concurrency: group: ${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: false permissions: write-all @@ -200,7 +198,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust + 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 tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} ${{ steps.meta-ee-public.outputs.tags }} @@ -358,7 +356,7 @@ jobs: needs: [run_integration_test, build] if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || - startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch') + startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch') steps: - uses: actions/checkout@v4 with: @@ -398,7 +396,7 @@ jobs: verify_ee_image_vulnerabilities: runs-on: ubicloud needs: [tag_latest_ee] - if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch') + if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch') steps: - name: Checkout code uses: actions/checkout@v4 @@ -440,8 +438,7 @@ jobs: build_ee_nsjail: needs: [build_ee] runs-on: ubicloud - if: - (github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)) + if: (github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)) steps: - uses: actions/checkout@v4 @@ -480,7 +477,7 @@ jobs: run: | sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail cat ./docker/DockerfileNsjail | grep "FROM" - + - name: Build and push publicly ee uses: depot/build-push-action@v1 with: @@ -494,14 +491,12 @@ jobs: ${{ steps.meta-ee-public.outputs.labels }} org.opencontainers.image.licenses=Windmill-Enterprise-License - publish_ecr_s3: needs: [build_ee_nsjail] runs-on: ubicloud-standard-2-arm - if: - (github.event_name != 'pull_request') && (github.event_name != + if: (github.event_name != 'pull_request') && (github.event_name != 'workflow_dispatch') && (github.ref == 'refs/heads/main' || - startsWith(github.ref, 'refs/tags/v')) + startsWith(github.ref, 'refs/tags/v')) env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/frontend-check.yml b/.github/workflows/frontend-check.yml index 64883cb303..a838ab3627 100644 --- a/.github/workflows/frontend-check.yml +++ b/.github/workflows/frontend-check.yml @@ -1,10 +1,15 @@ name: check frontend build on: - pull_request: - types: [opened, synchronize, reopened, closed] + workflow_run: + workflows: ["Change versions"] + types: + - completed + + merge_group: + push: paths: - "frontend/**" - merge_group: + - ".github/workflows/frontend-check.yml" jobs: npm_check: @@ -16,5 +21,6 @@ jobs: node-version: 18 - name: "npm check" timeout-minutes: 5 - run: cd frontend && npm ci && npm run generate-backend-client && npm run + run: + cd frontend && npm ci && npm run generate-backend-client && npm run check diff --git a/.github/workflows/helmchart_on_release.yml b/.github/workflows/helmchart_on_release.yml new file mode 100644 index 0000000000..0a4ee872ce --- /dev/null +++ b/.github/workflows/helmchart_on_release.yml @@ -0,0 +1,91 @@ +name: Publish Helm Chart on Release + +on: + release: + types: [published] + +jobs: + bump-helm-version: + runs-on: ubicloud-standard-2 + + steps: + - name: Generate an installation token + id: app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.INTERNAL_APP_ID }} + private-key: ${{ secrets.INTERNAL_APP_KEY }} + owner: windmill-labs + + - name: Checkout on helm repository + uses: actions/checkout@v3 + with: + repository: windmill-labs/windmill-helm-charts + token: ${{ steps.app.outputs.token }} + + - name: Get version + id: get_version + run: | + echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV + + - name: Create new branch + run: | + # Check if branch already exists remotely + if git ls-remote --heads origin bump-helm-version-${{ env.VERSION }} | grep -q bump-helm-version-${{ env.VERSION }}; then + # Branch exists, check it out + git fetch origin bump-helm-version-${{ env.VERSION }} + git checkout bump-helm-version-${{ env.VERSION }} + else + # Create new branch + git checkout -b bump-helm-version-${{ env.VERSION }} + fi + + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Bump helm version + run: | + # Get current version and increment it by 1 + CURRENT_VERSION=$(grep "version:" ./charts/windmill/Chart.yaml | awk '{print $2}' | head -n 1) + NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') + sed -i "s/^version: .*/version: $NEW_VERSION/" ./charts/windmill/Chart.yaml + + # Get the app version from the version + VERSION=${{ env.VERSION }} + APP_VERSION=${VERSION#refs/tag/} + APP_VERSION=${APP_VERSION#v} + APP_VERSION=${APP_VERSION%/} + sed -i "s/appVersion: .*/appVersion: $APP_VERSION/" ./charts/windmill/Chart.yaml + + - name: Close existing bump-helm PRs + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + run: | + # List open PR numbers whose title starts with the prefix + prs=$(gh pr list \ + --state open \ + --search '"helm: bump version to" in:title' \ + --json number \ + -q '.[].number') + + for pr in $prs; do + echo "Closing outdated bump PR #$pr" + gh pr close "$pr" \ + --comment "Closed automatically – superseded by a newer Helm-chart bump PR." + done + + - name: Commit and push + run: | + git add . + git commit -m "Bump helm version to ${{ env.VERSION }}" + git push origin bump-helm-version-${{ env.VERSION }} + + - name: Create PR + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + run: | + gh pr create \ + --title "helm: bump version to ${{ env.VERSION }}" \ + --body "This PR was auto-generated to bring the helm chart up to date for [release ${{ env.VERSION }}](https://github.com/windmill-labs/windmill/releases/tag/v${{ env.VERSION }}) in the main repo." \ + --head bump-helm-version-${{ env.VERSION }} \ + --base main diff --git a/.github/workflows/linear-claude.yaml b/.github/workflows/linear-claude.yaml new file mode 100644 index 0000000000..c74cfeba1d --- /dev/null +++ b/.github/workflows/linear-claude.yaml @@ -0,0 +1,38 @@ +name: Claude PR Assistant + +on: + repository_dispatch: + types: [external_claude_issue_fix] + +jobs: + claude-code-action: + runs-on: ubicloud-standard-8 + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Process inputs + id: process_inputs + shell: bash + run: | + ISSUE_TITLE="${{ github.event.client_payload.issue_title }}" + INSTRUCTION="${{ github.event.client_payload.instruction }}" + ISSUE_BODY=$(printf '%q' "${{ github.event.client_payload.issue_body }}") + BASE_PROMPT="Try to fix the following issue based on the instruction given. You are provided with the issue title, issue body, and instruction. You are to fix the issue based on the instruction. You are to create a pull request to fix the issue." + CUSTOM_PROMPT=$(printf -v PROMPT "%s\n\nISSUE_TITLE: %s\n\nISSUE_BODY: %s\n\nINSTRUCTION: %s" "$BASE_PROMPT" "$ISSUE_TITLE" "$ISSUE_BODY" "$INSTRUCTION") + echo "CUSTOM_PROMPT=$CUSTOM_PROMPT" >> $GITHUB_OUTPUT + + - name: Run Claude PR Action + uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + timeout_minutes: "60" + allowed_tools: "mcp__github__create_pull_request" + direct_prompt: ${{ steps.process_inputs.outputs.CUSTOM_PROMPT }} diff --git a/.github/workflows/npm_on_release.yml b/.github/workflows/npm_on_release.yml index 352109a0da..18c52cb38d 100644 --- a/.github/workflows/npm_on_release.yml +++ b/.github/workflows/npm_on_release.yml @@ -27,7 +27,7 @@ jobs: registry-url: "https://registry.npmjs.org" - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - run: cd cli && ./build.sh && cd npm && npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pr-ready-review.yml b/.github/workflows/pr-ready-review.yml new file mode 100644 index 0000000000..bddb0788ae --- /dev/null +++ b/.github/workflows/pr-ready-review.yml @@ -0,0 +1,22 @@ +name: Auto Comment on PR Ready for Review + +on: + pull_request: + types: [opened, ready_for_review] + +jobs: + add-review-comment: + if: github.event.pull_request.draft == false + runs-on: ubicloud-standard-2 + steps: + - name: Add review comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PUBLIC_REPO_TOKEN }} + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: '/ai review this PR' + }); diff --git a/.github/workflows/publish_windows_worker.yml b/.github/workflows/publish_windows_worker.yml index 50576fc519..e9a8c77e87 100644 --- a/.github/workflows/publish_windows_worker.yml +++ b/.github/workflows/publish_windows_worker.yml @@ -53,8 +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,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,mqtt_trigger,websocket,python,smtp,csharp,static_frontend,rust - + 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_windows,mcp,private - name: Rename binary with corresponding architecture run: | Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe" diff --git a/.github/workflows/rust_on_release.yml b/.github/workflows/rust_on_release.yml new file mode 100644 index 0000000000..5594cc08bb --- /dev/null +++ b/.github/workflows/rust_on_release.yml @@ -0,0 +1,19 @@ +name: Publish rust-client to crates.io on release +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build_rust_and_publish_to_crates_io: + runs-on: ubicloud-standard-8 + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v20 + with: + extra_nix_config: | + experimental-features = nix-command flakes + - run: cd rust-client && nix develop ../ --command ./dev.nu --check --publish + env: + CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} diff --git a/.github/workflows/shareable-discord-notification.yml b/.github/workflows/shareable-discord-notification.yml new file mode 100644 index 0000000000..1b7936330f --- /dev/null +++ b/.github/workflows/shareable-discord-notification.yml @@ -0,0 +1,119 @@ +name: "Notify Discord when a PR is opened or merged" + +on: + workflow_call: + inputs: + PR_TITLE: + description: "The title of the PR" + type: string + PR_URL: + description: "The URL of the PR" + type: string + PR_AUTHOR: + description: "The author of the PR" + type: string + PR_STATUS: + description: "The status of the PR" + type: string + DISCORD_CHANNEL_ID: + description: "The Discord channel ID" + type: string + PR_NUMBER: + description: "The number of the PR" + type: string + DISCORD_GUILD_ID: + description: "The Discord guild ID" + type: string + secrets: + DISCORD_WEBHOOK_URL: + description: "Discord Webhook URL" + DISCORD_BOT_TOKEN: + description: "Discord Bot Token" + +jobs: + open_thread: + runs-on: ubicloud-standard-2 + if: ${{ inputs.PR_STATUS == 'opened' }} + steps: + - name: Send Discord notification and start a thread + env: + WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }} + GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }} + PR_TITLE: ${{ inputs.PR_TITLE }} + PR_NUMBER: ${{ inputs.PR_NUMBER }} + PR_URL: ${{ inputs.PR_URL }} + PR_AUTHOR: ${{ inputs.PR_AUTHOR }} + run: | + # Check if thread already exists + thread_exists=false + if threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" "https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active"); then + if thread_id=$(echo "$threads" | jq -r --arg cid "$CHANNEL_ID" --arg pref "#${PR_NUMBER}:" '.threads[] | select(.parent_id == $cid and (.name | startswith($pref))) | .id' 2>/dev/null); then + if [ -n "$thread_id" ]; then + thread_exists=true + echo "Thread already exists, skipping creation" + fi + fi + else + echo "Failed to check for existing threads, will create new thread" + fi + + # Create thread if it doesn't exist or if check failed + if [ "$thread_exists" = false ]; then + echo "Creating new thread" + THREAD_TITLE="#${PR_NUMBER}: ${PR_TITLE} by \`${PR_AUTHOR}\`" + payload=$(jq -n \ + --arg content "${PR_URL}" \ + --arg thread "${THREAD_TITLE:0:99}" \ + '{ + content: $content, + thread_name: $thread, + auto_archive_duration: 10080 + }' + ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "$payload" \ + "$WEBHOOK_URL" + fi + + merge_success_emoji: + runs-on: ubuntu-latest + if: ${{ inputs.PR_STATUS == 'merged' }} + steps: + - name: React + env: + BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }} + GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }} + PR_NUMBER: ${{ inputs.PR_NUMBER }} + run: | + # 1) get PR thread + threads=$(curl -H "Authorization: Bot $BOT_TOKEN" "https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active") + thread_id=$( + echo "$threads" \ + | jq -r --arg cid "$CHANNEL_ID" \ + --arg pref "#${PR_NUMBER}:" \ + '.threads[] + | select(.parent_id == $cid and (.name | startswith($pref))) + | .id' + ) + if [ -z "$thread_id" ]; then + echo "Thread not found" + exit 1 + 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") + message_id=$(echo "$messages" | jq -r '.[-1].id') + + if [ -z "$message_id" ]; then + echo "Message not found" + exit 1 + fi + + # 3) add the ✅ reaction + curl -X PUT \ + -H "Authorization: Bot $BOT_TOKEN" \ + "https://discord.com/api/v10/channels/$thread_id/messages/$message_id/reactions/%E2%9C%85/@me" diff --git a/.github/workflows/update-sqlx.yaml b/.github/workflows/update-sqlx.yaml new file mode 100644 index 0000000000..7632a9b705 --- /dev/null +++ b/.github/workflows/update-sqlx.yaml @@ -0,0 +1,105 @@ +name: Update SQLx + +on: + issue_comment: + types: [created] + +jobs: + update-sqlx: + if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/updatesqlx') + runs-on: ubicloud-standard-8 + permissions: + contents: write + pull-requests: write + issues: write + + services: + postgres: + image: postgres:14 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: windmill + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Comment on PR - Starting + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Starting sqlx update...' + }) + + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ github.event.issue.pull_request.head.ref }} + fetch-depth: 0 + + - name: Checkout windmill-ee-private + uses: actions/checkout@v3 + with: + repository: windmill-labs/windmill-ee-private + path: windmill-ee-private + token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }} + + # Cache rust dependencies + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "./backend -> target" + + - name: Install xmlsec build-time deps + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + pkg-config libxml2-dev libssl-dev \ + xmlsec1 libxmlsec1-dev libxmlsec1-openssl + + - name: Run update-sqlx script + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/windmill + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.issue.number }} + BRANCH_NAME=$(gh pr view $PR_NUMBER --json headRefName --jq .headRefName) + echo "Checking out PR branch: $BRANCH_NAME" + git checkout $BRANCH_NAME + mkdir frontend/build + cd backend + cargo install sqlx-cli --version 0.8.5 + sqlx migrate run + ./update_sqlx.sh --dir ./windmill-ee-private + # Pass the branch name to the next step + echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + + - name: Commit changes if any + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add backend/.sqlx + git commit -m "Update SQLx metadata" + git push origin ${{ env.BRANCH_NAME }} + + - name: Comment on PR - Completed + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Successfully ran sqlx update' + }) diff --git a/.github/workflows/validate-openapi.yml b/.github/workflows/validate-openapi.yml new file mode 100644 index 0000000000..5bc59ebc4c --- /dev/null +++ b/.github/workflows/validate-openapi.yml @@ -0,0 +1,34 @@ +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 + diff --git a/.gitignore b/.gitignore index 3e6c2f3a75..04859823c9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ CaddyfileRemoteMalo **/.idea/ .direnv .vscode +.dev-docker-wrapper* +backend/.minio-data +.aider* +!.aiderignore +rust-client/Cargo.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f65c9615e..62749c0973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,743 @@ # Changelog +## [1.501.4](https://github.com/windmill-labs/windmill/compare/v1.501.3...v1.501.4) (2025-06-26) + + +### Bug Fixes + +* add windows paths to uv install to find git/ssh ([#6063](https://github.com/windmill-labs/windmill/issues/6063)) ([835f1d2](https://github.com/windmill-labs/windmill/commit/835f1d2ec945145942deaa41cb3bd176ed276279)) +* optionally enable CSP headers ([#6033](https://github.com/windmill-labs/windmill/issues/6033)) ([d933648](https://github.com/windmill-labs/windmill/commit/d933648d3666b2ca9d813e04b9f19ddc3c7efda3)) +* schemaform reorder ([#6069](https://github.com/windmill-labs/windmill/issues/6069)) ([1a4b096](https://github.com/windmill-labs/windmill/commit/1a4b096f3ce40e238f1724aa4fd26649d70cb62a)) + +## [1.501.3](https://github.com/windmill-labs/windmill/compare/v1.501.2...v1.501.3) (2025-06-25) + + +### Bug Fixes + +* **backend:** return correct content-type for openapi spec ([#6045](https://github.com/windmill-labs/windmill/issues/6045)) ([44457c7](https://github.com/windmill-labs/windmill/commit/44457c72cf75c969de97c39bb23f57acad268e10)) +* **frontend:** load all flow jobs on page load ([#6029](https://github.com/windmill-labs/windmill/issues/6029)) ([dc5e764](https://github.com/windmill-labs/windmill/commit/dc5e764d9db9251dc356094d6ac47c45fdf72c74)) +* ignore type only imports when computing ts lockfiles ([900c8ed](https://github.com/windmill-labs/windmill/commit/900c8edd7b35802e23a1359029da8ddbfb783753)) +* improve ordering of forms for non complete ordering + array schema fix ([18ee03a](https://github.com/windmill-labs/windmill/commit/18ee03a32371885f5e608cb306b5ccbccc31dac5)) +* missing static_asset_config from api call ([#6058](https://github.com/windmill-labs/windmill/issues/6058)) ([395f1ff](https://github.com/windmill-labs/windmill/commit/395f1ff8ba05020d72d1d8b34bd6bb32517b7aec)) + +## [1.501.2](https://github.com/windmill-labs/windmill/compare/v1.501.1...v1.501.2) (2025-06-24) + + +### Bug Fixes + +* improve schema form handling of inconsistent order and properties ([3daf79f](https://github.com/windmill-labs/windmill/commit/3daf79ffbc45ca32ff443e5521a67d62528665db)) + +## [1.501.1](https://github.com/windmill-labs/windmill/compare/v1.501.0...v1.501.1) (2025-06-24) + + +### Bug Fixes + +* optimize jobs list run incremental refresh performance ([1bdd00a](https://github.com/windmill-labs/windmill/commit/1bdd00a3e4a94ecb23efb9614c341c64a67ac389)) +* pwsh skip already installed modules outside of cache ([#6037](https://github.com/windmill-labs/windmill/issues/6037)) ([29f6fab](https://github.com/windmill-labs/windmill/commit/29f6fab60c6f8cf251182a56c09bac7692868bae)) + +## [1.501.0](https://github.com/windmill-labs/windmill/compare/v1.500.3...v1.501.0) (2025-06-24) + + +### Features + +* ai flow chat prompt and UX improvements ([#5942](https://github.com/windmill-labs/windmill/issues/5942)) ([5722014](https://github.com/windmill-labs/windmill/commit/57220146513444436faff95f58c1b36481d1fa1d)) + + +### Bug Fixes + +* improve reactivity of apps ([27e12a1](https://github.com/windmill-labs/windmill/commit/27e12a1527c41ac801042038b707a94897e718f8)) + +## [1.500.3](https://github.com/windmill-labs/windmill/compare/v1.500.2...v1.500.3) (2025-06-23) + + +### Bug Fixes + +* fix conditional wrappre ([6f3cb5e](https://github.com/windmill-labs/windmill/commit/6f3cb5eabb7b2224d04ec10f151f67c0955a5cfd)) + +## [1.500.2](https://github.com/windmill-labs/windmill/compare/v1.500.1...v1.500.2) (2025-06-20) + + +### Bug Fixes + +* consistency of root job propagation fixing cases where runFlow in scripts would fail ([9c2f6a7](https://github.com/windmill-labs/windmill/commit/9c2f6a757fb168c7305c991c9fdbf78acd856a1c)) + +## [1.500.1](https://github.com/windmill-labs/windmill/compare/v1.500.0...v1.500.1) (2025-06-20) + + +### Bug Fixes + +* git repository resource picker effect loop ([#6017](https://github.com/windmill-labs/windmill/issues/6017)) ([1b1bee5](https://github.com/windmill-labs/windmill/commit/1b1bee5b53d78e4407b684b567d0fddd2b5283f3)) + +## [1.500.0](https://github.com/windmill-labs/windmill/compare/v1.499.0...v1.500.0) (2025-06-20) + + +### Features + +* add typescript client context to ai chat system prompt ([#6004](https://github.com/windmill-labs/windmill/issues/6004)) ([3e82282](https://github.com/windmill-labs/windmill/commit/3e822823519d1d5c22e422e4bd1ad4d37b6428b6)) +* blacklist remote agent worker token ([#5985](https://github.com/windmill-labs/windmill/issues/5985)) ([86eb907](https://github.com/windmill-labs/windmill/commit/86eb9074cc94f309f17ea72e9cecd0d502ffd2be)) +* **frontend:** run steps from graph ([#5915](https://github.com/windmill-labs/windmill/issues/5915)) ([67e6bce](https://github.com/windmill-labs/windmill/commit/67e6bce9b2eba1653450921afab3eabbd41fc715)) + + +### Bug Fixes + +* ai button in inline script editor to open AI chat in flow builder ([#5989](https://github.com/windmill-labs/windmill/issues/5989)) ([4ae5928](https://github.com/windmill-labs/windmill/commit/4ae5928788831196672e212b32ca410afab640e0)) +* improve piptar upload - sequential uploads via background task queue ([#5994](https://github.com/windmill-labs/windmill/issues/5994)) ([c4adaee](https://github.com/windmill-labs/windmill/commit/c4adaeeabd287ca1c4f3522bcd8bcea30b00fe6d)) +* new MultiSelect component ([#5979](https://github.com/windmill-labs/windmill/issues/5979)) ([fa8d1b4](https://github.com/windmill-labs/windmill/commit/fa8d1b47db19e15fe854e01f9987c8f97cb45b44)) +* replace worker tags to listen multiselect ([#5997](https://github.com/windmill-labs/windmill/issues/5997)) ([e4255e6](https://github.com/windmill-labs/windmill/commit/e4255e6276565c4a45b1f45a5d627bcfb5369270)) + +## [1.499.0](https://github.com/windmill-labs/windmill/compare/v1.498.0...v1.499.0) (2025-06-18) + + +### Features + +* devOps role can edit worker groups ([#5984](https://github.com/windmill-labs/windmill/issues/5984)) ([b1c4f8b](https://github.com/windmill-labs/windmill/commit/b1c4f8b29d0fb4cad76853110b84a87892b54661)) + + +### Bug Fixes + +* prevent keypress events from bubbling in decision tree drawer ([#5993](https://github.com/windmill-labs/windmill/issues/5993)) ([2a33442](https://github.com/windmill-labs/windmill/commit/2a334421e85abf046784aab57522582439ef2901)) + +## [1.498.0](https://github.com/windmill-labs/windmill/compare/v1.497.2...v1.498.0) (2025-06-17) + + +### Features + +* use provider api to list available AI models in workspace settings ([#5947](https://github.com/windmill-labs/windmill/issues/5947)) ([7490e88](https://github.com/windmill-labs/windmill/commit/7490e883d747a7f65b2fefd3ec14b1cfc3d9bbd4)) +* windmill http triggers and webhooks to openapi spec ([#5918](https://github.com/windmill-labs/windmill/issues/5918)) ([aba8c01](https://github.com/windmill-labs/windmill/commit/aba8c01d7f44ba4be369a3c711be9e156d6bf215)) + +## [1.497.2](https://github.com/windmill-labs/windmill/compare/v1.497.1...v1.497.2) (2025-06-17) + + +### Bug Fixes + +* always rm containers in docker mode ([38eb71b](https://github.com/windmill-labs/windmill/commit/38eb71bdf55ee2f606d1d2ad2e987d5af16d88c0)) +* flow steps use their tags if any specific when used as subflow ([26bec05](https://github.com/windmill-labs/windmill/commit/26bec054a3447a91c5d5f56d8b98717c06496087)) + +## [1.497.1](https://github.com/windmill-labs/windmill/compare/v1.497.0...v1.497.1) (2025-06-16) + + +### Bug Fixes + +* fix mcp server initialization ([1c6a7c8](https://github.com/windmill-labs/windmill/commit/1c6a7c8cd0bd8396f158e3cb0583b927ce957f12)) + +## [1.497.0](https://github.com/windmill-labs/windmill/compare/v1.496.3...v1.497.0) (2025-06-16) + + +### Features + +* add api tools to ai chat ([#5921](https://github.com/windmill-labs/windmill/issues/5921)) ([f7a83c0](https://github.com/windmill-labs/windmill/commit/f7a83c03c12b8ae70179fb228e0e2391b6ea2858)) +* **backend:** use streamable http in favor of sse for MCP ([#5910](https://github.com/windmill-labs/windmill/issues/5910)) ([d47c078](https://github.com/windmill-labs/windmill/commit/d47c078bb5ab86d82d9cbbce3c55c89c0c20d809)) +* better graph layout algorithm + migrate to svelte 5 almost everywhere + xyflow 1.0 ([23920ae](https://github.com/windmill-labs/windmill/commit/23920aee84fdca4a557a34ff2d66a0bb7bdca605)) +* fill runnable inputs with AI chat ([#5887](https://github.com/windmill-labs/windmill/issues/5887)) ([b4a6a7e](https://github.com/windmill-labs/windmill/commit/b4a6a7e72429617d420af85a9de35bb13adfc6fb)) +* **go:** local go.mod ([#5929](https://github.com/windmill-labs/windmill/issues/5929)) ([0b89260](https://github.com/windmill-labs/windmill/commit/0b89260540b307c6d614ca4275dd038fbfdac33c)) +* multiple azure models support ([#5920](https://github.com/windmill-labs/windmill/issues/5920)) ([f412ede](https://github.com/windmill-labs/windmill/commit/f412ede6ed48e9a492f39582ac70a5584477529e)) +* **rust:** add rust sdk ([#5909](https://github.com/windmill-labs/windmill/issues/5909)) ([332f66e](https://github.com/windmill-labs/windmill/commit/332f66e3483abbeacd4e7c1b74c94c5265314882)) + + +### Bug Fixes + +* ai chat tooltip + user settings autocomplete issue ([#5917](https://github.com/windmill-labs/windmill/issues/5917)) ([6f907c7](https://github.com/windmill-labs/windmill/commit/6f907c79b4cf6279bd52e35a3ee96e0d021422f5)) +* audit logs for token refresh + consider refresh for active users ([#5930](https://github.com/windmill-labs/windmill/issues/5930)) ([cf2d09e](https://github.com/windmill-labs/windmill/commit/cf2d09e7a8c5d2472af0d483689c3fcfa2976117)) +* fix input with wrong height on first render ([#5935](https://github.com/windmill-labs/windmill/issues/5935)) ([1a6283b](https://github.com/windmill-labs/windmill/commit/1a6283b42a6a514ab2e05160855cdc0f70b61d0e)) +* flow step missing input warnings ([#5916](https://github.com/windmill-labs/windmill/issues/5916)) ([f077849](https://github.com/windmill-labs/windmill/commit/f077849b8f7c1916fd420e85b4844a5c5e93a139)) +* **frontend:** use correct kind for flow insert module btn ([#5938](https://github.com/windmill-labs/windmill/issues/5938)) ([17c8c8a](https://github.com/windmill-labs/windmill/commit/17c8c8a5616ab8656799cea3fc5bc7cfaedc4995)) + +## [1.496.3](https://github.com/windmill-labs/windmill/compare/v1.496.2...v1.496.3) (2025-06-09) + + +### Bug Fixes + +* improve concurrent job parallelism performance ([e8836a3](https://github.com/windmill-labs/windmill/commit/e8836a393a872bb91e68ba0037681caf24149470)) +* Prioritize diff contexts in script mode for ai chat ([#5888](https://github.com/windmill-labs/windmill/issues/5888)) ([a47939d](https://github.com/windmill-labs/windmill/commit/a47939d13c30e2d4b41efd539f845959174d4fb1)) + +## [1.496.2](https://github.com/windmill-labs/windmill/compare/v1.496.1...v1.496.2) (2025-06-07) + + +### Bug Fixes + +* add clearable by default for select ([#5900](https://github.com/windmill-labs/windmill/issues/5900)) ([b44b9c1](https://github.com/windmill-labs/windmill/commit/b44b9c1b82116ad5487af95d1f78226d56c75179)) + +## [1.496.1](https://github.com/windmill-labs/windmill/compare/v1.496.0...v1.496.1) (2025-06-07) + + +### Bug Fixes + +* never consider minor version for global site packages ([#5893](https://github.com/windmill-labs/windmill/issues/5893)) ([22b2f49](https://github.com/windmill-labs/windmill/commit/22b2f4988db9314f2403508933d0aa932187c668)) + +## [1.496.0](https://github.com/windmill-labs/windmill/compare/v1.495.1...v1.496.0) (2025-06-06) + + +### Features + +* generate http route triggers from openapi spec ([#5857](https://github.com/windmill-labs/windmill/issues/5857)) ([5713483](https://github.com/windmill-labs/windmill/commit/571348377b73d54b4d2a1c5775ab00b247b01910)) + + +### Bug Fixes + +* allow fileupload drag and drop in edit mode on full component without triggering file picker ([#5889](https://github.com/windmill-labs/windmill/issues/5889)) ([9ae3212](https://github.com/windmill-labs/windmill/commit/9ae3212a1e0f88a8297bf41ab53e3c1be4bcc56c)) +* **python:** account instance version when cli deploy and local lockfile ([#5894](https://github.com/windmill-labs/windmill/issues/5894)) ([ec552d5](https://github.com/windmill-labs/windmill/commit/ec552d5ef6fdb5e824e453f196f9cf16629ee2ea)) +* use full client side js library for route gen from openapi ([#5891](https://github.com/windmill-labs/windmill/issues/5891)) ([3c3fdbd](https://github.com/windmill-labs/windmill/commit/3c3fdbdf26a9581b815210839b91ebdedb924093)) + +## [1.495.0](https://github.com/windmill-labs/windmill/compare/v1.494.0...v1.495.0) (2025-06-05) + + +### Features + +* Add ask mode to AI chat ([#5878](https://github.com/windmill-labs/windmill/issues/5878)) ([67ab469](https://github.com/windmill-labs/windmill/commit/67ab46990ad0c9fad810a64c54297419c6151c79)) +* add navigator mode to AIChat and unify UI ([#5859](https://github.com/windmill-labs/windmill/issues/5859)) ([cbba829](https://github.com/windmill-labs/windmill/commit/cbba8297cd4c1caa21b96a8422bbbd5c306b8398)) +* ai flow chat ([#5842](https://github.com/windmill-labs/windmill/issues/5842)) ([68ebf66](https://github.com/windmill-labs/windmill/commit/68ebf667d5c0bc306329d0b55a3cc59e5b4862cb)) +* ai prompts improvements + o3/o4 support ([#5862](https://github.com/windmill-labs/windmill/issues/5862)) ([825422c](https://github.com/windmill-labs/windmill/commit/825422c48456b2c9b230e1a35914b3fbf7d1e836)) +* connect fix btn in flow editor to ai chat ([#5863](https://github.com/windmill-labs/windmill/issues/5863)) ([6247d15](https://github.com/windmill-labs/windmill/commit/6247d159ce25ae13f6fbc5c105df88305ce29451)) +* fix backward compatibility pg 14 for postgres trigger ([#5851](https://github.com/windmill-labs/windmill/issues/5851)) ([4cbcbdb](https://github.com/windmill-labs/windmill/commit/4cbcbdb960b469acf773d3943128b6c7d0dcb0b8)) +* ssh repl like direct to workers hosts machine ([#5809](https://github.com/windmill-labs/windmill/issues/5809)) ([f252657](https://github.com/windmill-labs/windmill/commit/f2526571a3614156b2b1e5cc91b15d0c57565d99)) +* use rust-postgres client instead of sqlx for postgres trigger ([#5853](https://github.com/windmill-labs/windmill/issues/5853)) ([39dbd64](https://github.com/windmill-labs/windmill/commit/39dbd646b9683e0ad8de047cca786ae468759e77)) + + +### Bug Fixes + +* broken event dispatch for simpleditor ([#5879](https://github.com/windmill-labs/windmill/issues/5879)) ([df4992a](https://github.com/windmill-labs/windmill/commit/df4992a9295ed188c2a2cb0a5dfd3e33ae2e2dcb)) +* cannot parse INSTANCE_PYTHON_VERSION ([#5874](https://github.com/windmill-labs/windmill/issues/5874)) ([a0b302d](https://github.com/windmill-labs/windmill/commit/a0b302d2c58d4245260376cf280bc866be91717c)) +* fix regex that extract workspaces from custom tags ([#5876](https://github.com/windmill-labs/windmill/issues/5876)) ([1551dc8](https://github.com/windmill-labs/windmill/commit/1551dc8af22f6ea41f68290ace4c58f936c47745)) +* nit ai flow prompt ([#5867](https://github.com/windmill-labs/windmill/issues/5867)) ([3e769f0](https://github.com/windmill-labs/windmill/commit/3e769f0c591b80138b3a356d147228675756452f)) +* **python:** assign PATCH version to python runtime only when needed ([#5866](https://github.com/windmill-labs/windmill/issues/5866)) ([50a5c1f](https://github.com/windmill-labs/windmill/commit/50a5c1f56a7e45882fa0095203de709571e149bb)) +* remove duplicate tools from script ai chat ([#5880](https://github.com/windmill-labs/windmill/issues/5880)) ([fe4a767](https://github.com/windmill-labs/windmill/commit/fe4a767df0e6f46fd0c0fd21b4116c7375978bf9)) +* replace crypto.randomUUID with generateRandomString for HTTP compatibility ([#5849](https://github.com/windmill-labs/windmill/issues/5849)) ([64f35d0](https://github.com/windmill-labs/windmill/commit/64f35d050fb0d1008ce7142fd62d500845e62c4a)), closes [#5847](https://github.com/windmill-labs/windmill/issues/5847) + +## [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) + + +### Bug Fixes + +* improve handling of custom concurrency key/tag with preprocessors ([#5762](https://github.com/windmill-labs/windmill/issues/5762)) ([59afa49](https://github.com/windmill-labs/windmill/commit/59afa493fa20cc70b6825e6356713cef84d75312)) +* S3 sql mode returns S3Object ([#5764](https://github.com/windmill-labs/windmill/issues/5764)) ([b29c6e7](https://github.com/windmill-labs/windmill/commit/b29c6e7636bb21c4d977bdaf89ac90e2a1a1086c)) + +## [1.491.4](https://github.com/windmill-labs/windmill/compare/v1.491.3...v1.491.4) (2025-05-15) + + +### Bug Fixes + +* add v1 preprocessor support to workspace preprocessor script ([#5757](https://github.com/windmill-labs/windmill/issues/5757)) ([9b1c30e](https://github.com/windmill-labs/windmill/commit/9b1c30eeff35291ad50f3ddeb64831eac88e2f66)) + +## [1.491.3](https://github.com/windmill-labs/windmill/compare/v1.491.2...v1.491.3) (2025-05-15) + + +### Bug Fixes + +* **frontend:** fix accordeon tabs initialization ([f488903](https://github.com/windmill-labs/windmill/commit/f488903635a1457f839ca641ed4f8d0891ef8212)) +* http trigger routers cache version sequence ([#5755](https://github.com/windmill-labs/windmill/issues/5755)) ([d53bceb](https://github.com/windmill-labs/windmill/commit/d53bceb8004541b79d33220ae8de06d25521da91)) + +## [1.491.2](https://github.com/windmill-labs/windmill/compare/v1.491.1...v1.491.2) (2025-05-15) + + +### Bug Fixes + +* **cli:** --version improvement ([f8f2015](https://github.com/windmill-labs/windmill/commit/f8f201564f7a323eb96f6dc684a525a0784d41f2)) +* http trigger signature validation ([#5753](https://github.com/windmill-labs/windmill/issues/5753)) ([9e9514b](https://github.com/windmill-labs/windmill/commit/9e9514b9af2337e143a9e4cf1e915e1477032e80)) +* Improve indexer performance by factoring required queries to the DB # ([#5749](https://github.com/windmill-labs/windmill/issues/5749)) ([b12feaf](https://github.com/windmill-labs/windmill/commit/b12feaf50ae0ef03816719ff39157fcf55159dbf)) +* improve perf of job deletion ([0efba94](https://github.com/windmill-labs/windmill/commit/0efba945bac9b84a489c6ef552e834593f209fe1)) + + +### Performance Improvements + +* cache http trigger routers and auth ([#5748](https://github.com/windmill-labs/windmill/issues/5748)) ([ddd18d2](https://github.com/windmill-labs/windmill/commit/ddd18d22a615408a9f57f910d0a58f17e6d6e29d)) + +## [1.491.1](https://github.com/windmill-labs/windmill/compare/v1.491.0...v1.491.1) (2025-05-15) + + +### Bug Fixes + +* avoid deadlocks in sending completed job to result processors ([#5742](https://github.com/windmill-labs/windmill/issues/5742)) ([e87d4f3](https://github.com/windmill-labs/windmill/commit/e87d4f3c1afb4ad356b326b7600c89e6c7803eff)) + +## [1.491.0](https://github.com/windmill-labs/windmill/compare/v1.490.0...v1.491.0) (2025-05-14) + + +### Features + +* Microsoft Teams approvals ([#5734](https://github.com/windmill-labs/windmill/issues/5734)) ([039f3e0](https://github.com/windmill-labs/windmill/commit/039f3e02268f2acda48abea420479216970e58e7)) +* sql jobs outputting to s3 + streaming for high-number of rows ([#5704](https://github.com/windmill-labs/windmill/issues/5704)) ([c7886ea](https://github.com/windmill-labs/windmill/commit/c7886ea07ae44af56f1467288b2d73ff2ae27964)) + + +### Bug Fixes + +* add missing run job transaction drop ([#5730](https://github.com/windmill-labs/windmill/issues/5730)) ([318def9](https://github.com/windmill-labs/windmill/commit/318def976cf0e4d5c32d01ac611a89e0a6425368)) +* add support for log compaction on docker jobs ([#5732](https://github.com/windmill-labs/windmill/issues/5732)) ([d35a7d2](https://github.com/windmill-labs/windmill/commit/d35a7d22f960f485889e22de48e8de8557069cb7)) +* Ansible lockfile back compatibility issue ([#5731](https://github.com/windmill-labs/windmill/issues/5731)) ([f73c90c](https://github.com/windmill-labs/windmill/commit/f73c90c7518569204b298b916d0fc298932d3cf0)) +* trigger event support for webhook get endpoints ([#5728](https://github.com/windmill-labs/windmill/issues/5728)) ([76258b7](https://github.com/windmill-labs/windmill/commit/76258b7b1af1313f694731d77f3fa6994e9ded70)) + +## [1.490.0](https://github.com/windmill-labs/windmill/compare/v1.489.0...v1.490.0) (2025-05-12) + + +### Features + +* preprocessor refactor ([#5629](https://github.com/windmill-labs/windmill/issues/5629)) ([254c3cf](https://github.com/windmill-labs/windmill/commit/254c3cf8eff32071d5290429aafd26992527fbca)) + + +### Bug Fixes + +* add back missing query args from http trigger object + correct wm_trigger shape ([#5722](https://github.com/windmill-labs/windmill/issues/5722)) ([66798df](https://github.com/windmill-labs/windmill/commit/66798df38464d732864627ae27a0e51e9518c609)) +* fix date input issue with initializer ([0cd9293](https://github.com/windmill-labs/windmill/commit/0cd92932f0e0998fc30ac02065d292ec35db5cae)) +* improve agents workers handling of WHITELIST_ENVS ([7c69959](https://github.com/windmill-labs/windmill/commit/7c699598533dade9713d976d8dd90fc657ebb503)) +* improve error display of nativets exceptions ([a3c76fb](https://github.com/windmill-labs/windmill/commit/a3c76fb10cba4d18547e66e47edec84833172b64)) +* make ansible more resilient to invalid lockfiles ([b51568c](https://github.com/windmill-labs/windmill/commit/b51568c166e29ec5ee4053fb14abda2fe6d46488)) + +## [1.489.0](https://github.com/windmill-labs/windmill/compare/v1.488.0...v1.489.0) (2025-05-08) + + +### Features + +* raise error if end early in flow ([#5653](https://github.com/windmill-labs/windmill/issues/5653)) ([242a565](https://github.com/windmill-labs/windmill/commit/242a5654285b0a3bf222c80e82f6861ffafed838)) + +## [1.488.0](https://github.com/windmill-labs/windmill/compare/v1.487.0...v1.488.0) (2025-05-07) + + +### Features + +* handle . in interpolated args ([0ac8e47](https://github.com/windmill-labs/windmill/commit/0ac8e477d6fb7c5a7699a198fce9d18a08aff68c)) + + +### Bug Fixes + +* fix azure object storage regression due to object_store regression ([df9f827](https://github.com/windmill-labs/windmill/commit/df9f827d103def27166a767044373bd0754285e2)) +* performance and stability improvement to fetch last deployed script ([75d9924](https://github.com/windmill-labs/windmill/commit/75d992449c845fd11c9a317d401c405e7d78e1ec)) + +## [1.487.0](https://github.com/windmill-labs/windmill/compare/v1.486.1...v1.487.0) (2025-05-06) + + +### Features + +* critical alert if disk near full ([#5549](https://github.com/windmill-labs/windmill/issues/5549)) ([4fd0561](https://github.com/windmill-labs/windmill/commit/4fd056123907337efb5f5669975b337973a124cc)) + + +### Bug Fixes + +* ansible in agent mode can use inventory.ini ([9bdd301](https://github.com/windmill-labs/windmill/commit/9bdd301f5296fbfb631df9ff9100e92e0984ff64)) + +## [1.486.1](https://github.com/windmill-labs/windmill/compare/v1.486.0...v1.486.1) (2025-05-04) + + +### Bug Fixes + +* improve MultiSelectWrapper behavior ([36da8ae](https://github.com/windmill-labs/windmill/commit/36da8aec080742e13f23e1dee12b3954947f53dd)) + +## [1.486.0](https://github.com/windmill-labs/windmill/compare/v1.485.3...v1.486.0) (2025-05-01) + + +### Features + +* add run now directly on schedule drawer and duplicate schedule option ([#5674](https://github.com/windmill-labs/windmill/issues/5674)) ([dfb947f](https://github.com/windmill-labs/windmill/commit/dfb947ff37c688f54a32de5aa3c5c3d142cb80f4)) +* Database Manager ([#5586](https://github.com/windmill-labs/windmill/issues/5586)) ([41c15fc](https://github.com/windmill-labs/windmill/commit/41c15fc78aaf844c559d3d6c772e04ecce436e9d)) +* Integrate MCP with hub ([#5685](https://github.com/windmill-labs/windmill/issues/5685)) ([ec701a9](https://github.com/windmill-labs/windmill/commit/ec701a9ee74c9d890b54234362392deca63a77c7)) + + +### Bug Fixes + +* Ai Chat: do not send tools if empty + respond even if tool fails ([#5692](https://github.com/windmill-labs/windmill/issues/5692)) ([9c55040](https://github.com/windmill-labs/windmill/commit/9c55040e47e76af8b7e2864b82fa30505545dcb5)) +* do not track relative deps for scripts with raw defined deps from CLI ([#5696](https://github.com/windmill-labs/windmill/issues/5696)) ([7eb9d7d](https://github.com/windmill-labs/windmill/commit/7eb9d7d46cb48ae69a3fd3ff852a57abae450a3b)) +* improve CLI file scanning performances ([0916978](https://github.com/windmill-labs/windmill/commit/09169784bd2d0ab7acf5f40dc86f36f1cae967b7)) + +## [1.485.3](https://github.com/windmill-labs/windmill/compare/v1.485.2...v1.485.3) (2025-04-29) + + +### Bug Fixes + +* improve performance of background cleanup monitoring operations ([18dced3](https://github.com/windmill-labs/windmill/commit/18dced3c748cd5305f0934b26e50d69899563723)) + +## [1.485.2](https://github.com/windmill-labs/windmill/compare/v1.485.1...v1.485.2) (2025-04-29) + + +### Bug Fixes + +* improve agent workers for deployed scripts ([60018aa](https://github.com/windmill-labs/windmill/commit/60018aadf62cecadf111e019d3600513a89810f1)) +* make `#(extra_)requirements:` work better with pins ([#5680](https://github.com/windmill-labs/windmill/issues/5680)) ([1ab4160](https://github.com/windmill-labs/windmill/commit/1ab41603f4fd1526d0c944396ef250b184aed1f4)) +* **python:** handle better relative imports with requirements or extra_requirements ([f662cf5](https://github.com/windmill-labs/windmill/commit/f662cf5d75beed8fd114ba171cbe0fa8e4b2773f)) + +## [1.485.1](https://github.com/windmill-labs/windmill/compare/v1.485.0...v1.485.1) (2025-04-28) + + +### Bug Fixes + +* improve mcp mode api ([cf77ff0](https://github.com/windmill-labs/windmill/commit/cf77ff088b8382b861113120589de58f7cf241d0)) +* MCP handle long names + invalid char in prop key + fix for not found resource type ([#5668](https://github.com/windmill-labs/windmill/issues/5668)) ([eadae95](https://github.com/windmill-labs/windmill/commit/eadae95a42d679bf8792bdefd8b9d19dbcbc4b57)) +* skip_flow_update for dependency tracking table ([#5670](https://github.com/windmill-labs/windmill/issues/5670)) ([35b69da](https://github.com/windmill-labs/windmill/commit/35b69da25c5bd17deff5a54b635e9150cb865cc0)) + +## [1.485.0](https://github.com/windmill-labs/windmill/compare/v1.484.0...v1.485.0) (2025-04-28) + + +### Features + +* add universal search to object viewer ([7254743](https://github.com/windmill-labs/windmill/commit/72547437fead0a071fceac27dae8628cdcae6a3e)) + + +### Bug Fixes + +* add svelte 5 boundaries to app components to contain errors ([1b16918](https://github.com/windmill-labs/windmill/commit/1b1691837a7e6b88afbacf7d88c14ca5e475b493)) +* Fix object handling on some MCP clients + better frontend for MCP ([#5663](https://github.com/windmill-labs/windmill/issues/5663)) ([12c3202](https://github.com/windmill-labs/windmill/commit/12c32026e5879a65fc0f1cc9f2481087c4b95111)) + +## [1.484.0](https://github.com/windmill-labs/windmill/compare/v1.483.2...v1.484.0) (2025-04-26) + + +### Features + +* Add MCP endpoints ([#5639](https://github.com/windmill-labs/windmill/issues/5639)) ([a34ac4f](https://github.com/windmill-labs/windmill/commit/a34ac4fa24c2a5482e45724e76316d57f64f7040)) +* Add MCP only mode ([#5661](https://github.com/windmill-labs/windmill/issues/5661)) ([1625524](https://github.com/windmill-labs/windmill/commit/162552431138d68002c7060cad4ae31f1ec4c69c)) +* Ansible improvements (vault, roles and git repos) ([#5655](https://github.com/windmill-labs/windmill/issues/5655)) ([fdd1642](https://github.com/windmill-labs/windmill/commit/fdd1642ce10866da1d8d373bda44f050e2e0f403)) + + +### Bug Fixes + +* check for valid teams_channel config when saving critical alerts settings ([#5660](https://github.com/windmill-labs/windmill/issues/5660)) ([dc5c8d8](https://github.com/windmill-labs/windmill/commit/dc5c8d8c5f8577b7ded3da1d684cdb735fa7a936)) +* Fix CI for MCP + optimization ([#5657](https://github.com/windmill-labs/windmill/issues/5657)) ([b199a77](https://github.com/windmill-labs/windmill/commit/b199a77d486c5bfd086ca73a58a14bb747e386b5)) +* fix token creation after mcp mode change to make it non workspace specific ([2b5dfcf](https://github.com/windmill-labs/windmill/commit/2b5dfcfb251471dcc39b04c54e25008d617cc34f)) +* improve full-scaleout of autoscaling event logging ([8435eb3](https://github.com/windmill-labs/windmill/commit/8435eb3adff8429a73db88b12204f7cf8f14d3d2)) +* improve skip failure on parallel branchall ([a7b2b51](https://github.com/windmill-labs/windmill/commit/a7b2b51444d757964560de3a89024b1c9b0fefe9)) + +## [1.483.2](https://github.com/windmill-labs/windmill/compare/v1.483.1...v1.483.2) (2025-04-23) + + +### Bug Fixes + +* batch reruns query missing workspace_id check in subquery ([#5652](https://github.com/windmill-labs/windmill/issues/5652)) ([444a6ab](https://github.com/windmill-labs/windmill/commit/444a6abad670114c52e44f3606bf6fefc5d3fd98)) +* **frontend:** fix validity check ([#5654](https://github.com/windmill-labs/windmill/issues/5654)) ([c41c1eb](https://github.com/windmill-labs/windmill/commit/c41c1eb587bf22364f1202310a0c64b6040ab968)) +* improve MySQL datetime parser timezone handling (WIN-1155) ([#5645](https://github.com/windmill-labs/windmill/issues/5645)) ([5bca8f6](https://github.com/windmill-labs/windmill/commit/5bca8f60e970cc67839edb5dc491685f36cf0499)) +* track relative imports in python and ts even if lockfile is provided ([e316dbd](https://github.com/windmill-labs/windmill/commit/e316dbd9bdd5c59e9aaba6a4472bb7d832834e84)) + +## [1.483.1](https://github.com/windmill-labs/windmill/compare/v1.483.0...v1.483.1) (2025-04-19) + + +### Bug Fixes + +* pin libxml to 0.3.3 ([e5595e4](https://github.com/windmill-labs/windmill/commit/e5595e41b5c87704d814eff95bc00b82195728ba)) + +## [1.483.0](https://github.com/windmill-labs/windmill/compare/v1.482.1...v1.483.0) (2025-04-19) + + +### Features + +* handle different aws auth resource type ([#5637](https://github.com/windmill-labs/windmill/issues/5637)) ([5b123b0](https://github.com/windmill-labs/windmill/commit/5b123b01a1318208450789b5bcade447a0b331c7)) +* oidc support for sqs trigger ([#5614](https://github.com/windmill-labs/windmill/issues/5614)) ([34b307b](https://github.com/windmill-labs/windmill/commit/34b307b2be1f6cf92a81694325f4c333bdd7b055)) + + +### Bug Fixes + +* fix click outside popover fullscreen ([#5631](https://github.com/windmill-labs/windmill/issues/5631)) ([0811457](https://github.com/windmill-labs/windmill/commit/081145726a5d4ab81510a7637126a036823a1565)) +* improve flow editor step switch performance ([58fa4c8](https://github.com/windmill-labs/windmill/commit/58fa4c80062a5704bbd13ddda1b2f00c7c9e40dd)) +* linter in early stop doesn't include flow_input ([#5638](https://github.com/windmill-labs/windmill/issues/5638)) ([6a9bdfd](https://github.com/windmill-labs/windmill/commit/6a9bdfd3bd52ff802b6a71c3ae9504bfe7d0421f)) +* output picker output opening doesn't change id ([#5641](https://github.com/windmill-labs/windmill/issues/5641)) ([64c72b6](https://github.com/windmill-labs/windmill/commit/64c72b6fce669e47f04dc620750840857cbe66cf)) + +## [1.482.1](https://github.com/windmill-labs/windmill/compare/v1.482.0...v1.482.1) (2025-04-16) + + +### Bug Fixes + +* flow editor workspace script test use actual workspace script hash ([24e893b](https://github.com/windmill-labs/windmill/commit/24e893b8c50fafdb41f4b6e1777cb34aceafc466)) +* **frontend:** postgres remove selectedTable ([#5386](https://github.com/windmill-labs/windmill/issues/5386)) ([bd7c6a2](https://github.com/windmill-labs/windmill/commit/bd7c6a2a46047de5fe89753decdfdf1f4851ee3f)) +* **openapi:** fix openapi def of batch re-run jobs ([cb8731e](https://github.com/windmill-labs/windmill/commit/cb8731e7e37fb6cd052f5dae6fdce46e6ca2409c)) +* show workspace color if superadmin and not in workspace + change workspace name when switching workspace ([#5625](https://github.com/windmill-labs/windmill/issues/5625)) ([cc4384f](https://github.com/windmill-labs/windmill/commit/cc4384f48cc89f883237a2082d854d69a7b5dc56)) + +## [1.482.0](https://github.com/windmill-labs/windmill/compare/v1.481.0...v1.482.0) (2025-04-15) + + +### Features + +* add diff toggle to flow inline scripts ([#5550](https://github.com/windmill-labs/windmill/issues/5550)) ([b3ecde3](https://github.com/windmill-labs/windmill/commit/b3ecde3316252bcd7323de98149786349019ba7e)) +* add gcp trigger ([#5501](https://github.com/windmill-labs/windmill/issues/5501)) ([6339775](https://github.com/windmill-labs/windmill/commit/63397754046eed41d32e28d4698db37b4c9b9710)) +* add wildcards filter for worker/label/tags ([62f14d1](https://github.com/windmill-labs/windmill/commit/62f14d1cb95e3f1c7de85c46e1c6bb092247656c)) +* add windmill context to autocomplete ([#5548](https://github.com/windmill-labs/windmill/issues/5548)) ([b47c151](https://github.com/windmill-labs/windmill/commit/b47c15165f93ca68a58f81cf2b86fc9467155482)) +* agent workers v2 using http ([#5588](https://github.com/windmill-labs/windmill/issues/5588)) ([63fa499](https://github.com/windmill-labs/windmill/commit/63fa4990153f33434b49269922f7803d04e407cd)) +* Batch re-run ([#5553](https://github.com/windmill-labs/windmill/issues/5553)) ([26b5ea5](https://github.com/windmill-labs/windmill/commit/26b5ea5023a100c57d077910c99a5e5703edf1c1)) +* **frontend:** app editor code input component (monaco) ([#5566](https://github.com/windmill-labs/windmill/issues/5566)) ([177e16b](https://github.com/windmill-labs/windmill/commit/177e16bb18eed0d1c454b967aaa59547f61e8d26)) +* handle sending selected lines to ai context ([#5527](https://github.com/windmill-labs/windmill/issues/5527)) ([5abdc3e](https://github.com/windmill-labs/windmill/commit/5abdc3e4403b5c604309bd99a24d7a2847a17b9b)) +* Implement sending diff to ai ([#5510](https://github.com/windmill-labs/windmill/issues/5510)) ([e118d2c](https://github.com/windmill-labs/windmill/commit/e118d2cd5f9c641884a76229802a5228ef41f1a5)) +* make azure a standalone AI provider ([#5558](https://github.com/windmill-labs/windmill/issues/5558)) ([2c5e58c](https://github.com/windmill-labs/windmill/commit/2c5e58cf1ab9225d516540b38d9e4dde482a3a7f)) +* migrate to svelte5 + vite6 ([#4813](https://github.com/windmill-labs/windmill/issues/4813)) ([3c99b3f](https://github.com/windmill-labs/windmill/commit/3c99b3fdc7b78b1cdc7d8fb21d999296695f7889)) +* **postgres-trigger:** postgres trigger fix circular dependencies and add remove associate resource ([#5606](https://github.com/windmill-labs/windmill/issues/5606)) ([1daeb2f](https://github.com/windmill-labs/windmill/commit/1daeb2f48f3026621b3ffc58e10f048d5911906c)) +* **python:** per import requirement pin ([#5520](https://github.com/windmill-labs/windmill/issues/5520)) ([0b6d017](https://github.com/windmill-labs/windmill/commit/0b6d017fedc31e790a76cf29a1adaaf2a72acc61)) +* signed s3 objects ([#5593](https://github.com/windmill-labs/windmill/issues/5593)) ([b9e8796](https://github.com/windmill-labs/windmill/commit/b9e879618bc223ce17effde8bb4c5d1df2ad6df5)) + + +### Bug Fixes + +* add support for ${} syntax without default in bash ([#5594](https://github.com/windmill-labs/windmill/issues/5594)) ([3950cfd](https://github.com/windmill-labs/windmill/commit/3950cfd7e3297d7f8ec56430d6462f6b67ecd3c2)) +* app editor svelte 5 fixes ([#5570](https://github.com/windmill-labs/windmill/issues/5570)) ([b926076](https://github.com/windmill-labs/windmill/commit/b9260769883348ecd5aeb5684f527a8bf0073928)) +* binding not working in nested array script arg ([#5585](https://github.com/windmill-labs/windmill/issues/5585)) ([f5d46d5](https://github.com/windmill-labs/windmill/commit/f5d46d5751bc875b7f4da1db06be40571ac55ab8)) +* **cli:** properly handle enabled/disabled updates of schedules ([2629458](https://github.com/windmill-labs/windmill/commit/26294584d6c2ca02bbc4fc5f28cb8df6a5fb3790)) +* **cli:** wmill-locks improvement ([8d062c4](https://github.com/windmill-labs/windmill/commit/8d062c47ecd9e84a81140d5c59814da9217dd434)) +* Dynamic select does not work with tag //native ([#5576](https://github.com/windmill-labs/windmill/issues/5576)) ([1f3e7d9](https://github.com/windmill-labs/windmill/commit/1f3e7d9029051832db6ab1755b3cad38176a9e96)), closes [#5490](https://github.com/windmill-labs/windmill/issues/5490) +* fix list jobs by tag ([0c3cb37](https://github.com/windmill-labs/windmill/commit/0c3cb3700a3fb9b69e396487bd7491dbbd8861c0)) +* flow editor svelte 5 issues ([#5567](https://github.com/windmill-labs/windmill/issues/5567)) ([4f6be6e](https://github.com/windmill-labs/windmill/commit/4f6be6ed340e26bf1ed95398a9dc9f1eb41b33dd)) +* freeze when clicking script history diff button ([#5581](https://github.com/windmill-labs/windmill/issues/5581)) ([07094b6](https://github.com/windmill-labs/windmill/commit/07094b6aa21f10688b138d2a81d4fd5833f003fc)) +* **frontend:** app builder - force json configuration in rich result ([#5565](https://github.com/windmill-labs/windmill/issues/5565)) ([6fae3a5](https://github.com/windmill-labs/windmill/commit/6fae3a566be06dae88ece8ec23f5723cd8f3f2b9)) +* **frontend:** load all step jobs ([#5617](https://github.com/windmill-labs/windmill/issues/5617)) ([16bed59](https://github.com/windmill-labs/windmill/commit/16bed593dfd0b735a92d0928df5091547b98ae79)) +* **frontend:** prevent deploy popover to show if deploy dropdown is open ([#5542](https://github.com/windmill-labs/windmill/issues/5542)) ([c2180c6](https://github.com/windmill-labs/windmill/commit/c2180c6eb34e14fe2292ff40aa6a99c627698d5e)) +* **frontend:** proper each block binding + better app settings reactivity ([#5568](https://github.com/windmill-labs/windmill/issues/5568)) ([4c71af8](https://github.com/windmill-labs/windmill/commit/4c71af8a74627d0ba76917e0dac0ac9e5e984cca)) +* improve app image picker UX ([#5589](https://github.com/windmill-labs/windmill/issues/5589)) ([f497a4b](https://github.com/windmill-labs/windmill/commit/f497a4bfae8d1bff097e0c2c9df8381a531dfeb9)) +* legacy script gen model selection ([#5574](https://github.com/windmill-labs/windmill/issues/5574)) ([3507925](https://github.com/windmill-labs/windmill/commit/3507925624a43804a3be463b6f7913cea5821384)) +* mssql ca_cert deserializing ([#5587](https://github.com/windmill-labs/windmill/issues/5587)) ([b4f8c88](https://github.com/windmill-labs/windmill/commit/b4f8c88c19bd4f844c3ecb53ececc340ee326b0e)) +* number input in app multiselect yields NOT_NUMBER ([#5616](https://github.com/windmill-labs/windmill/issues/5616)) ([4aae6ab](https://github.com/windmill-labs/windmill/commit/4aae6ab634280adc1de9abd890100b7c12c89158)) +* prevent invalid returned ai completion object errors ([#5564](https://github.com/windmill-labs/windmill/issues/5564)) ([9276c71](https://github.com/windmill-labs/windmill/commit/9276c717a21aaee3241845a9cc00d3fb6bce9eb9)) +* Remaining svelte 5 bugs ([#5563](https://github.com/windmill-labs/windmill/issues/5563)) ([6e9ec63](https://github.com/windmill-labs/windmill/commit/6e9ec6323c265a747ef8696865297e6d47abb016)) +* tenant id to never be undefined on teams ([#5572](https://github.com/windmill-labs/windmill/issues/5572)) ([102b58a](https://github.com/windmill-labs/windmill/commit/102b58a5f40dde22f15700d4b6c11eb7f3fbf4bb)) +* validate saved module before passing to flow module editor ([#5580](https://github.com/windmill-labs/windmill/issues/5580)) ([2eb1a16](https://github.com/windmill-labs/windmill/commit/2eb1a161d15627b440195b65eec54998561f4ef6)) + +## [1.481.0](https://github.com/windmill-labs/windmill/compare/v1.480.1...v1.481.0) (2025-04-02) + + +### Features + +* mssql support cert configuration ([#5559](https://github.com/windmill-labs/windmill/issues/5559)) ([e5519f7](https://github.com/windmill-labs/windmill/commit/e5519f79aaa83f04014364c7d1ec11157044011d)) + +## [1.480.1](https://github.com/windmill-labs/windmill/compare/v1.480.0...v1.480.1) (2025-04-02) + + +### Bug Fixes + +* aad_token can be empty string ([#5557](https://github.com/windmill-labs/windmill/issues/5557)) ([3fd7a5c](https://github.com/windmill-labs/windmill/commit/3fd7a5ce9c02332be40c34c0b6da57894b0b3d55)) +* improve workspace selection for default tag settings ([7083efd](https://github.com/windmill-labs/windmill/commit/7083efd051aeb7f653cccc97db099f4d9b2591a0)) +* mssql aad_token can be empty string ([#5556](https://github.com/windmill-labs/windmill/issues/5556)) ([dd30692](https://github.com/windmill-labs/windmill/commit/dd30692617e3cbc852239c4b1c50f975ff247c33)) + +## [1.480.0](https://github.com/windmill-labs/windmill/compare/v1.479.3...v1.480.0) (2025-03-31) + + +### Features + +* ms sql aad authentication support ([#5539](https://github.com/windmill-labs/windmill/issues/5539)) ([c230e2a](https://github.com/windmill-labs/windmill/commit/c230e2aed9b7fafb86548a4f4151939d5aca5127)) +* put db resources in ai context ([#5507](https://github.com/windmill-labs/windmill/issues/5507)) ([f7c8654](https://github.com/windmill-labs/windmill/commit/f7c86549879582c7f9dc72d52524f3a394f493f3)) + + +### Bug Fixes + +* correctly run empty flow with preprocessor from UI ([#5537](https://github.com/windmill-labs/windmill/issues/5537)) ([3d32501](https://github.com/windmill-labs/windmill/commit/3d3250194d43aee1a640a57505bc7a6afee62c84)) +* **frontend:** use custom caret position function ([#5544](https://github.com/windmill-labs/windmill/issues/5544)) ([ca0cda3](https://github.com/windmill-labs/windmill/commit/ca0cda3ecf5bd449f9c371cf5102c11d880c9822)) +* ignore invalid chunks in completion stream: empty choices when using azure ([#5545](https://github.com/windmill-labs/windmill/issues/5545)) ([b31090c](https://github.com/windmill-labs/windmill/commit/b31090cb544632680947492dc28f7b7c1a9c7287)) +* only format valid resource types ([#5541](https://github.com/windmill-labs/windmill/issues/5541)) ([113f038](https://github.com/windmill-labs/windmill/commit/113f038fc0e53e37c3bc319f85b3f7fa780c6fe5)) + +## [1.479.3](https://github.com/windmill-labs/windmill/compare/v1.479.2...v1.479.3) (2025-03-28) + + +### Bug Fixes + +* **cli:** pin encodeHex to 1.0.4 to work with dnt ([4703e3c](https://github.com/windmill-labs/windmill/commit/4703e3c848c9b06603b83885267023ccf84316c3)) + + +### Performance Improvements + +* improve hub resource type pulling when using the cli ([#5535](https://github.com/windmill-labs/windmill/issues/5535)) ([dd488a2](https://github.com/windmill-labs/windmill/commit/dd488a2bdbc0c9c7311c06dc25504a1336661cde)) + +## [1.479.2](https://github.com/windmill-labs/windmill/compare/v1.479.1...v1.479.2) (2025-03-28) + + +### Bug Fixes + +* fetch correct resource for interactive slack when multiple workspaces connected ([#5532](https://github.com/windmill-labs/windmill/issues/5532)) ([08e8283](https://github.com/windmill-labs/windmill/commit/08e8283c58c94f773936bac09d56bc6430382bbb)) + +## [1.479.1](https://github.com/windmill-labs/windmill/compare/v1.479.0...v1.479.1) (2025-03-27) + + +### Bug Fixes + +* pin backend deps half to 2.4.1 ([6cd2dc7](https://github.com/windmill-labs/windmill/commit/6cd2dc7178c62530f893d69f6e76b6cbc465e419)) + +## [1.479.0](https://github.com/windmill-labs/windmill/compare/v1.478.1...v1.479.0) (2025-03-27) + + +### Features + +* add description option to schedule page ([#5500](https://github.com/windmill-labs/windmill/issues/5500)) ([4c6f600](https://github.com/windmill-labs/windmill/commit/4c6f60010fec7d82181867e0082079e446797ce2)) +* add java support ([#5458](https://github.com/windmill-labs/windmill/issues/5458)) ([59740c0](https://github.com/windmill-labs/windmill/commit/59740c047816ad90d7383b15c846302db1a2e354)) +* add nu-lang support ([#5217](https://github.com/windmill-labs/windmill/issues/5217)) ([a3faea1](https://github.com/windmill-labs/windmill/commit/a3faea16e77796a1b989db4285b3fef722ac55b2)) +* api key/basic/hmac auth for http triggers ([#5476](https://github.com/windmill-labs/windmill/issues/5476)) ([e920101](https://github.com/windmill-labs/windmill/commit/e920101107256589bb5aee09fa8f04f5bd9707e4)) +* autocomplete v2 + AI chat ([#5323](https://github.com/windmill-labs/windmill/issues/5323)) ([234b20f](https://github.com/windmill-labs/windmill/commit/234b20f8bd55ea19b17b80f08d9ff1e0e00ba739)) +* github app token instead of pat for git sync ([#5279](https://github.com/windmill-labs/windmill/issues/5279)) ([b822c66](https://github.com/windmill-labs/windmill/commit/b822c66262f7c4c01ea4baad9383a12d138b0815)) +* list references upon renaming a script or a flow ([#5487](https://github.com/windmill-labs/windmill/issues/5487)) ([e868fe2](https://github.com/windmill-labs/windmill/commit/e868fe2bf5695b968151e27826854def3e847eb1)) +* make custom ai CE + add together AI provider ([#5522](https://github.com/windmill-labs/windmill/issues/5522)) ([a28c78d](https://github.com/windmill-labs/windmill/commit/a28c78dd920c695c3dfac05bc48c82f1477b022d)) +* **python:** fully qualified imports mapping ([#5511](https://github.com/windmill-labs/windmill/issues/5511)) ([1a5566b](https://github.com/windmill-labs/windmill/commit/1a5566b8c29773d94a681c86676d4cdb0b7c7777)) +* remove stripe dep ([#5508](https://github.com/windmill-labs/windmill/issues/5508)) ([7a62527](https://github.com/windmill-labs/windmill/commit/7a625275752ba69e26d7e3b41416e335496eff84)) +* unsafe parameters for sql queries (table names, column names) ([#5488](https://github.com/windmill-labs/windmill/issues/5488)) ([38ee018](https://github.com/windmill-labs/windmill/commit/38ee0183aaa014c740da7b54d66928ec851fb522)) + + +### Bug Fixes + +* add missing privileged hub script for app slack reports ([#5515](https://github.com/windmill-labs/windmill/issues/5515)) ([63fe9c1](https://github.com/windmill-labs/windmill/commit/63fe9c1852c1f87901f42eff8904c3482f7ceb43)) +* clean job dirs between flow locks ([8129672](https://github.com/windmill-labs/windmill/commit/8129672d9e8c6b591c1a46c30060a9d4f207e499)) +* **cli:** add --dry-run option ([4667507](https://github.com/windmill-labs/windmill/commit/466750752f6ffcb098cecd4ef6d6f33fb42d39ba)) +* correct private hub url in CLI for resource types sync ([#5513](https://github.com/windmill-labs/windmill/issues/5513)) ([9fd224c](https://github.com/windmill-labs/windmill/commit/9fd224cc469ae6f47c3ba9839ed43c85ff4d2181)) +* **frontend:** use stable path for capture tables + nits ([#5495](https://github.com/windmill-labs/windmill/issues/5495)) ([e16d629](https://github.com/windmill-labs/windmill/commit/e16d6299f52564def484e78fb2f48e9bf39cbd3d)) +* improve cancel for flows with many substeps ([ec11d57](https://github.com/windmill-labs/windmill/commit/ec11d577c6089df0b6019cd05064f5ea63fb317c)) + + +### Performance Improvements + +* cache workspace env variables to avoid one query ([#5499](https://github.com/windmill-labs/windmill/issues/5499)) ([a3f6db7](https://github.com/windmill-labs/windmill/commit/a3f6db7dca983a4dfd62b30423340f899c4d1da6)) +* cache workspace premium check ([5573d88](https://github.com/windmill-labs/windmill/commit/5573d886954182efcac71b3baa54d455f5086b30)) +* optimize number of queries needed for job run ([#5504](https://github.com/windmill-labs/windmill/issues/5504)) ([3edca4b](https://github.com/windmill-labs/windmill/commit/3edca4bc91ee9a1f1c0a98d39bc673dc56f899b6)) + +## [1.478.1](https://github.com/windmill-labs/windmill/compare/v1.478.0...v1.478.1) (2025-03-20) + + +### Bug Fixes + +* update deps versions ([0463c10](https://github.com/windmill-labs/windmill/commit/0463c10a84ab09f66b99c331d3860fa750606f51)) + +## [1.478.0](https://github.com/windmill-labs/windmill/compare/v1.477.1...v1.478.0) (2025-03-20) + + +### Features + +* add raw string option and wrap option for http trigger ([#5467](https://github.com/windmill-labs/windmill/issues/5467)) ([9dba57d](https://github.com/windmill-labs/windmill/commit/9dba57d546c984ff8cfb26c73d2ccdda4c18aaf3)) +* add support for python list[x] ([#5486](https://github.com/windmill-labs/windmill/issues/5486)) ([90ccc3a](https://github.com/windmill-labs/windmill/commit/90ccc3aae5f79e701e2c9241ce2cf009674ff356)) +* backend arg schema validation ([#5455](https://github.com/windmill-labs/windmill/issues/5455)) ([6634c82](https://github.com/windmill-labs/windmill/commit/6634c82e209a36021e5b0c392de433f48f3d8b80)) +* eager app mode ([fe20e33](https://github.com/windmill-labs/windmill/commit/fe20e3374f24fc644036a6d19e34421aeb839a73)) +* filter by worker + backend perf opt ([#5489](https://github.com/windmill-labs/windmill/issues/5489)) ([880db31](https://github.com/windmill-labs/windmill/commit/880db319e8e2479fdf12abcac526f7fd5064a00f)) +* keep captures across drafts and deploys ([#5482](https://github.com/windmill-labs/windmill/issues/5482)) ([4f43b19](https://github.com/windmill-labs/windmill/commit/4f43b1984f4ea9a87b0489d4b40c1ecdcfdbdecd)) + + +### Bug Fixes + +* avoid lock contention for native workers on cached connection ([#5481](https://github.com/windmill-labs/windmill/issues/5481)) ([8e95bc3](https://github.com/windmill-labs/windmill/commit/8e95bc397284607188f861f27288bcc0ab368023)) +* fix delete completed job ([ead1592](https://github.com/windmill-labs/windmill/commit/ead1592399d832039e3e866c554529dfd25a7af9)) +* fix empty schema on flow page error ([86121ed](https://github.com/windmill-labs/windmill/commit/86121ed4ab68ec17b9481b8d74bb2ccaae8c3b60)) +* improve concurrency limit check performances ([eee7d33](https://github.com/windmill-labs/windmill/commit/eee7d33bd8811be75d319956133bd8d6292aea90)) +* improve memory metrics graph ([a6cf327](https://github.com/windmill-labs/windmill/commit/a6cf327f74ae84d58181280995f8d8e2d909ee05)) +* improve row lock contention on concurrency counter ([e8bb307](https://github.com/windmill-labs/windmill/commit/e8bb3075020ca44978f503a81a7997ba1bcd671b)) +* label not part of default variant arg ([4bc5c04](https://github.com/windmill-labs/windmill/commit/4bc5c04cd40e23ef9d13ba48d28612d5a865796e)) +* set proper slot for MobileFitlers popover ([#5491](https://github.com/windmill-labs/windmill/issues/5491)) ([6b4c25d](https://github.com/windmill-labs/windmill/commit/6b4c25d0d808a841dbfeaf91d29437071128266a)) + + +### Performance Improvements + +* improve perf of get completed flow node ([#5418](https://github.com/windmill-labs/windmill/issues/5418)) ([551c0ec](https://github.com/windmill-labs/windmill/commit/551c0ecd6a83671d60ede5a81f656c27ddbdbe4c)) + +## [1.477.1](https://github.com/windmill-labs/windmill/compare/v1.477.0...v1.477.1) (2025-03-13) + + +### Bug Fixes + +* fix rusttls panic ([6a6b760](https://github.com/windmill-labs/windmill/commit/6a6b760e321fae949a02c1b0e0c32b0beaa8693b)) + +## [1.477.0](https://github.com/windmill-labs/windmill/compare/v1.476.0...v1.477.0) (2025-03-12) + + +### Features + +* add search by args on input history directly ([593dc30](https://github.com/windmill-labs/windmill/commit/593dc30bc81ab407bd119963a6befaa4fbc16eae)) + + +### Bug Fixes + +* add setValue support for tables ([ec52476](https://github.com/windmill-labs/windmill/commit/ec5247645d425a35b5adf0aaed40713d08439b11)) +* improve oneOf arg input reactivity to value changes ([a695621](https://github.com/windmill-labs/windmill/commit/a6956215eca8d1180b3c999519f9fa2ef43b5ab0)) +* pg_listeners have no timeout ([52f55ff](https://github.com/windmill-labs/windmill/commit/52f55ff1f11adf9157ca0f0fe356fa17d65ea20a)) +* prevent monitoring task to die without sending killpill ([#5472](https://github.com/windmill-labs/windmill/issues/5472)) ([d58ca9b](https://github.com/windmill-labs/windmill/commit/d58ca9b395cb151b05c43d910b0081988b3291ae)) +* tutorial's step 6 not working (button.click is not a function) ([#5474](https://github.com/windmill-labs/windmill/issues/5474)) ([00e1841](https://github.com/windmill-labs/windmill/commit/00e18419f5db8ef19ad92c6ac290812084cd1ecd)) +* update bun to 1.2.4 ([8e0963e](https://github.com/windmill-labs/windmill/commit/8e0963eec8a86b6d8593995c803dfbdd2c96bfc1)) + +## [1.476.0](https://github.com/windmill-labs/windmill/compare/v1.475.1...v1.476.0) (2025-03-11) + + +### Features + +* option to prefix http route with workspace id ([#5461](https://github.com/windmill-labs/windmill/issues/5461)) ([61a5cea](https://github.com/windmill-labs/windmill/commit/61a5ceaba38787dc146a36b443bbd3f78e26102b)) + + +### Bug Fixes + +* cache for querying scripts correclty handles ScriptMetadata ([#5466](https://github.com/windmill-labs/windmill/issues/5466)) ([6dd2502](https://github.com/windmill-labs/windmill/commit/6dd2502d70dffcadee4427164db02607cd109c61)) +* codebases compatible with git sync ([#5470](https://github.com/windmill-labs/windmill/issues/5470)) ([bd7586a](https://github.com/windmill-labs/windmill/commit/bd7586a5eec5516fe291070303fa6516d8adc8de)) + +## [1.475.1](https://github.com/windmill-labs/windmill/compare/v1.475.0...v1.475.1) (2025-03-11) + + +### Bug Fixes + +* improve arginput sql and object viewer args change ([2a8a756](https://github.com/windmill-labs/windmill/commit/2a8a756b3f0a0e69145421eee87251956d85403b)) +* improve flow status viewer iteration picker behavior with very large forloops ([78d9664](https://github.com/windmill-labs/windmill/commit/78d9664ad89212196ef32c0a02114092331bfe63)) + ## [1.475.0](https://github.com/windmill-labs/windmill/compare/v1.474.0...v1.475.0) (2025-03-06) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..f61e0336be --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,10 @@ +# Windmill Development Guide + +## Overview + +Windmill is an open-source developer platform for building internal tools, workflows, API integrations, background jobs, workflows, and user interfaces. See @windmill-overview.mdc for full platform details. + +## Language-Specific Guides + +- Backend (Rust): @backend/rust-best-practices.mdc + @backend/summarized_schema.txt +- Frontend (Svelte 5): @frontend/svelte5-best-practices.mdc diff --git a/Caddyfile b/Caddyfile index 67925e6492..933407b98d 100644 --- a/Caddyfile +++ b/Caddyfile @@ -12,7 +12,7 @@ bind {$ADDRESS} reverse_proxy /ws/* http://lsp:3001 # reverse_proxy /ws_mp/* http://multiplayer:3002 - # reverse_proxy /api/srch/* http://windmill_indexer:8001 + # reverse_proxy /api/srch/* http://windmill_indexer:8002 reverse_proxy /* http://windmill_server:8000 # tls /certs/cert.pem /certs/key.pem } diff --git a/Dockerfile b/Dockerfile index 0f5fe5db26..16b3a312ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG DEBIAN_IMAGE=debian:bookworm-slim -ARG RUST_IMAGE=rust:1.85-slim-bookworm +ARG RUST_IMAGE=rust:1.88-slim-bookworm FROM ${RUST_IMAGE} AS rust_base @@ -25,6 +25,7 @@ FROM node:20-alpine as frontend # install dependencies WORKDIR /frontend COPY ./frontend/package.json ./frontend/package-lock.json ./ +COPY ./frontend/scripts/ ./scripts/ RUN npm ci # Copy all local files into the image. @@ -41,6 +42,8 @@ COPY /typescript-client/docs/ /frontend/static/tsdocs/ RUN npm run generate-backend-client ENV NODE_OPTIONS "--max-old-space-size=8192" ARG VITE_BASE_URL "" +# Read more about macro in docker/dev.nu +# -- MACRO-SPREAD-WASM-PARSER-DEV-ONLY -- # RUN npm run build @@ -83,8 +86,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ FROM ${DEBIAN_IMAGE} ARG TARGETPLATFORM -ARG POWERSHELL_VERSION=7.3.5 -ARG POWERSHELL_DEB_VERSION=7.3.5-1 +ARG POWERSHELL_VERSION=7.5.0 +ARG POWERSHELL_DEB_VERSION=7.5.0-1 ARG KUBECTL_VERSION=1.28.7 ARG HELM_VERSION=3.14.3 ARG GO_VERSION=1.22.5 @@ -191,7 +194,7 @@ COPY --from=builder /windmill/target/release/windmill ${APP}/windmill COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno -COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.2.4 /usr/local/bin/bun /usr/bin/bun COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer @@ -219,9 +222,10 @@ RUN cp -r /root/.cache /home/windmill/.cache RUN mkdir -p /tmp/windmill/logs && \ mkdir -p /tmp/windmill/search -RUN chown -R windmill:windmill ${APP} && \ - chown -R windmill:windmill /tmp/windmill && \ - chown -R windmill:windmill /home/windmill/.cache +# Make directories world-readable and writable +RUN chmod -R 777 ${APP} && \ + chmod -R 777 /tmp/windmill && \ + chmod -R 777 /home/windmill/.cache USER root diff --git a/README.md b/README.md index e2c9a27aa6..c86977bf72 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ you to have it being synced automatically everyday. ## Environment Variables | Environment Variable name | Default | Description | Api Server/Worker/All | -| ----------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | --- | ------ | +| ----------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | DATABASE_URL | | The Postgres database url. | All | | WORKER_GROUP | default | The worker group the worker belongs to and get its configuration pulled from | Worker | | MODE | standalone | The mode if the binary. Possible values: standalone, worker, server, agent | All | @@ -352,7 +352,7 @@ you to have it being synced automatically everyday. | GO_PATH | /usr/bin/go | The path to the go binary. | Worker | | GOPRIVATE | | The GOPRIVATE env variable to use private go modules | Worker | | GOPROXY | | The GOPROXY env variable to use | Worker | -| NETRC | | The netrc content to use a private go registry | Worker | | Worker | +| NETRC | | The netrc content to use a private go registry | Worker | | PY_CONCURRENT_DOWNLOADS | 20 | Sets the maximum number of in-flight concurrent python downloads that windmill will perform at any given time. | Worker | | PATH | None | The path environment variable, usually inherited | Worker | | HOME | None | The home directory to use for Go and Bash , usually inherited | Worker | @@ -363,13 +363,15 @@ you to have it being synced automatically everyday. | DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker | | DISABLE_RESPONSE_LOGS | false | Disable response logs | Server | | CREATE_WORKSPACE_REQUIRE_SUPERADMIN | true | If true, only superadmins can create new workspaces | Server | +| MIN_FREE_DISK_SPACE_MB | 15000 | Minimum amount of free space on worker. Sends critical alert if worker has less free space. | Worker | ## Run a local dev setup +Using [Nix](./frontend/README_DEV.md#nix) (Recommended). + See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all running options. -Using [Nix](./frontend/README_DEV.md#nix). ### only Frontend diff --git a/backend/.gitignore b/backend/.gitignore index 4ca669fc53..2a3262acac 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -5,4 +5,6 @@ oauth2.json tracing.folded heaptrack* index/ -windmill-api/openapi-*.* \ No newline at end of file +windmill-api/openapi-*.* +.duckdb/* +*ee.rs \ No newline at end of file diff --git a/backend/.ignore b/backend/.ignore new file mode 100644 index 0000000000..3173520127 --- /dev/null +++ b/backend/.ignore @@ -0,0 +1 @@ +!*ee.rs \ No newline at end of file diff --git a/backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json b/backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json similarity index 69% rename from backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json rename to backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json index 3adc1f8cd5..a3ec5fbabe 100644 --- a/backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json +++ b/backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval \n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ", + "query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ", "describe": { "columns": [ { @@ -36,5 +36,5 @@ null ] }, - "hash": "f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45" + "hash": "00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f" } diff --git a/backend/.sqlx/query-01755585cd3f6e100a66da331720286cbc09d4abf2926146b24a8c95cf21e5c8.json b/backend/.sqlx/query-01755585cd3f6e100a66da331720286cbc09d4abf2926146b24a8c95cf21e5c8.json new file mode 100644 index 0000000000..8c1ba3782c --- /dev/null +++ b/backend/.sqlx/query-01755585cd3f6e100a66da331720286cbc09d4abf2926146b24a8c95cf21e5c8.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Bool", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "01755585cd3f6e100a66da331720286cbc09d4abf2926146b24a8c95cf21e5c8" +} diff --git a/backend/.sqlx/query-3c0b2a840102b12864c5d721b8e0142602ab37f3e1a95d39b3c7cbd7ff34d0b2.json b/backend/.sqlx/query-019100d178129340a7c35d60ab61f983c8a9cb810db4369554bf26c6b0d6003d.json similarity index 61% rename from backend/.sqlx/query-3c0b2a840102b12864c5d721b8e0142602ab37f3e1a95d39b3c7cbd7ff34d0b2.json rename to backend/.sqlx/query-019100d178129340a7c35d60ab61f983c8a9cb810db4369554bf26c6b0d6003d.json index dbb99a4ec1..9e97808b41 100644 --- a/backend/.sqlx/query-3c0b2a840102b12864c5d721b8e0142602ab37f3e1a95d39b3c7cbd7ff34d0b2.json +++ b/backend/.sqlx/query-019100d178129340a7c35d60ab61f983c8a9cb810db4369554bf26c6b0d6003d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n id As \"id!\",\n flow_status->'restarted_from'->'flow_job_id' AS \"restarted_from: Json\"\n FROM v2_as_queue\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $1), $1) = id AND workspace_id = $2", + "query": "SELECT\n id As \"id!\",\n flow_status->'restarted_from'->'flow_job_id' AS \"restarted_from: Json\"\n FROM v2_job_status\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $1), $1) = id", "describe": { "columns": [ { @@ -16,14 +16,13 @@ ], "parameters": { "Left": [ - "Uuid", - "Text" + "Uuid" ] }, "nullable": [ - true, + false, null ] }, - "hash": "3c0b2a840102b12864c5d721b8e0142602ab37f3e1a95d39b3c7cbd7ff34d0b2" + "hash": "019100d178129340a7c35d60ab61f983c8a9cb810db4369554bf26c6b0d6003d" } diff --git a/backend/.sqlx/query-33351de09c72ccc0a39eb977d26f867595813bfa1ae0b26bc4181780801294bf.json b/backend/.sqlx/query-05c65ba8a56b3b5f8bd37c30c0c6707522e01c4a05104969889b7bb41d6aa509.json similarity index 56% rename from backend/.sqlx/query-33351de09c72ccc0a39eb977d26f867595813bfa1ae0b26bc4181780801294bf.json rename to backend/.sqlx/query-05c65ba8a56b3b5f8bd37c30c0c6707522e01c4a05104969889b7bb41d6aa509.json index a3d39e0b59..3a4f5e1b82 100644 --- a/backend/.sqlx/query-33351de09c72ccc0a39eb977d26f867595813bfa1ae0b26bc4181780801294bf.json +++ b/backend/.sqlx/query-05c65ba8a56b3b5f8bd37c30c0c6707522e01c4a05104969889b7bb41d6aa509.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT success AS \"success!\" FROM v2_as_completed_job WHERE id = ANY($1)", + "query": "SELECT status = 'success' OR status = 'skipped' AS \"success!\" FROM v2_job_completed WHERE id = ANY($1)", "describe": { "columns": [ { @@ -15,8 +15,8 @@ ] }, "nullable": [ - true + null ] }, - "hash": "33351de09c72ccc0a39eb977d26f867595813bfa1ae0b26bc4181780801294bf" + "hash": "05c65ba8a56b3b5f8bd37c30c0c6707522e01c4a05104969889b7bb41d6aa509" } diff --git a/backend/.sqlx/query-05cb171b610bfb45f6228128a385cde8a5b86d7ca377a028004cc382e12faf41.json b/backend/.sqlx/query-05cb171b610bfb45f6228128a385cde8a5b86d7ca377a028004cc382e12faf41.json new file mode 100644 index 0000000000..5290fab4c4 --- /dev/null +++ b/backend/.sqlx/query-05cb171b610bfb45f6228128a385cde8a5b86d7ca377a028004cc382e12faf41.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) \n VALUES ($1, '{}'::jsonb)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "05cb171b610bfb45f6228128a385cde8a5b86d7ca377a028004cc382e12faf41" +} diff --git a/backend/.sqlx/query-06731936fb073169b3a1a8a9817f1e669b60edccd260625a95094b7244f5fb83.json b/backend/.sqlx/query-06731936fb073169b3a1a8a9817f1e669b60edccd260625a95094b7244f5fb83.json deleted file mode 100644 index c509986552..0000000000 --- a/backend/.sqlx/query-06731936fb073169b3a1a8a9817f1e669b60edccd260625a95094b7244f5fb83.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['retry'], $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "06731936fb073169b3a1a8a9817f1e669b60edccd260625a95094b7244f5fb83" -} diff --git a/backend/.sqlx/query-ceda377fa534656ac12a7f41db77db1f054ec751855c8db7352b0e9c20b63ef8.json b/backend/.sqlx/query-0689cdc6c7676f5e1984792a0e0b172ea9a70835bfba6cef56142556197e9767.json similarity index 56% rename from backend/.sqlx/query-ceda377fa534656ac12a7f41db77db1f054ec751855c8db7352b0e9c20b63ef8.json rename to backend/.sqlx/query-0689cdc6c7676f5e1984792a0e0b172ea9a70835bfba6cef56142556197e9767.json index a309a03762..27c1d1d2d8 100644 --- a/backend/.sqlx/query-ceda377fa534656ac12a7f41db77db1f054ec751855c8db7352b0e9c20b63ef8.json +++ b/backend/.sqlx/query-0689cdc6c7676f5e1984792a0e0b172ea9a70835bfba6cef56142556197e9767.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1", + "query": "SELECT ai_config FROM workspace_settings WHERE workspace_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "ai_resource", + "name": "ai_config", "type_info": "Jsonb" } ], @@ -18,5 +18,5 @@ true ] }, - "hash": "ceda377fa534656ac12a7f41db77db1f054ec751855c8db7352b0e9c20b63ef8" + "hash": "0689cdc6c7676f5e1984792a0e0b172ea9a70835bfba6cef56142556197e9767" } diff --git a/backend/.sqlx/query-06db0e720dd59a7c52c0a98ea7b316237eb1a547678858c1a1e45985035b3468.json b/backend/.sqlx/query-06db0e720dd59a7c52c0a98ea7b316237eb1a547678858c1a1e45985035b3468.json new file mode 100644 index 0000000000..aea8b8302b --- /dev/null +++ b/backend/.sqlx/query-06db0e720dd59a7c52c0a98ea7b316237eb1a547678858c1a1e45985035b3468.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = flow_status - 'retry'\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "06db0e720dd59a7c52c0a98ea7b316237eb1a547678858c1a1e45985035b3468" +} diff --git a/backend/.sqlx/query-070b8ad0b59f485fa5bf68082b060f5c3561c37e9c6f2834d234a862a475a6eb.json b/backend/.sqlx/query-070b8ad0b59f485fa5bf68082b060f5c3561c37e9c6f2834d234a862a475a6eb.json new file mode 100644 index 0000000000..280db517dd --- /dev/null +++ b/backend/.sqlx/query-070b8ad0b59f485fa5bf68082b060f5c3561c37e9c6f2834d234a862a475a6eb.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger \n SET \n enabled = $1, \n email = $2, \n edited_by = $3, \n edited_at = now(), \n server_id = NULL, \n error = NULL\n WHERE \n path = $4 AND \n workspace_id = $5 \n RETURNING 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Bool", + "Varchar", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "070b8ad0b59f485fa5bf68082b060f5c3561c37e9c6f2834d234a862a475a6eb" +} diff --git a/backend/.sqlx/query-6c90d5ea2a09b47b81fdab465062c25f8768b220e53bf469550a3a3697ab756a.json b/backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json similarity index 52% rename from backend/.sqlx/query-6c90d5ea2a09b47b81fdab465062c25f8768b220e53bf469550a3a3697ab756a.json rename to backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json index fb5e174ced..11c3c200c0 100644 --- a/backend/.sqlx/query-6c90d5ea2a09b47b81fdab465062c25f8768b220e53bf469550a3a3697ab756a.json +++ b/backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT set_config('session.user', $1, true)", + "query": "SELECT COUNT(*) FROM app WHERE workspace_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "set_config", - "type_info": "Text" + "name": "count", + "type_info": "Int8" } ], "parameters": { @@ -18,5 +18,5 @@ null ] }, - "hash": "6c90d5ea2a09b47b81fdab465062c25f8768b220e53bf469550a3a3697ab756a" + "hash": "08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb" } diff --git a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json index 4bcf3c6ce3..388fd55418 100644 --- a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json +++ b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json @@ -65,7 +65,7 @@ }, { "ordinal": 12, - "name": "ai_resource", + "name": "ai_config", "type_info": "Jsonb" }, { @@ -100,58 +100,48 @@ }, { "ordinal": 19, - "name": "automatic_billing", - "type_info": "Bool" - }, - { - "ordinal": 20, "name": "default_scripts", "type_info": "Jsonb" }, { - "ordinal": 21, + "ordinal": 20, "name": "deploy_ui", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 21, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 23, + "ordinal": 22, "name": "color", "type_info": "Varchar" }, { - "ordinal": 24, + "ordinal": 23, "name": "operator_settings", "type_info": "Jsonb" }, { - "ordinal": 25, - "name": "ai_models", - "type_info": "VarcharArray" - }, - { - "ordinal": 26, - "name": "code_completion_model", - "type_info": "Varchar" - }, - { - "ordinal": 27, + "ordinal": 24, "name": "teams_command_script", "type_info": "Text" }, { - "ordinal": 28, + "ordinal": 25, "name": "teams_team_id", "type_info": "Text" }, { - "ordinal": 29, + "ordinal": 26, "name": "teams_team_name", "type_info": "Text" + }, + { + "ordinal": 27, + "name": "git_app_installations", + "type_info": "Jsonb" } ], "parameters": { @@ -179,17 +169,15 @@ true, true, true, - false, true, true, true, true, true, - false, true, true, true, - true + false ] }, "hash": "08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7" diff --git a/backend/.sqlx/query-89de3ff8ab32e545efcbcda05f994cb1a32c4991cbd25046282d34272587d2de.json b/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json similarity index 86% rename from backend/.sqlx/query-89de3ff8ab32e545efcbcda05f994cb1a32c4991cbd25046282d34272587d2de.json rename to backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json index d90a9646de..152ae38fdc 100644 --- a/backend/.sqlx/query-89de3ff8ab32e545efcbcda05f994cb1a32c4991cbd25046282d34272587d2de.json +++ b/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)", + "query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by, path from script where hash = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -59,7 +59,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } @@ -99,11 +102,16 @@ "ordinal": 13, "name": "created_by", "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "path", + "type_info": "Varchar" } ], "parameters": { "Left": [ - "Text", + "Int8", "Text" ] }, @@ -121,8 +129,9 @@ true, true, true, + false, false ] }, - "hash": "89de3ff8ab32e545efcbcda05f994cb1a32c4991cbd25046282d34272587d2de" + "hash": "0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936" } diff --git a/backend/.sqlx/query-09efbd7177c6172569dc29b7d9ede70315eeb4e0ef9ed3165365f257e27f5e68.json b/backend/.sqlx/query-09efbd7177c6172569dc29b7d9ede70315eeb4e0ef9ed3165365f257e27f5e68.json new file mode 100644 index 0000000000..4500681506 --- /dev/null +++ b/backend/.sqlx/query-09efbd7177c6172569dc29b7d9ede70315eeb4e0ef9ed3165365f257e27f5e68.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "09efbd7177c6172569dc29b7d9ede70315eeb4e0ef9ed3165365f257e27f5e68" +} diff --git a/backend/.sqlx/query-0aa8f50fe377a23e2ae3821fd6eda4cbe80b0150827c6e90f6e4f9e512587ba1.json b/backend/.sqlx/query-0aa8f50fe377a23e2ae3821fd6eda4cbe80b0150827c6e90f6e4f9e512587ba1.json deleted file mode 100644 index d48b865a92..0000000000 --- a/backend/.sqlx/query-0aa8f50fe377a23e2ae3821fd6eda4cbe80b0150827c6e90f6e4f9e512587ba1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_queue SET started_at = NOW() WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "0aa8f50fe377a23e2ae3821fd6eda4cbe80b0150827c6e90f6e4f9e512587ba1" -} diff --git a/backend/.sqlx/query-0aaec91ab06753e46c595d82469924a98f28b0dead245df7248a9ccb8a5f20c3.json b/backend/.sqlx/query-0aaec91ab06753e46c595d82469924a98f28b0dead245df7248a9ccb8a5f20c3.json new file mode 100644 index 0000000000..04f5ba5b84 --- /dev/null +++ b/backend/.sqlx/query-0aaec91ab06753e46c595d82469924a98f28b0dead245df7248a9ccb8a5f20c3.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "0aaec91ab06753e46c595d82469924a98f28b0dead245df7248a9ccb8a5f20c3" +} diff --git a/backend/.sqlx/query-63b5f03741be97d0e8763dd070649ebb6ec02aa083d7e175c1a02a38935a4024.json b/backend/.sqlx/query-0d8153986cea6166820f601f80d8e67156408b08360d628300b28221ea995a58.json similarity index 52% rename from backend/.sqlx/query-63b5f03741be97d0e8763dd070649ebb6ec02aa083d7e175c1a02a38935a4024.json rename to backend/.sqlx/query-0d8153986cea6166820f601f80d8e67156408b08360d628300b28221ea995a58.json index 4d914355db..1d2f993f5d 100644 --- a/backend/.sqlx/query-63b5f03741be97d0e8763dd070649ebb6ec02aa083d7e175c1a02a38935a4024.json +++ b/backend/.sqlx/query-0d8153986cea6166820f601f80d8e67156408b08360d628300b28221ea995a58.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND http_method = $2 AND ($3::TEXT IS NULL OR path != $3))", + "query": "\n SELECT EXISTS(\n SELECT 1 \n FROM http_trigger \n WHERE \n ((workspaced_route IS TRUE AND workspace_id || '/' || route_path_key = $1) \n OR (workspaced_route IS FALSE AND route_path_key = $1))\n AND http_method = $2 \n AND ($3::TEXT IS NULL OR path != $3)\n )\n ", "describe": { "columns": [ { @@ -33,5 +33,5 @@ null ] }, - "hash": "63b5f03741be97d0e8763dd070649ebb6ec02aa083d7e175c1a02a38935a4024" + "hash": "0d8153986cea6166820f601f80d8e67156408b08360d628300b28221ea995a58" } diff --git a/backend/.sqlx/query-0e52a588f3edeb8fb58d6d62247b8590e51171e2811c62737bdb81fb0ac8f182.json b/backend/.sqlx/query-0e52a588f3edeb8fb58d6d62247b8590e51171e2811c62737bdb81fb0ac8f182.json deleted file mode 100644 index f23cf3f710..0000000000 --- a/backend/.sqlx/query-0e52a588f3edeb8fb58d6d62247b8590e51171e2811c62737bdb81fb0ac8f182.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE worker_ping SET \n ping_at = now(), \n jobs_executed = 1, \n current_job_id = $1, \n current_job_workspace_id = 'admins' \n WHERE worker = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0e52a588f3edeb8fb58d6d62247b8590e51171e2811c62737bdb81fb0ac8f182" -} diff --git a/backend/.sqlx/query-0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8.json b/backend/.sqlx/query-0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8.json new file mode 100644 index 0000000000..ac882aec64 --- /dev/null +++ b/backend/.sqlx/query-0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'account_id' as account_id\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "installation_id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "account_id", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8" +} diff --git a/backend/.sqlx/query-103ef3cf5cf4d25d780e4aefd5b290d810a5e8ea6458d9f9fd484ced549ea82e.json b/backend/.sqlx/query-103ef3cf5cf4d25d780e4aefd5b290d810a5e8ea6458d9f9fd484ced549ea82e.json deleted file mode 100644 index 15d772ab16..0000000000 --- a/backend/.sqlx/query-103ef3cf5cf4d25d780e4aefd5b290d810a5e8ea6458d9f9fd484ced549ea82e.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT value, resource_type FROM resource WHERE path = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "value", - "type_info": "Jsonb" - }, - { - "ordinal": 1, - "name": "resource_type", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - true, - false - ] - }, - "hash": "103ef3cf5cf4d25d780e4aefd5b290d810a5e8ea6458d9f9fd484ced549ea82e" -} diff --git a/backend/.sqlx/query-1060c503cf8d4bb5cef9720c162b8192924b4a938d249fae92624cd55e44f488.json b/backend/.sqlx/query-1060c503cf8d4bb5cef9720c162b8192924b4a938d249fae92624cd55e44f488.json new file mode 100644 index 0000000000..39b7179e5c --- /dev/null +++ b/backend/.sqlx/query-1060c503cf8d4bb5cef9720c162b8192924b4a938d249fae92624cd55e44f488.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT],\n $4\n )\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Text", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "1060c503cf8d4bb5cef9720c162b8192924b4a938d249fae92624cd55e44f488" +} diff --git a/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json b/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json deleted file mode 100644 index 767f5cba57..0000000000 --- a/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f" -} diff --git a/backend/.sqlx/query-1252ef3a652ffb99529c2ce84928197fa15efb9c78d68e3a191c01a04efe153f.json b/backend/.sqlx/query-1252ef3a652ffb99529c2ce84928197fa15efb9c78d68e3a191c01a04efe153f.json deleted file mode 100644 index d3d6dd84cf..0000000000 --- a/backend/.sqlx/query-1252ef3a652ffb99529c2ce84928197fa15efb9c78d68e3a191c01a04efe153f.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'iterator', 'index'],\n ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "int4", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Uuid", - "Text", - "Jsonb" - ] - }, - "nullable": [ - null - ] - }, - "hash": "1252ef3a652ffb99529c2ce84928197fa15efb9c78d68e3a191c01a04efe153f" -} diff --git a/backend/.sqlx/query-1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d.json b/backend/.sqlx/query-1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d.json new file mode 100644 index 0000000000..582896cc65 --- /dev/null +++ b/backend/.sqlx/query-1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d.json @@ -0,0 +1,145 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "gcp_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "subscription_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "topic_id", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "delivery_type: _", + "type_info": { + "Custom": { + "name": "delivery_mode", + "kind": { + "Enum": [ + "push", + "pull" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "delivery_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "subscription_mode: _", + "type_info": { + "Custom": { + "name": "gcp_subscription_mode", + "kind": { + "Enum": [ + "create_update", + "existing" + ] + } + } + } + }, + { + "ordinal": 7, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 13, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + true, + false + ] + }, + "hash": "1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d" +} diff --git a/backend/.sqlx/query-38a3fbc28e827d08a928d441274c5eb28780abc8adffcc7175f6c8d4ff8849ca.json b/backend/.sqlx/query-13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf.json similarity index 54% rename from backend/.sqlx/query-38a3fbc28e827d08a928d441274c5eb28780abc8adffcc7175f6c8d4ff8849ca.json rename to backend/.sqlx/query-13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf.json index a3929e5e4b..6b8e275167 100644 --- a/backend/.sqlx/query-38a3fbc28e827d08a928d441274c5eb28780abc8adffcc7175f6c8d4ff8849ca.json +++ b/backend/.sqlx/query-13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1 RETURNING (SELECT COUNT(*) FROM jsonb_object_keys(job_uuids))", + "query": "SELECT COUNT(*) FROM sqs_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", "describe": { "columns": [ { @@ -12,6 +12,7 @@ "parameters": { "Left": [ "Text", + "Bool", "Text" ] }, @@ -19,5 +20,5 @@ null ] }, - "hash": "38a3fbc28e827d08a928d441274c5eb28780abc8adffcc7175f6c8d4ff8849ca" + "hash": "13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf" } diff --git a/backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json b/backend/.sqlx/query-16be720bf1c88ecfa2a4bf6adbb1924df4817b6236b3a949209465f3a2c42bb9.json similarity index 50% rename from backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json rename to backend/.sqlx/query-16be720bf1c88ecfa2a4bf6adbb1924df4817b6236b3a949209465f3a2c42bb9.json index da7d13f71c..7e55cc5446 100644 --- a/backend/.sqlx/query-2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48.json +++ b/backend/.sqlx/query-16be720bf1c88ecfa2a4bf6adbb1924df4817b6236b3a949209465f3a2c42bb9.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SHOW WAL_LEVEL;", + "query": "SELECT nextval('http_trigger_version_seq')", "describe": { "columns": [ { "ordinal": 0, - "name": "wal_level", - "type_info": "Text" + "name": "nextval", + "type_info": "Int8" } ], "parameters": { @@ -16,5 +16,5 @@ null ] }, - "hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48" + "hash": "16be720bf1c88ecfa2a4bf6adbb1924df4817b6236b3a949209465f3a2c42bb9" } diff --git a/backend/.sqlx/query-171b11d66b9ec6cb7b0dd74929e233389683f8d510850487453052a317391f0f.json b/backend/.sqlx/query-171b11d66b9ec6cb7b0dd74929e233389683f8d510850487453052a317391f0f.json new file mode 100644 index 0000000000..b8f2ccca5f --- /dev/null +++ b/backend/.sqlx/query-171b11d66b9ec6cb7b0dd74929e233389683f8d510850487453052a317391f0f.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT a.path\n FROM workspace_runnable_dependencies wru \n JOIN app a\n ON wru.app_path = a.path AND wru.workspace_id = a.workspace_id\n WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Bool", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "171b11d66b9ec6cb7b0dd74929e233389683f8d510850487453052a317391f0f" +} diff --git a/backend/.sqlx/query-173fbfd3ca2344fd08f73af75524c917d27fdb6273a35a563292b1f0701dc6ed.json b/backend/.sqlx/query-173fbfd3ca2344fd08f73af75524c917d27fdb6273a35a563292b1f0701dc6ed.json deleted file mode 100644 index 214bf50c6e..0000000000 --- a/backend/.sqlx/query-173fbfd3ca2344fd08f73af75524c917d27fdb6273a35a563292b1f0701dc6ed.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['failure_module'], $1),\n ARRAY['step'],\n $2\n )\n WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "173fbfd3ca2344fd08f73af75524c917d27fdb6273a35a563292b1f0701dc6ed" -} diff --git a/backend/.sqlx/query-17851a0710b80ffd6bebe42012a354665dff01554549ea7bbbb9953c68231296.json b/backend/.sqlx/query-17851a0710b80ffd6bebe42012a354665dff01554549ea7bbbb9953c68231296.json deleted file mode 100644 index eefc009b5e..0000000000 --- a/backend/.sqlx/query-17851a0710b80ffd6bebe42012a354665dff01554549ea7bbbb9953c68231296.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'iterator', 'index'],\n ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "int4", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Uuid" - ] - }, - "nullable": [ - null - ] - }, - "hash": "17851a0710b80ffd6bebe42012a354665dff01554549ea7bbbb9953c68231296" -} diff --git a/backend/.sqlx/query-1850552883e67da181d68ff5c4e1babaa2fe072900b57e78e461590a6dafb682.json b/backend/.sqlx/query-1850552883e67da181d68ff5c4e1babaa2fe072900b57e78e461590a6dafb682.json deleted file mode 100644 index 037e63ae49..0000000000 --- a/backend/.sqlx/query-1850552883e67da181d68ff5c4e1babaa2fe072900b57e78e461590a6dafb682.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "1850552883e67da181d68ff5c4e1babaa2fe072900b57e78e461590a6dafb682" -} diff --git a/backend/.sqlx/query-18ddbb9eb4c0ec03e6170f8c70634546cddaf1029618ed14015fd7b0a7017441.json b/backend/.sqlx/query-18ddbb9eb4c0ec03e6170f8c70634546cddaf1029618ed14015fd7b0a7017441.json new file mode 100644 index 0000000000..c1258697c9 --- /dev/null +++ b/backend/.sqlx/query-18ddbb9eb4c0ec03e6170f8c70634546cddaf1029618ed14015fd7b0a7017441.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT set_session_context($1, $2, $3, $4, $5, $6)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "set_session_context", + "type_info": "Void" + } + ], + "parameters": { + "Left": [ + "Bool", + "Text", + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "18ddbb9eb4c0ec03e6170f8c70634546cddaf1029618ed14015fd7b0a7017441" +} diff --git a/backend/.sqlx/query-1961d15ae075072bd5f677c95f9b4dac7f747585d98aa8bcf60dcfb4c8124028.json b/backend/.sqlx/query-1961d15ae075072bd5f677c95f9b4dac7f747585d98aa8bcf60dcfb4c8124028.json deleted file mode 100644 index 418be57757..0000000000 --- a/backend/.sqlx/query-1961d15ae075072bd5f677c95f9b4dac7f747585d98aa8bcf60dcfb4c8124028.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger WHERE http_method = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "route_path", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "is_async", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "requires_auth", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "static_asset_config: _", - "type_info": "Jsonb" - }, - { - "ordinal": 10, - "name": "is_static_website", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - { - "Custom": { - "name": "http_method", - "kind": { - "Enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] - } - } - } - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false - ] - }, - "hash": "1961d15ae075072bd5f677c95f9b4dac7f747585d98aa8bcf60dcfb4c8124028" -} diff --git a/backend/.sqlx/query-197590e7ab74f64bbf374f23128850bed8f435ea5de16ba796f346fba51d9437.json b/backend/.sqlx/query-197590e7ab74f64bbf374f23128850bed8f435ea5de16ba796f346fba51d9437.json new file mode 100644 index 0000000000..28d629c587 --- /dev/null +++ b/backend/.sqlx/query-197590e7ab74f64bbf374f23128850bed8f435ea5de16ba796f346fba51d9437.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT ai_config as \"ai_config: sqlx::types::Json\" FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "ai_config: sqlx::types::Json", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "197590e7ab74f64bbf374f23128850bed8f435ea5de16ba796f346fba51d9437" +} diff --git a/backend/.sqlx/query-19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74.json b/backend/.sqlx/query-19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74.json new file mode 100644 index 0000000000..62c47f2503 --- /dev/null +++ b/backend/.sqlx/query-19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74" +} diff --git a/backend/.sqlx/query-1bbef6baa5b8e2522d685df2979bb1e4b9022f5e841afd9eeb08a81688f6c0c8.json b/backend/.sqlx/query-1bbef6baa5b8e2522d685df2979bb1e4b9022f5e841afd9eeb08a81688f6c0c8.json deleted file mode 100644 index 4fcd1f0969..0000000000 --- a/backend/.sqlx/query-1bbef6baa5b8e2522d685df2979bb1e4b9022f5e841afd9eeb08a81688f6c0c8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET ai_resource = NULL, code_completion_model = $1, ai_models = '{}' WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "1bbef6baa5b8e2522d685df2979bb1e4b9022f5e841afd9eeb08a81688f6c0c8" -} diff --git a/backend/.sqlx/query-1bceaf6e9f25745b7f70128054ca81d68f3d56d4782e99e05b4f1cb362683514.json b/backend/.sqlx/query-1bceaf6e9f25745b7f70128054ca81d68f3d56d4782e99e05b4f1cb362683514.json new file mode 100644 index 0000000000..1be74d47f2 --- /dev/null +++ b/backend/.sqlx/query-1bceaf6e9f25745b7f70128054ca81d68f3d56d4782e99e05b4f1cb362683514.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH inserted_concurrency_counter AS (\n INSERT INTO concurrency_counter (concurrency_id, job_uuids) \n VALUES ($1, '{}'::jsonb)\n ON CONFLICT DO NOTHING\n )\n INSERT INTO concurrency_key(key, job_id) VALUES ($1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "1bceaf6e9f25745b7f70128054ca81d68f3d56d4782e99e05b4f1cb362683514" +} diff --git a/backend/.sqlx/query-1c5d3556fc8436ddd294f39c5431e1f501a821d6143c5d8aece20814237a6b86.json b/backend/.sqlx/query-1c5d3556fc8436ddd294f39c5431e1f501a821d6143c5d8aece20814237a6b86.json new file mode 100644 index 0000000000..adf00884b8 --- /dev/null +++ b/backend/.sqlx/query-1c5d3556fc8436ddd294f39c5431e1f501a821d6143c5d8aece20814237a6b86.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT token, expires_at, blacklisted_at, blacklisted_by \n FROM agent_token_blacklist \n ORDER BY blacklisted_at DESC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "token", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "expires_at", + "type_info": "Timestamp" + }, + { + "ordinal": 2, + "name": "blacklisted_at", + "type_info": "Timestamp" + }, + { + "ordinal": 3, + "name": "blacklisted_by", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "1c5d3556fc8436ddd294f39c5431e1f501a821d6143c5d8aece20814237a6b86" +} diff --git a/backend/.sqlx/query-1d819b829cd92995c39d29540df8cffbcc3334bada244a331a0bd8db06029d42.json b/backend/.sqlx/query-1d819b829cd92995c39d29540df8cffbcc3334bada244a331a0bd8db06029d42.json deleted file mode 100644 index 9f40c5293d..0000000000 --- a/backend/.sqlx/query-1d819b829cd92995c39d29540df8cffbcc3334bada244a331a0bd8db06029d42.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM v2_job_completed c\n USING v2_job j\n WHERE\n created_at <= now() - ($1::bigint::text || ' s')::interval\n AND completed_at + ($1::bigint::text || ' s')::interval <= now()\n AND c.id = j.id\n RETURNING c.id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - }, - "hash": "1d819b829cd92995c39d29540df8cffbcc3334bada244a331a0bd8db06029d42" -} diff --git a/backend/.sqlx/query-1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa.json b/backend/.sqlx/query-1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa.json deleted file mode 100644 index 6a79013c0b..0000000000 --- a/backend/.sqlx/query-1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa" -} diff --git a/backend/.sqlx/query-1e659916501e668913e591eb7282d3881fa44468ed5330a501daa0d61c84cb71.json b/backend/.sqlx/query-1e659916501e668913e591eb7282d3881fa44468ed5330a501daa0d61c84cb71.json deleted file mode 100644 index 2cb0d351da..0000000000 --- a/backend/.sqlx/query-1e659916501e668913e591eb7282d3881fa44468ed5330a501daa0d61c84cb71.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET automatic_billing = false WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "1e659916501e668913e591eb7282d3881fa44468ed5330a501daa0d61c84cb71" -} diff --git a/backend/.sqlx/query-197321abfe4667256761884970334f58c1b3edfcc1e863ec4316c9742a1ac7c8.json b/backend/.sqlx/query-1eeb218c30c0a6b0f7633813c764f57f8968894b4786b94109a057796ff500e4.json similarity index 54% rename from backend/.sqlx/query-197321abfe4667256761884970334f58c1b3edfcc1e863ec4316c9742a1ac7c8.json rename to backend/.sqlx/query-1eeb218c30c0a6b0f7633813c764f57f8968894b4786b94109a057796ff500e4.json index bfc823c911..51d3302cef 100644 --- a/backend/.sqlx/query-197321abfe4667256761884970334f58c1b3edfcc1e863ec4316c9742a1ac7c8.json +++ b/backend/.sqlx/query-1eeb218c30c0a6b0f7633813c764f57f8968894b4786b94109a057796ff500e4.json @@ -1,42 +1,101 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as \"http_method: _\", edited_by, email, edited_at, extra_perms, is_async, requires_auth, static_asset_config as \"static_asset_config: _\", is_static_website\n FROM http_trigger\n WHERE workspace_id = $1 AND path = $2", + "query": "\n SELECT \n path, \n script_path, \n is_flow, \n route_path, \n authentication_resource_path,\n workspace_id, \n is_async, \n authentication_method AS \"authentication_method: _\", \n edited_by, \n email, \n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website\n FROM \n http_trigger \n WHERE \n http_method = $1\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, "name": "path", "type_info": "Varchar" }, { - "ordinal": 2, - "name": "route_path", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "route_path_key", - "type_info": "Varchar" - }, - { - "ordinal": 4, + "ordinal": 1, "name": "script_path", "type_info": "Varchar" }, { - "ordinal": 5, + "ordinal": 2, "name": "is_flow", "type_info": "Bool" }, + { + "ordinal": 3, + "name": "route_path", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "authentication_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "workspace_id", + "type_info": "Varchar" + }, { "ordinal": 6, - "name": "http_method: _", + "name": "is_async", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "authentication_method: _", "type_info": { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + } + }, + { + "ordinal": 8, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "static_asset_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "wrap_body", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "raw_string", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "workspaced_route", + "type_info": "Bool" + }, + { + "ordinal": 14, + "name": "is_static_website", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + { "Custom": { "name": "http_method", "kind": { @@ -50,52 +109,6 @@ } } } - }, - { - "ordinal": 7, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "edited_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 11, - "name": "is_async", - "type_info": "Bool" - }, - { - "ordinal": 12, - "name": "requires_auth", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "static_asset_config: _", - "type_info": "Jsonb" - }, - { - "ordinal": 14, - "name": "is_static_website", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" ] }, "nullable": [ @@ -103,18 +116,18 @@ false, false, false, - false, - false, - false, - false, + true, false, false, false, false, false, true, + false, + false, + false, false ] }, - "hash": "197321abfe4667256761884970334f58c1b3edfcc1e863ec4316c9742a1ac7c8" + "hash": "1eeb218c30c0a6b0f7633813c764f57f8968894b4786b94109a057796ff500e4" } diff --git a/backend/.sqlx/query-20fd50949796913dd48f67ee75b11272ce5b3046f87b9efd504e013fba9724f5.json b/backend/.sqlx/query-20fd50949796913dd48f67ee75b11272ce5b3046f87b9efd504e013fba9724f5.json new file mode 100644 index 0000000000..e63998cc52 --- /dev/null +++ b/backend/.sqlx/query-20fd50949796913dd48f67ee75b11272ce5b3046f87b9efd504e013fba9724f5.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT last_value FROM http_trigger_version_seq", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "last_value", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "20fd50949796913dd48f67ee75b11272ce5b3046f87b9efd504e013fba9724f5" +} diff --git a/backend/.sqlx/query-21204693fa8608c78151f63fa76bb36bdece81385380a42ca06ca6be19694896.json b/backend/.sqlx/query-21204693fa8608c78151f63fa76bb36bdece81385380a42ca06ca6be19694896.json new file mode 100644 index 0000000000..5454fdf323 --- /dev/null +++ b/backend/.sqlx/query-21204693fa8608c78151f63fa76bb36bdece81385380a42ca06ca6be19694896.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_queue SET canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = ANY($3) AND workspace_id = $4 AND (canceled_by IS NULL OR canceled_reason != $2) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "UuidArray", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "21204693fa8608c78151f63fa76bb36bdece81385380a42ca06ca6be19694896" +} diff --git a/backend/.sqlx/query-222e29b89d10f3840d4e9b9ab63207df3cbab63c83d4a6374e72a11893841653.json b/backend/.sqlx/query-222e29b89d10f3840d4e9b9ab63207df3cbab63c83d4a6374e72a11893841653.json new file mode 100644 index 0000000000..b2879a71a9 --- /dev/null +++ b/backend/.sqlx/query-222e29b89d10f3840d4e9b9ab63207df3cbab63c83d4a6374e72a11893841653.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "CREATE INDEX CONCURRENTLY idx_audit_recent_login_activities \nON audit (timestamp, username) \nWHERE operation IN ('users.login', 'oauth.login', 'users.token.refresh');", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "222e29b89d10f3840d4e9b9ab63207df3cbab63c83d4a6374e72a11893841653" +} diff --git a/backend/.sqlx/query-e23e110e1f0438d21534fc4323e0e7bc1f0dbeca2e4f44ced05bae0ca5ca1039.json b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json similarity index 53% rename from backend/.sqlx/query-e23e110e1f0438d21534fc4323e0e7bc1f0dbeca2e4f44ced05bae0ca5ca1039.json rename to backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json index 7b30e079f0..5b810738b3 100644 --- a/backend/.sqlx/query-e23e110e1f0438d21534fc4323e0e7bc1f0dbeca2e4f44ced05bae0ca5ca1039.json +++ b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT trigger_config as \"trigger_config: _\", owner, email\n FROM capture_config\n WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4 AND last_client_ping > NOW() - INTERVAL '10 seconds'", + "query": "\n SELECT \n trigger_config AS \"trigger_config: _\", \n owner, \n email\n FROM \n capture_config\n WHERE \n workspace_id = $1\n AND path = $2\n AND is_flow = $3\n AND trigger_kind = $4\n AND last_client_ping > NOW() - INTERVAL '10 seconds'\n AND (\n $5::bool IS FALSE\n OR (\n trigger_config IS NOT NULL\n AND trigger_config ->> 'delivery_type' = 'push'\n )\n )\n ", "describe": { "columns": [ { @@ -37,11 +37,13 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } - } + }, + "Bool" ] }, "nullable": [ @@ -50,5 +52,5 @@ false ] }, - "hash": "e23e110e1f0438d21534fc4323e0e7bc1f0dbeca2e4f44ced05bae0ca5ca1039" + "hash": "23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2" } diff --git a/backend/.sqlx/query-234acda79d470e99e9cbde5c7401d6f7894c25f90e39ec8606f79b8be56d1c17.json b/backend/.sqlx/query-234acda79d470e99e9cbde5c7401d6f7894c25f90e39ec8606f79b8be56d1c17.json new file mode 100644 index 0000000000..1bb99fad8e --- /dev/null +++ b/backend/.sqlx/query-234acda79d470e99e9cbde5c7401d6f7894c25f90e39ec8606f79b8be56d1c17.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS(\n SELECT 1\n FROM \n http_trigger \n WHERE \n workspace_id = $1 AND \n path = $2\n )\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "234acda79d470e99e9cbde5c7401d6f7894c25f90e39ec8606f79b8be56d1c17" +} diff --git a/backend/.sqlx/query-240cf4ba63ec39a7ccfa8360824259d2fdc6681bbbd44e3ccde0a3893f6cf9a0.json b/backend/.sqlx/query-240cf4ba63ec39a7ccfa8360824259d2fdc6681bbbd44e3ccde0a3893f6cf9a0.json deleted file mode 100644 index 134cb58d17..0000000000 --- a/backend/.sqlx/query-240cf4ba63ec39a7ccfa8360824259d2fdc6681bbbd44e3ccde0a3893f6cf9a0.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT \n MAX (created_at) AS last_deploy, \n COUNT (*) AS deploys_count \n , 'python' AS language\n FROM metrics \n WHERE id = 'no_uv_usage_py'\n\n UNION ALL\n \n SELECT \n MAX (created_at) AS last_deploy, \n COUNT (*) AS deploys_count \n , 'ansible' AS language\n FROM metrics \n WHERE id = 'no_uv_usage_ansible'\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "last_deploy", - "type_info": "Timestamptz" - }, - { - "ordinal": 1, - "name": "deploys_count", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "language", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "240cf4ba63ec39a7ccfa8360824259d2fdc6681bbbd44e3ccde0a3893f6cf9a0" -} diff --git a/backend/.sqlx/query-26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4.json b/backend/.sqlx/query-26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4.json new file mode 100644 index 0000000000..b14dab2503 --- /dev/null +++ b/backend/.sqlx/query-26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "jsonb_build_object", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "UuidArray" + ] + }, + "nullable": [ + null + ] + }, + "hash": "26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4" +} diff --git a/backend/.sqlx/query-282afbff89d3186d47ef5dbd0b65026ad37fb31b485fc44b6ec257dd77825428.json b/backend/.sqlx/query-282afbff89d3186d47ef5dbd0b65026ad37fb31b485fc44b6ec257dd77825428.json deleted file mode 100644 index 200bd3155d..0000000000 --- a/backend/.sqlx/query-282afbff89d3186d47ef5dbd0b65026ad37fb31b485fc44b6ec257dd77825428.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT created_by, coalesce(job_logs.logs, '') as logs, job_logs.log_offset, job_logs.log_file_index\n FROM v2_as_completed_job\n LEFT JOIN job_logs ON job_logs.job_id = v2_as_completed_job.id\n WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "created_by", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "logs", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "log_offset", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "log_file_index", - "type_info": "TextArray" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - true, - null, - false, - true - ] - }, - "hash": "282afbff89d3186d47ef5dbd0b65026ad37fb31b485fc44b6ec257dd77825428" -} diff --git a/backend/.sqlx/query-28b42ab9c3ce0c2f05cf385e81f3b72fa7c4b3c458d52a5891a61f9c53a49c6d.json b/backend/.sqlx/query-28b42ab9c3ce0c2f05cf385e81f3b72fa7c4b3c458d52a5891a61f9c53a49c6d.json deleted file mode 100644 index 817b993d8f..0000000000 --- a/backend/.sqlx/query-28b42ab9c3ce0c2f05cf385e81f3b72fa7c4b3c458d52a5891a61f9c53a49c6d.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "int4", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Uuid" - ] - }, - "nullable": [ - null - ] - }, - "hash": "28b42ab9c3ce0c2f05cf385e81f3b72fa7c4b3c458d52a5891a61f9c53a49c6d" -} diff --git a/backend/.sqlx/query-29624682d687790dd199c4af759132d79fdb2982de111cb5fd43e3d9ecd0f15e.json b/backend/.sqlx/query-29624682d687790dd199c4af759132d79fdb2982de111cb5fd43e3d9ecd0f15e.json deleted file mode 100644 index 7623243f07..0000000000 --- a/backend/.sqlx/query-29624682d687790dd199c4af759132d79fdb2982de111cb5fd43e3d9ecd0f15e.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $21, $22, $23, $24, $25, $26,\n CASE WHEN $14::VARCHAR IS NOT NULL THEN 'schedule'::job_trigger_kind END,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Varchar", - "Text", - "Text", - "Jsonb", - "Varchar", - "Uuid", - "Varchar", - "Varchar", - "Int8", - "Varchar", - "Jsonb", - { - "Custom": { - "name": "job_kind", - "kind": { - "Enum": [ - "script", - "preview", - "flow", - "dependencies", - "flowpreview", - "script_hub", - "identity", - "flowdependencies", - "http", - "graphql", - "postgresql", - "noop", - "appdependencies", - "deploymentcallback", - "singlescriptflow", - "flowscript", - "flownode", - "appscript" - ] - } - } - }, - "Varchar", - { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb" - ] - } - } - }, - "Bool", - "Text", - "Varchar", - "Bool", - "Uuid", - "Int4", - "Int4", - "Int4", - "Varchar", - "Int4", - "Int2", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "29624682d687790dd199c4af759132d79fdb2982de111cb5fd43e3d9ecd0f15e" -} diff --git a/backend/.sqlx/query-299e16725162888c01712f371785199960264b54c1ddf928c0c654ab15176f63.json b/backend/.sqlx/query-299e16725162888c01712f371785199960264b54c1ddf928c0c654ab15176f63.json new file mode 100644 index 0000000000..d64d8f404b --- /dev/null +++ b/backend/.sqlx/query-299e16725162888c01712f371785199960264b54c1ddf928c0c654ab15176f63.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n capture_config \n SET \n server_id = $1,\n last_server_ping = now(), \n error = 'Connecting...' \n WHERE \n last_client_ping > NOW() - INTERVAL '10 seconds' AND \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'gcp' AND \n (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') \n RETURNING true\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text", + "Bool" + ] + }, + "nullable": [ + null + ] + }, + "hash": "299e16725162888c01712f371785199960264b54c1ddf928c0c654ab15176f63" +} diff --git a/backend/.sqlx/query-2a33a35afc1ba4c31a5713cfd1a2c662f25cda387197aaf9f35000df31b8b07d.json b/backend/.sqlx/query-2a33a35afc1ba4c31a5713cfd1a2c662f25cda387197aaf9f35000df31b8b07d.json new file mode 100644 index 0000000000..236c2fa597 --- /dev/null +++ b/backend/.sqlx/query-2a33a35afc1ba4c31a5713cfd1a2c662f25cda387197aaf9f35000df31b8b07d.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DROP INDEX CONCURRENTLY IF EXISTS log_file_hostname_log_ts_idx", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "2a33a35afc1ba4c31a5713cfd1a2c662f25cda387197aaf9f35000df31b8b07d" +} diff --git a/backend/.sqlx/query-ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc.json b/backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json similarity index 63% rename from backend/.sqlx/query-ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc.json rename to backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json index 1d30071300..77f61ccc47 100644 --- a/backend/.sqlx/query-ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc.json +++ b/backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))", + "query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)", "describe": { "columns": [ { @@ -19,5 +19,5 @@ null ] }, - "hash": "ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc" + "hash": "2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4" } diff --git a/backend/.sqlx/query-d2def87d7f7901eebc65082f7df5e0a33e5702b25c3db3affa06155e90480e42.json b/backend/.sqlx/query-2a510a8bec98055796f987d86c344ca116895d71de09db338ec09e425dcebe5e.json similarity index 63% rename from backend/.sqlx/query-d2def87d7f7901eebc65082f7df5e0a33e5702b25c3db3affa06155e90480e42.json rename to backend/.sqlx/query-2a510a8bec98055796f987d86c344ca116895d71de09db338ec09e425dcebe5e.json index 58f03dbec2..23337708f8 100644 --- a/backend/.sqlx/query-d2def87d7f7901eebc65082f7df5e0a33e5702b25c3db3affa06155e90480e42.json +++ b/backend/.sqlx/query-2a510a8bec98055796f987d86c344ca116895d71de09db338ec09e425dcebe5e.json @@ -1,50 +1,35 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM job_perms WHERE job_id = $1 AND workspace_id = $2", + "query": "SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "job_id", - "type_info": "Uuid" - }, - { - "ordinal": 1, "name": "email", "type_info": "Varchar" }, { - "ordinal": 2, + "ordinal": 1, "name": "username", "type_info": "Varchar" }, { - "ordinal": 3, + "ordinal": 2, "name": "is_admin", "type_info": "Bool" }, { - "ordinal": 4, + "ordinal": 3, "name": "is_operator", "type_info": "Bool" }, { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamp" - }, - { - "ordinal": 6, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 7, + "ordinal": 4, "name": "groups", "type_info": "TextArray" }, { - "ordinal": 8, + "ordinal": 5, "name": "folders", "type_info": "JsonbArray" } @@ -61,11 +46,8 @@ false, false, false, - false, - false, - false, false ] }, - "hash": "d2def87d7f7901eebc65082f7df5e0a33e5702b25c3db3affa06155e90480e42" + "hash": "2a510a8bec98055796f987d86c344ca116895d71de09db338ec09e425dcebe5e" } diff --git a/backend/.sqlx/query-2ae44acae7ac80e191b37071baa26e57488489177fe596940d3698d843a93666.json b/backend/.sqlx/query-2ae44acae7ac80e191b37071baa26e57488489177fe596940d3698d843a93666.json new file mode 100644 index 0000000000..405902863c --- /dev/null +++ b/backend/.sqlx/query-2ae44acae7ac80e191b37071baa26e57488489177fe596940d3698d843a93666.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT script_path FROM v2_as_queue WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "script_path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "2ae44acae7ac80e191b37071baa26e57488489177fe596940d3698d843a93666" +} diff --git a/backend/.sqlx/query-2bb2cf6accb18d3e37a63388cca52a6591e7593b1a7c3d7a6848587679a48187.json b/backend/.sqlx/query-2bb2cf6accb18d3e37a63388cca52a6591e7593b1a7c3d7a6848587679a48187.json deleted file mode 100644 index 11e7df65bf..0000000000 --- a/backend/.sqlx/query-2bb2cf6accb18d3e37a63388cca52a6591e7593b1a7c3d7a6848587679a48187.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE flow_workspace_runnables SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "2bb2cf6accb18d3e37a63388cca52a6591e7593b1a7c3d7a6848587679a48187" -} diff --git a/backend/.sqlx/query-2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3.json b/backend/.sqlx/query-2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3.json new file mode 100644 index 0000000000..d60b424505 --- /dev/null +++ b/backend/.sqlx/query-2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3" +} diff --git a/backend/.sqlx/query-dee32ce9c4010ae407b4d1ba2ecf2062c36ee5dab3d4894ad4b2aa8b2f5a0db9.json b/backend/.sqlx/query-2bf99d540365c228e1776ee5d2ba01ebe289183526afab19c1390bbf5082f019.json similarity index 61% rename from backend/.sqlx/query-dee32ce9c4010ae407b4d1ba2ecf2062c36ee5dab3d4894ad4b2aa8b2f5a0db9.json rename to backend/.sqlx/query-2bf99d540365c228e1776ee5d2ba01ebe289183526afab19c1390bbf5082f019.json index bb8d339e55..04acf05335 100644 --- a/backend/.sqlx/query-dee32ce9c4010ae407b4d1ba2ecf2062c36ee5dab3d4894ad4b2aa8b2f5a0db9.json +++ b/backend/.sqlx/query-2bf99d540365c228e1776ee5d2ba01ebe289183526afab19c1390bbf5082f019.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM log_file WHERE hostname = $1 AND log_ts = $2)", + "query": "SELECT EXISTS(SELECT 1 FROM agent_token_blacklist WHERE token = $1 AND expires_at > $2)", "describe": { "columns": [ { @@ -19,5 +19,5 @@ null ] }, - "hash": "dee32ce9c4010ae407b4d1ba2ecf2062c36ee5dab3d4894ad4b2aa8b2f5a0db9" + "hash": "2bf99d540365c228e1776ee5d2ba01ebe289183526afab19c1390bbf5082f019" } diff --git a/backend/.sqlx/query-2d4d4564108376ab310cb4a50fa2fa84fefbb3df8bb1bc9996c40a86d464b8a1.json b/backend/.sqlx/query-2d4d4564108376ab310cb4a50fa2fa84fefbb3df8bb1bc9996c40a86d464b8a1.json deleted file mode 100644 index 8c26257ef8..0000000000 --- a/backend/.sqlx/query-2d4d4564108376ab310cb4a50fa2fa84fefbb3df8bb1bc9996c40a86d464b8a1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET automatic_billing = TRUE WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "2d4d4564108376ab310cb4a50fa2fa84fefbb3df8bb1bc9996c40a86d464b8a1" -} diff --git a/backend/.sqlx/query-2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e.json b/backend/.sqlx/query-2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e.json new file mode 100644 index 0000000000..5622b3691e --- /dev/null +++ b/backend/.sqlx/query-2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e.json @@ -0,0 +1,142 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "gcp_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "subscription_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "topic_id", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "delivery_type: _", + "type_info": { + "Custom": { + "name": "delivery_mode", + "kind": { + "Enum": [ + "push", + "pull" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "delivery_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "subscription_mode: _", + "type_info": { + "Custom": { + "name": "gcp_subscription_mode", + "kind": { + "Enum": [ + "create_update", + "existing" + ] + } + } + } + }, + { + "ordinal": 7, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 13, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + true, + false + ] + }, + "hash": "2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e" +} diff --git a/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json b/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json deleted file mode 100644 index 70c5674cdf..0000000000 --- a/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "usage", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Varchar" - ] - }, - "nullable": [ - false - ] - }, - "hash": "2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31" -} diff --git a/backend/.sqlx/query-2ebb0463b790ddf7ba0ee22d8c9afc88eb57c4110a202775003fb48b2f4e317f.json b/backend/.sqlx/query-2ebb0463b790ddf7ba0ee22d8c9afc88eb57c4110a202775003fb48b2f4e317f.json new file mode 100644 index 0000000000..49e6e6c6ae --- /dev/null +++ b/backend/.sqlx/query-2ebb0463b790ddf7ba0ee22d8c9afc88eb57c4110a202775003fb48b2f4e317f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n COALESCE(workflow_as_code_status, '{}'::jsonb),\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'started_at'],\n to_jsonb(now()::text)\n )\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "2ebb0463b790ddf7ba0ee22d8c9afc88eb57c4110a202775003fb48b2f4e317f" +} diff --git a/backend/.sqlx/query-6199e8be5cb13db71108e555ea20f0b76dc38476670f9fc0667b057d2766d42e.json b/backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json similarity index 52% rename from backend/.sqlx/query-6199e8be5cb13db71108e555ea20f0b76dc38476670f9fc0667b057d2766d42e.json rename to backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json index 2313dd087c..27854ceeff 100644 --- a/backend/.sqlx/query-6199e8be5cb13db71108e555ea20f0b76dc38476670f9fc0667b057d2766d42e.json +++ b/backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT set_config('session.groups', $1, true)", + "query": "SELECT COUNT(*) FROM token WHERE email = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "set_config", - "type_info": "Text" + "name": "count", + "type_info": "Int8" } ], "parameters": { @@ -18,5 +18,5 @@ null ] }, - "hash": "6199e8be5cb13db71108e555ea20f0b76dc38476670f9fc0667b057d2766d42e" + "hash": "2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4" } diff --git a/backend/.sqlx/query-6ff7a025f529c077c1b6c9632a367aa29e2f0fdac3f1984550484d5a06a6ea21.json b/backend/.sqlx/query-2faa27519624249f16cf89814ab5efe8f8daf928c1194cecacfa8223165fb9f2.json similarity index 50% rename from backend/.sqlx/query-6ff7a025f529c077c1b6c9632a367aa29e2f0fdac3f1984550484d5a06a6ea21.json rename to backend/.sqlx/query-2faa27519624249f16cf89814ab5efe8f8daf928c1194cecacfa8223165fb9f2.json index b045ed2b07..dbe3786571 100644 --- a/backend/.sqlx/query-6ff7a025f529c077c1b6c9632a367aa29e2f0fdac3f1984550484d5a06a6ea21.json +++ b/backend/.sqlx/query-2faa27519624249f16cf89814ab5efe8f8daf928c1194cecacfa8223165fb9f2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE v2_job_runtime r SET\n memory_peak = $1,\n ping = now()\n FROM v2_job_queue q\n WHERE r.id = $2 AND q.id = r.id\n RETURNING canceled_by, canceled_reason", + "query": "UPDATE v2_job_runtime r SET\n memory_peak = $1,\n ping = now()\n FROM v2_job_queue q\n WHERE r.id = $2 AND q.id = r.id\n RETURNING canceled_by, canceled_reason", "describe": { "columns": [ { @@ -25,5 +25,5 @@ true ] }, - "hash": "6ff7a025f529c077c1b6c9632a367aa29e2f0fdac3f1984550484d5a06a6ea21" + "hash": "2faa27519624249f16cf89814ab5efe8f8daf928c1194cecacfa8223165fb9f2" } diff --git a/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json b/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json deleted file mode 100644 index 58cfc98b09..0000000000 --- a/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH job_result AS (\n SELECT result \n FROM v2_job_completed \n WHERE id = $1\n )\n UPDATE v2_job \n SET args = COALESCE(\n CASE \n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object' \n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END, \n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9" -} diff --git a/backend/.sqlx/query-30483ae46f6d0452126eb2cd07fc4d960961cc6ee61cf065113b7a48f97caecc.json b/backend/.sqlx/query-30483ae46f6d0452126eb2cd07fc4d960961cc6ee61cf065113b7a48f97caecc.json new file mode 100644 index 0000000000..959b66239f --- /dev/null +++ b/backend/.sqlx/query-30483ae46f6d0452126eb2cd07fc4d960961cc6ee61cf065113b7a48f97caecc.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value as \"value: sqlx::types::Json>\" FROM resource WHERE path = $1 AND workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value: sqlx::types::Json>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "30483ae46f6d0452126eb2cd07fc4d960961cc6ee61cf065113b7a48f97caecc" +} diff --git a/backend/.sqlx/query-31b6fccad46b22bcbba6bbce22209ccb1825116004ed72682854ce3352f454a5.json b/backend/.sqlx/query-31b6fccad46b22bcbba6bbce22209ccb1825116004ed72682854ce3352f454a5.json deleted file mode 100644 index 0871986a4e..0000000000 --- a/backend/.sqlx/query-31b6fccad46b22bcbba6bbce22209ccb1825116004ed72682854ce3352f454a5.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT \n \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\"\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "websocket_used!", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "http_routes_used!", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "kafka_used!", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "nats_used!", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "postgres_used!", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "mqtt_used!", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "sqs_used!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null, - null, - null, - null, - null, - null, - null - ] - }, - "hash": "31b6fccad46b22bcbba6bbce22209ccb1825116004ed72682854ce3352f454a5" -} diff --git a/backend/.sqlx/query-31df83e9eb6078e93ec5fe4168306caccb849db9e0f71d86da655b01c6a3e8d0.json b/backend/.sqlx/query-31df83e9eb6078e93ec5fe4168306caccb849db9e0f71d86da655b01c6a3e8d0.json deleted file mode 100644 index b96c05d674..0000000000 --- a/backend/.sqlx/query-31df83e9eb6078e93ec5fe4168306caccb849db9e0f71d86da655b01c6a3e8d0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($1, $2, $3, COALESCE($4, now()), CASE WHEN $3 THEN now() END, $5, $6) RETURNING id AS \"id!\"", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id!", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Varchar", - "Uuid", - "Bool", - "Timestamptz", - "Varchar", - "Int2" - ] - }, - "nullable": [ - false - ] - }, - "hash": "31df83e9eb6078e93ec5fe4168306caccb849db9e0f71d86da655b01c6a3e8d0" -} diff --git a/backend/.sqlx/query-31fe5d2965f7b25dea785f8be529a9b2c4c83c910fd7e2a08f4d95ae195ab3ed.json b/backend/.sqlx/query-31fe5d2965f7b25dea785f8be529a9b2c4c83c910fd7e2a08f4d95ae195ab3ed.json index 85c0412f3b..7f79e83137 100644 --- a/backend/.sqlx/query-31fe5d2965f7b25dea785f8be529a9b2c4c83c910fd7e2a08f4d95ae195ab3ed.json +++ b/backend/.sqlx/query-31fe5d2965f7b25dea785f8be529a9b2c4c83c910fd7e2a08f4d95ae195ab3ed.json @@ -12,7 +12,7 @@ "parameters": { "Left": [ "Varchar", - "Json", + "Jsonb", "Text" ] }, diff --git a/backend/.sqlx/query-3325f8ed245b1bce27c3d9f5e62ffc14b5c8aabf5ab53384f5f2b20eafd66cb3.json b/backend/.sqlx/query-3325f8ed245b1bce27c3d9f5e62ffc14b5c8aabf5ab53384f5f2b20eafd66cb3.json new file mode 100644 index 0000000000..6c02ca51d6 --- /dev/null +++ b/backend/.sqlx/query-3325f8ed245b1bce27c3d9f5e62ffc14b5c8aabf5ab53384f5f2b20eafd66cb3.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['retry'], $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "3325f8ed245b1bce27c3d9f5e62ffc14b5c8aabf5ab53384f5f2b20eafd66cb3" +} diff --git a/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json b/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json new file mode 100644 index 0000000000..01dfc9ebe3 --- /dev/null +++ b/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n path,\n summary,\n description\n FROM\n flow\n WHERE\n path ~ ANY($1) AND\n workspace_id = $2 AND\n archived is FALSE\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "TextArray", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8" +} diff --git a/backend/.sqlx/query-33ee913ce263600a3f94f90e4a42cf0e4086030f3b7994e4892392765cbe1517.json b/backend/.sqlx/query-33ee913ce263600a3f94f90e4a42cf0e4086030f3b7994e4892392765cbe1517.json new file mode 100644 index 0000000000..9b7581526e --- /dev/null +++ b/backend/.sqlx/query-33ee913ce263600a3f94f90e4a42cf0e4086030f3b7994e4892392765cbe1517.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS(\n SELECT \n 1 \n FROM \n gcp_trigger \n WHERE \n path = $1 AND \n workspace_id = $2\n )", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "33ee913ce263600a3f94f90e4a42cf0e4086030f3b7994e4892392765cbe1517" +} diff --git a/backend/.sqlx/query-348f73fa8222ec195bb5b2260d1595e6e9fa9d73779cf352e94039fd47de4294.json b/backend/.sqlx/query-348f73fa8222ec195bb5b2260d1595e6e9fa9d73779cf352e94039fd47de4294.json new file mode 100644 index 0000000000..4f9e060ead --- /dev/null +++ b/backend/.sqlx/query-348f73fa8222ec195bb5b2260d1595e6e9fa9d73779cf352e94039fd47de4294.json @@ -0,0 +1,217 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "schedule", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "timezone", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "on_failure", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "on_failure_times", + "type_info": "Int4" + }, + { + "ordinal": 15, + "name": "on_failure_exact", + "type_info": "Bool" + }, + { + "ordinal": 16, + "name": "on_failure_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "on_recovery", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "on_recovery_times", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "on_recovery_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "on_success", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "on_success_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "ws_error_handler_muted", + "type_info": "Bool" + }, + { + "ordinal": 23, + "name": "retry", + "type_info": "Jsonb" + }, + { + "ordinal": 24, + "name": "no_flow_overlap", + "type_info": "Bool" + }, + { + "ordinal": 25, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "paused_until", + "type_info": "Timestamptz" + }, + { + "ordinal": 29, + "name": "cron_version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Jsonb", + "Varchar", + "Int4", + "Bool", + "Jsonb", + "Varchar", + "Int4", + "Jsonb", + "Varchar", + "Jsonb", + "Bool", + "Jsonb", + "Varchar", + "Bool", + "Varchar", + "Timestamptz", + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + false, + true, + true, + true, + true, + true + ] + }, + "hash": "348f73fa8222ec195bb5b2260d1595e6e9fa9d73779cf352e94039fd47de4294" +} diff --git a/backend/.sqlx/query-05d6405b2cc6aabf564a10f05402878e9f2a13e0ce0dad42723f95ac7fb15d4b.json b/backend/.sqlx/query-34d22638730d62e8bf7020ae0f7ccacf4b258877375ceff2da640fe65d270794.json similarity index 53% rename from backend/.sqlx/query-05d6405b2cc6aabf564a10f05402878e9f2a13e0ce0dad42723f95ac7fb15d4b.json rename to backend/.sqlx/query-34d22638730d62e8bf7020ae0f7ccacf4b258877375ceff2da640fe65d270794.json index 195c9bfe92..c0dbe95a6e 100644 --- a/backend/.sqlx/query-05d6405b2cc6aabf564a10f05402878e9f2a13e0ce0dad42723f95ac7fb15d4b.json +++ b/backend/.sqlx/query-34d22638730d62e8bf7020ae0f7ccacf4b258877375ceff2da640fe65d270794.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT success AS \"success!\"\n FROM v2_as_completed_job WHERE id = $1 AND workspace_id = $2", + "query": "SELECT status = 'success' OR status = 'skipped' AS \"success!\"\n FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -16,8 +16,8 @@ ] }, "nullable": [ - true + null ] }, - "hash": "05d6405b2cc6aabf564a10f05402878e9f2a13e0ce0dad42723f95ac7fb15d4b" + "hash": "34d22638730d62e8bf7020ae0f7ccacf4b258877375ceff2da640fe65d270794" } diff --git a/backend/.sqlx/query-35795d27c4ca69d2f145b4dba08a6ed16c25aea4584103c1b9a3651eb31bfe53.json b/backend/.sqlx/query-35795d27c4ca69d2f145b4dba08a6ed16c25aea4584103c1b9a3651eb31bfe53.json deleted file mode 100644 index 06730d3e26..0000000000 --- a/backend/.sqlx/query-35795d27c4ca69d2f145b4dba08a6ed16c25aea4584103c1b9a3651eb31bfe53.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow_workspace_runnables (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "35795d27c4ca69d2f145b4dba08a6ed16c25aea4584103c1b9a3651eb31bfe53" -} diff --git a/backend/.sqlx/query-37011e7f4cdfc87294e44252cca4f4683a12b82b972842f88f5c01111580224d.json b/backend/.sqlx/query-37011e7f4cdfc87294e44252cca4f4683a12b82b972842f88f5c01111580224d.json new file mode 100644 index 0000000000..5ab1ed1bc3 --- /dev/null +++ b/backend/.sqlx/query-37011e7f4cdfc87294e44252cca4f4683a12b82b972842f88f5c01111580224d.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n capture\n SET \n path = $1\n WHERE \n path = $2 \n AND workspace_id = $3 \n AND is_flow = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "37011e7f4cdfc87294e44252cca4f4683a12b82b972842f88f5c01111580224d" +} diff --git a/backend/.sqlx/query-3734117167a1269f78b3949eed005db96faeec6c18500ad96087cd06c2c85a8b.json b/backend/.sqlx/query-3734117167a1269f78b3949eed005db96faeec6c18500ad96087cd06c2c85a8b.json new file mode 100644 index 0000000000..6a3afafb5e --- /dev/null +++ b/backend/.sqlx/query-3734117167a1269f78b3949eed005db96faeec6c18500ad96087cd06c2c85a8b.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS (SELECT 1\n FROM workspace_settings\n WHERE workspace_id <> $1\n AND slack_command_script IS NOT NULL\n AND slack_team_id IS NOT NULL\n AND slack_team_id = (SELECT slack_team_id FROM workspace_settings WHERE workspace_id = $1))\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "3734117167a1269f78b3949eed005db96faeec6c18500ad96087cd06c2c85a8b" +} diff --git a/backend/.sqlx/query-2f2ef9b1ccff527c48fa01cf1b78cd0e58c8d534ac22ec0356d82a854b31d087.json b/backend/.sqlx/query-38b3945c04ae58aace881ed4d6aa4da72e61eeb8e081d2e3d1ab99a4fe450350.json similarity index 57% rename from backend/.sqlx/query-2f2ef9b1ccff527c48fa01cf1b78cd0e58c8d534ac22ec0356d82a854b31d087.json rename to backend/.sqlx/query-38b3945c04ae58aace881ed4d6aa4da72e61eeb8e081d2e3d1ab99a4fe450350.json index 77e16162bd..f4ed338505 100644 --- a/backend/.sqlx/query-2f2ef9b1ccff527c48fa01cf1b78cd0e58c8d534ac22ec0356d82a854b31d087.json +++ b/backend/.sqlx/query-38b3945c04ae58aace881ed4d6aa4da72e61eeb8e081d2e3d1ab99a4fe450350.json @@ -1,15 +1,14 @@ { "db_name": "PostgreSQL", - "query": "UPDATE v2_job_runtime SET ping = NULL\n WHERE id = $1 AND ping = $2", + "query": "UPDATE v2_job_runtime SET ping = NULL\n WHERE id = $1", "describe": { "columns": [], "parameters": { "Left": [ - "Uuid", - "Timestamptz" + "Uuid" ] }, "nullable": [] }, - "hash": "2f2ef9b1ccff527c48fa01cf1b78cd0e58c8d534ac22ec0356d82a854b31d087" + "hash": "38b3945c04ae58aace881ed4d6aa4da72e61eeb8e081d2e3d1ab99a4fe450350" } diff --git a/backend/.sqlx/query-4c970f10d345bcdcf956dcbfa22b6e80888e511fb4787cb1a7976878abed1d30.json b/backend/.sqlx/query-38c20dba51b1e2b4b28e5eed51f9071e1d3cf78e89ec467927823177c5a439cc.json similarity index 51% rename from backend/.sqlx/query-4c970f10d345bcdcf956dcbfa22b6e80888e511fb4787cb1a7976878abed1d30.json rename to backend/.sqlx/query-38c20dba51b1e2b4b28e5eed51f9071e1d3cf78e89ec467927823177c5a439cc.json index 26894a04ef..66681c2377 100644 --- a/backend/.sqlx/query-4c970f10d345bcdcf956dcbfa22b6e80888e511fb4787cb1a7976878abed1d30.json +++ b/backend/.sqlx/query-38c20dba51b1e2b4b28e5eed51f9071e1d3cf78e89ec467927823177c5a439cc.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT premium FROM workspace WHERE workspace.id = $1", + "query": "SELECT workspace_id FROM usr WHERE email = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "premium", - "type_info": "Bool" + "name": "workspace_id", + "type_info": "Varchar" } ], "parameters": { @@ -18,5 +18,5 @@ false ] }, - "hash": "4c970f10d345bcdcf956dcbfa22b6e80888e511fb4787cb1a7976878abed1d30" + "hash": "38c20dba51b1e2b4b28e5eed51f9071e1d3cf78e89ec467927823177c5a439cc" } diff --git a/backend/.sqlx/query-d8aa1a5813fedbb22128112fcdf9d16769811cd5d5ad3b66362cc5bb1750ae6b.json b/backend/.sqlx/query-39401cb0db8d367b5beb2be0c13aa7595adae0eac4e4e3a888cb12b972d1a7ce.json similarity index 51% rename from backend/.sqlx/query-d8aa1a5813fedbb22128112fcdf9d16769811cd5d5ad3b66362cc5bb1750ae6b.json rename to backend/.sqlx/query-39401cb0db8d367b5beb2be0c13aa7595adae0eac4e4e3a888cb12b972d1a7ce.json index 99c0212031..9906e0d56b 100644 --- a/backend/.sqlx/query-d8aa1a5813fedbb22128112fcdf9d16769811cd5d5ad3b66362cc5bb1750ae6b.json +++ b/backend/.sqlx/query-39401cb0db8d367b5beb2be0c13aa7595adae0eac4e4e3a888cb12b972d1a7ce.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, edited_by, edited_at, email, extra_perms, is_async, requires_auth, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger\n WHERE workspace_id = $1", + "query": "\n SELECT \n workspace_id, \n path, \n route_path, \n route_path_key,\n workspaced_route,\n script_path, \n summary,\n description,\n is_flow, \n http_method as \"http_method: _\", \n edited_by, \n email, \n edited_at, \n extra_perms, \n is_async, \n authentication_method as \"authentication_method: _\", \n static_asset_config as \"static_asset_config: _\", \n is_static_website,\n authentication_resource_path,\n wrap_body,\n raw_string\n FROM \n http_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", "describe": { "columns": [ { @@ -25,46 +25,31 @@ }, { "ordinal": 4, + "name": "workspaced_route", + "type_info": "Bool" + }, + { + "ordinal": 5, "name": "script_path", "type_info": "Varchar" }, - { - "ordinal": 5, - "name": "is_flow", - "type_info": "Bool" - }, { "ordinal": 6, - "name": "edited_by", + "name": "summary", "type_info": "Varchar" }, { "ordinal": 7, - "name": "edited_at", - "type_info": "Timestamptz" + "name": "description", + "type_info": "Text" }, { "ordinal": 8, - "name": "email", - "type_info": "Varchar" + "name": "is_flow", + "type_info": "Bool" }, { "ordinal": 9, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 10, - "name": "is_async", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "requires_auth", - "type_info": "Bool" - }, - { - "ordinal": 12, "name": "http_method: _", "type_info": { "Custom": { @@ -81,19 +66,79 @@ } } }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "edited_at", + "type_info": "Timestamptz" + }, { "ordinal": 13, - "name": "static_asset_config: _", + "name": "extra_perms", "type_info": "Jsonb" }, { "ordinal": 14, + "name": "is_async", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "authentication_method: _", + "type_info": { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + } + }, + { + "ordinal": 16, + "name": "static_asset_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 17, "name": "is_static_website", "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "authentication_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 19, + "name": "wrap_body", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "raw_string", + "type_info": "Bool" } ], "parameters": { "Left": [ + "Text", "Text" ] }, @@ -104,6 +149,9 @@ false, false, false, + true, + true, + false, false, false, false, @@ -112,8 +160,11 @@ false, false, true, + false, + true, + false, false ] }, - "hash": "d8aa1a5813fedbb22128112fcdf9d16769811cd5d5ad3b66362cc5bb1750ae6b" + "hash": "39401cb0db8d367b5beb2be0c13aa7595adae0eac4e4e3a888cb12b972d1a7ce" } diff --git a/backend/.sqlx/query-3a534b4fc36171efaa7c647f48320b59bbc414cfb92e960c174dd63fc180e187.json b/backend/.sqlx/query-3a534b4fc36171efaa7c647f48320b59bbc414cfb92e960c174dd63fc180e187.json deleted file mode 100644 index 11d343ba4d..0000000000 --- a/backend/.sqlx/query-3a534b4fc36171efaa7c647f48320b59bbc414cfb92e960c174dd63fc180e187.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT tag, dedicated_worker from flow WHERE path = $1 and workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "dedicated_worker", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - true, - true - ] - }, - "hash": "3a534b4fc36171efaa7c647f48320b59bbc414cfb92e960c174dd63fc180e187" -} diff --git a/backend/.sqlx/query-3af32856235690827a8700bb2396f3ab44afe0d1a7c261a626d93fb44c00bdb7.json b/backend/.sqlx/query-3af32856235690827a8700bb2396f3ab44afe0d1a7c261a626d93fb44c00bdb7.json new file mode 100644 index 0000000000..73d2bc7c6a --- /dev/null +++ b/backend/.sqlx/query-3af32856235690827a8700bb2396f3ab44afe0d1a7c261a626d93fb44c00bdb7.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = flow_status - 'approval_conditions'\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "3af32856235690827a8700bb2396f3ab44afe0d1a7c261a626d93fb44c00bdb7" +} diff --git a/backend/.sqlx/query-9897705b7c265333559df757a725711a40924ec551a7589a64ee96f8aa7f6a21.json b/backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json similarity index 52% rename from backend/.sqlx/query-9897705b7c265333559df757a725711a40924ec551a7589a64ee96f8aa7f6a21.json rename to backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json index abad579224..82d5378e40 100644 --- a/backend/.sqlx/query-9897705b7c265333559df757a725711a40924ec551a7589a64ee96f8aa7f6a21.json +++ b/backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT set_config('session.folders_write', $1, true)", + "query": "SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "set_config", - "type_info": "Text" + "name": "count", + "type_info": "Int8" } ], "parameters": { @@ -18,5 +18,5 @@ null ] }, - "hash": "9897705b7c265333559df757a725711a40924ec551a7589a64ee96f8aa7f6a21" + "hash": "3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562" } diff --git a/backend/.sqlx/query-3d58b5861c62f0b092b6b95c17ba1dac2cbcf00db116624bd2fe27a4d0dfb436.json b/backend/.sqlx/query-3d58b5861c62f0b092b6b95c17ba1dac2cbcf00db116624bd2fe27a4d0dfb436.json deleted file mode 100644 index 4f1fe255f9..0000000000 --- a/backend/.sqlx/query-3d58b5861c62f0b092b6b95c17ba1dac2cbcf00db116624bd2fe27a4d0dfb436.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['approval_conditions'], $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "3d58b5861c62f0b092b6b95c17ba1dac2cbcf00db116624bd2fe27a4d0dfb436" -} diff --git a/backend/.sqlx/query-3e0cdd500dffc3bd1d8374ca3cc8fd60ce778c2fece27637d9985d4650778653.json b/backend/.sqlx/query-3e0cdd500dffc3bd1d8374ca3cc8fd60ce778c2fece27637d9985d4650778653.json deleted file mode 100644 index 9f5cb1d19d..0000000000 --- a/backend/.sqlx/query-3e0cdd500dffc3bd1d8374ca3cc8fd60ce778c2fece27637d9985d4650778653.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['cleanup_module', 'flow_jobs_to_clean'], COALESCE(flow_status->'cleanup_module'->'flow_jobs_to_clean', '[]'::jsonb) || $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "3e0cdd500dffc3bd1d8374ca3cc8fd60ce778c2fece27637d9985d4650778653" -} diff --git a/backend/.sqlx/query-a3ccf362b4f6df400b3c7a084795dbf541eb14c5c374656ffb96da7283a2a6f1.json b/backend/.sqlx/query-3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0.json similarity index 50% rename from backend/.sqlx/query-a3ccf362b4f6df400b3c7a084795dbf541eb14c5c374656ffb96da7283a2a6f1.json rename to backend/.sqlx/query-3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0.json index bc5e2351f9..e98bd34c2d 100644 --- a/backend/.sqlx/query-a3ccf362b4f6df400b3c7a084795dbf541eb14c5c374656ffb96da7283a2a6f1.json +++ b/backend/.sqlx/query-3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0.json @@ -1,23 +1,22 @@ { "db_name": "PostgreSQL", - "query": "SELECT concurrency_key FROM script WHERE hash = $1 AND workspace_id = $2", + "query": "SELECT path FROM script WHERE workspace_id = $1 AND archived = false", "describe": { "columns": [ { "ordinal": 0, - "name": "concurrency_key", + "name": "path", "type_info": "Varchar" } ], "parameters": { "Left": [ - "Int8", "Text" ] }, "nullable": [ - true + false ] }, - "hash": "a3ccf362b4f6df400b3c7a084795dbf541eb14c5c374656ffb96da7283a2a6f1" + "hash": "3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0" } diff --git a/backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json b/backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json similarity index 68% rename from backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json rename to backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json index 6c92e12f6a..321831d724 100644 --- a/backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json +++ b/backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval \n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", + "query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval\n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", "describe": { "columns": [ { @@ -22,5 +22,5 @@ null ] }, - "hash": "ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1" + "hash": "3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8" } diff --git a/backend/.sqlx/query-3e3d12a51cb524fbd3d6949e150cb608acfbe8c8eade1939e813086380c205e0.json b/backend/.sqlx/query-3e3d12a51cb524fbd3d6949e150cb608acfbe8c8eade1939e813086380c205e0.json deleted file mode 100644 index d6fd72a8b5..0000000000 --- a/backend/.sqlx/query-3e3d12a51cb524fbd3d6949e150cb608acfbe8c8eade1939e813086380c205e0.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) VALUES ($1, $2)\n ON CONFLICT (concurrency_id) \n DO UPDATE SET job_uuids = jsonb_set(concurrency_counter.job_uuids, array[$3], '{}')\n RETURNING (SELECT COUNT(*) FROM jsonb_object_keys(job_uuids))", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Varchar", - "Jsonb", - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "3e3d12a51cb524fbd3d6949e150cb608acfbe8c8eade1939e813086380c205e0" -} diff --git a/backend/.sqlx/query-3f05e6186050a7ce6d8efb41067d3c5282319fe7e041f114e02fb22b91716637.json b/backend/.sqlx/query-3f05e6186050a7ce6d8efb41067d3c5282319fe7e041f114e02fb22b91716637.json new file mode 100644 index 0000000000..86d22794c6 --- /dev/null +++ b/backend/.sqlx/query-3f05e6186050a7ce6d8efb41067d3c5282319fe7e041f114e02fb22b91716637.json @@ -0,0 +1,60 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n http_trigger \n SET \n route_path = $1, \n route_path_key = $2, \n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7, \n path = $8, \n is_flow = $9, \n http_method = $10, \n static_asset_config = $11, \n edited_by = $12, \n email = $13, \n is_async = $14, \n authentication_method = $15, \n summary = $16,\n description = $17,\n edited_at = now(), \n is_static_website = $18\n WHERE \n workspace_id = $19 AND \n path = $20\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Bool", + "Bool", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Bool", + { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + }, + "Jsonb", + "Varchar", + "Varchar", + "Bool", + { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + }, + "Varchar", + "Text", + "Bool", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "3f05e6186050a7ce6d8efb41067d3c5282319fe7e041f114e02fb22b91716637" +} diff --git a/backend/.sqlx/query-3f08ffbb5c71b873a9e164ecb0b10fffb37599f2a703885ee723cb9290fed13e.json b/backend/.sqlx/query-3f08ffbb5c71b873a9e164ecb0b10fffb37599f2a703885ee723cb9290fed13e.json new file mode 100644 index 0000000000..9f5e2e9d8f --- /dev/null +++ b/backend/.sqlx/query-3f08ffbb5c71b873a9e164ecb0b10fffb37599f2a703885ee723cb9290fed13e.json @@ -0,0 +1,91 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH job_info AS (\n -- Query for Teams (running jobs)\n SELECT\n parent.job_kind AS \"job_kind!: JobKind\",\n parent.script_hash AS \"script_hash: ScriptHash\",\n parent.raw_flow AS \"raw_flow: sqlx::types::Json>\",\n child.parent_job AS \"parent_job: Uuid\",\n parent.created_at AS \"created_at!: chrono::NaiveDateTime\",\n parent.created_by AS \"created_by!\",\n parent.script_path,\n parent.args AS \"args: sqlx::types::Json>\"\n FROM v2_as_queue child\n JOIN v2_as_queue parent ON parent.id = child.parent_job\n WHERE child.id = $1 AND child.workspace_id = $2\n UNION ALL\n -- Query for Slack (completed jobs)\n SELECT\n v2_as_queue.job_kind AS \"job_kind!: JobKind\",\n v2_as_queue.script_hash AS \"script_hash: ScriptHash\",\n v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json>\",\n v2_as_completed_job.parent_job AS \"parent_job: Uuid\",\n v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\",\n v2_as_completed_job.created_by AS \"created_by!\",\n v2_as_queue.script_path,\n v2_as_queue.args AS \"args: sqlx::types::Json>\"\n FROM v2_as_queue\n JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id\n WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2\n )\n SELECT * FROM job_info LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "job_kind!: JobKind", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 1, + "name": "script_hash: ScriptHash", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "raw_flow: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 3, + "name": "parent_job: Uuid", + "type_info": "Uuid" + }, + { + "ordinal": 4, + "name": "created_at!: chrono::NaiveDateTime", + "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "args: sqlx::types::Json>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [ + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "hash": "3f08ffbb5c71b873a9e164ecb0b10fffb37599f2a703885ee723cb9290fed13e" +} diff --git a/backend/.sqlx/query-3f5520e0ea00569bf169da9abde31617043fcc0bea3ef62c50fcd881f3d48605.json b/backend/.sqlx/query-3f5520e0ea00569bf169da9abde31617043fcc0bea3ef62c50fcd881f3d48605.json new file mode 100644 index 0000000000..b225a5a1da --- /dev/null +++ b/backend/.sqlx/query-3f5520e0ea00569bf169da9abde31617043fcc0bea3ef62c50fcd881f3d48605.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4,\n $5,\n $6, \n $7, \n $8, \n $9,\n $10,\n $11,\n $12\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + { + "Custom": { + "name": "delivery_mode", + "kind": { + "Enum": [ + "push", + "pull" + ] + } + } + }, + "Jsonb", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Bool", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "3f5520e0ea00569bf169da9abde31617043fcc0bea3ef62c50fcd881f3d48605" +} diff --git a/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json b/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json new file mode 100644 index 0000000000..3bb4151982 --- /dev/null +++ b/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) \n VALUES ($1, $2)\n ON CONFLICT (concurrency_id)\n DO UPDATE SET job_uuids = jsonb_set(concurrency_counter.job_uuids, array[$3], '{}')", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60" +} diff --git a/backend/.sqlx/query-41e557e1b63b13c9fcc195901c0bd0de7e03c539ee046955543d9693551246f7.json b/backend/.sqlx/query-41e557e1b63b13c9fcc195901c0bd0de7e03c539ee046955543d9693551246f7.json deleted file mode 100644 index 0df0167c42..0000000000 --- a/backend/.sqlx/query-41e557e1b63b13c9fcc195901c0bd0de7e03c539ee046955543d9693551246f7.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM capture WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "41e557e1b63b13c9fcc195901c0bd0de7e03c539ee046955543d9693551246f7" -} diff --git a/backend/.sqlx/query-4228b098883408323bd8413ee094454b95962047458a6927d19ac0d3e7b3f0fa.json b/backend/.sqlx/query-4228b098883408323bd8413ee094454b95962047458a6927d19ac0d3e7b3f0fa.json new file mode 100644 index 0000000000..365bce7366 --- /dev/null +++ b/backend/.sqlx/query-4228b098883408323bd8413ee094454b95962047458a6927d19ac0d3e7b3f0fa.json @@ -0,0 +1,169 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n workspace_id, \n workspaced_route,\n path, \n route_path, \n route_path_key, \n authentication_resource_path,\n script_path, \n is_flow, \n summary,\n description,\n edited_by, \n edited_at, \n email, \n extra_perms, \n is_async, \n authentication_method AS \"authentication_method: _\", \n http_method AS \"http_method: _\", \n static_asset_config AS \"static_asset_config: _\", \n is_static_website,\n wrap_body,\n raw_string\n FROM http_trigger\n WHERE workspace_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "workspaced_route", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "route_path", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "route_path_key", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "authentication_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 12, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "is_async", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "authentication_method: _", + "type_info": { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + } + }, + { + "ordinal": 16, + "name": "http_method: _", + "type_info": { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + } + }, + { + "ordinal": 17, + "name": "static_asset_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "is_static_website", + "type_info": "Bool" + }, + { + "ordinal": 19, + "name": "wrap_body", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "raw_string", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false + ] + }, + "hash": "4228b098883408323bd8413ee094454b95962047458a6927d19ac0d3e7b3f0fa" +} diff --git a/backend/.sqlx/query-429aef2c320a152b16fe20c1ab84aab41142897db108dc8cbc2c51abb2e30c7c.json b/backend/.sqlx/query-429aef2c320a152b16fe20c1ab84aab41142897db108dc8cbc2c51abb2e30c7c.json deleted file mode 100644 index c40a18e4c9..0000000000 --- a/backend/.sqlx/query-429aef2c320a152b16fe20c1ab84aab41142897db108dc8cbc2c51abb2e30c7c.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "429aef2c320a152b16fe20c1ab84aab41142897db108dc8cbc2c51abb2e30c7c" -} diff --git a/backend/.sqlx/query-c5270ee815689e42b65df507b850da43239c9a5aaea41c9aed7ed33a6219a534.json b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json similarity index 59% rename from backend/.sqlx/query-c5270ee815689e42b65df507b850da43239c9a5aaea41c9aed7ed33a6219a534.json rename to backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json index 8c2a88d485..c1a38be77e 100644 --- a/backend/.sqlx/query-c5270ee815689e42b65df507b850da43239c9a5aaea41c9aed7ed33a6219a534.json +++ b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE capture_config SET last_client_ping = now() WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4", + "query": "\n UPDATE \n capture_config\n SET \n last_client_ping = NOW()\n WHERE \n workspace_id = $1 \n AND path = $2 \n AND is_flow = $3 \n AND trigger_kind = $4\n ", "describe": { "columns": [], "parameters": { @@ -21,7 +21,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -30,5 +31,5 @@ }, "nullable": [] }, - "hash": "c5270ee815689e42b65df507b850da43239c9a5aaea41c9aed7ed33a6219a534" + "hash": "42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2" } diff --git a/backend/.sqlx/query-433da02be85347333323480f7f279e9bd7e1b8348e91b33030df7181a55798a6.json b/backend/.sqlx/query-433da02be85347333323480f7f279e9bd7e1b8348e91b33030df7181a55798a6.json new file mode 100644 index 0000000000..bee9c556b2 --- /dev/null +++ b/backend/.sqlx/query-433da02be85347333323480f7f279e9bd7e1b8348e91b33030df7181a55798a6.json @@ -0,0 +1,30 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT j.id, jc.flow_status AS \"flow_status!: Json\"\n FROM v2_job j\n JOIN v2_job_completed jc ON j.id = jc.id\n WHERE j.parent_job = $1 AND j.workspace_id = $2 AND j.created_at >= $3 AND jc.flow_status IS NOT NULL", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "flow_status!: Json", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text", + "Timestamptz" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "433da02be85347333323480f7f279e9bd7e1b8348e91b33030df7181a55798a6" +} diff --git a/backend/.sqlx/query-43aa468aac174529a74e6108af55a383f8a20e98b8c502929f4dc5041a55e72f.json b/backend/.sqlx/query-43aa468aac174529a74e6108af55a383f8a20e98b8c502929f4dc5041a55e72f.json deleted file mode 100644 index e33b0dc0b8..0000000000 --- a/backend/.sqlx/query-43aa468aac174529a74e6108af55a383f8a20e98b8c502929f4dc5041a55e72f.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT EXISTS (\n SELECT 1 FROM v2_as_completed_job \n WHERE workspace_id = $2 \n AND (job_kind = 'appscript' OR job_kind = 'preview')\n AND created_by = 'anonymous' \n AND started_at > now() - interval '3 hours'\n AND script_path LIKE $3 || '/%' \n AND result @> ('{\"s3\":\"' || $1 || '\"}')::jsonb \n )", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "43aa468aac174529a74e6108af55a383f8a20e98b8c502929f4dc5041a55e72f" -} diff --git a/backend/.sqlx/query-43c8cd9f8560412bb06d9966ccfa2524943ba277c5f9a37c06bd5ee14fd46bda.json b/backend/.sqlx/query-43c8cd9f8560412bb06d9966ccfa2524943ba277c5f9a37c06bd5ee14fd46bda.json new file mode 100644 index 0000000000..7d6ecdbf32 --- /dev/null +++ b/backend/.sqlx/query-43c8cd9f8560412bb06d9966ccfa2524943ba277c5f9a37c06bd5ee14fd46bda.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n capture_config \n SET \n error = $1, \n server_id = NULL, \n last_server_ping = NULL \n WHERE \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'gcp'\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "43c8cd9f8560412bb06d9966ccfa2524943ba277c5f9a37c06bd5ee14fd46bda" +} diff --git a/backend/.sqlx/query-443bd83bcea1d37c79cb080095343c98104529879f991c49585cd181e34aa827.json b/backend/.sqlx/query-443bd83bcea1d37c79cb080095343c98104529879f991c49585cd181e34aa827.json new file mode 100644 index 0000000000..65b21050c0 --- /dev/null +++ b/backend/.sqlx/query-443bd83bcea1d37c79cb080095343c98104529879f991c49585cd181e34aa827.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT content FROM script WHERE path = $1 AND workspace_id = $2\n AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND\n workspace_id = $2)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "content", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "443bd83bcea1d37c79cb080095343c98104529879f991c49585cd181e34aa827" +} diff --git a/backend/.sqlx/query-4455c7e8aa7616f3d547c5eb7d93c840d8aff3df4a6926f569792f69b2e9601f.json b/backend/.sqlx/query-4455c7e8aa7616f3d547c5eb7d93c840d8aff3df4a6926f569792f69b2e9601f.json new file mode 100644 index 0000000000..8c90c0c765 --- /dev/null +++ b/backend/.sqlx/query-4455c7e8aa7616f3d547c5eb7d93c840d8aff3df4a6926f569792f69b2e9601f.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "select hash from script where path = $1 AND workspace_id = $2 AND deleted = false AND lock IS not NULL AND lock_error_logs IS NULL ORDER BY created_at DESC LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hash", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "4455c7e8aa7616f3d547c5eb7d93c840d8aff3df4a6926f569792f69b2e9601f" +} diff --git a/backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json b/backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json deleted file mode 100644 index 46a8d2e4f0..0000000000 --- a/backend/.sqlx/query-4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-44b9bea3651edc8ee732def1241b3d956c004376102ccc1707fc016801599dbd.json b/backend/.sqlx/query-44b9bea3651edc8ee732def1241b3d956c004376102ccc1707fc016801599dbd.json new file mode 100644 index 0000000000..87d384f5a1 --- /dev/null +++ b/backend/.sqlx/query-44b9bea3651edc8ee732def1241b3d956c004376102ccc1707fc016801599dbd.json @@ -0,0 +1,36 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger \n SET \n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6, \n edited_by = $7, \n email = $8,\n script_path = $9,\n path = $10,\n enabled = $11,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $12 AND \n path = $13\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + { + "Custom": { + "name": "delivery_mode", + "kind": { + "Enum": [ + "push", + "pull" + ] + } + } + }, + "Jsonb", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "44b9bea3651edc8ee732def1241b3d956c004376102ccc1707fc016801599dbd" +} diff --git a/backend/.sqlx/query-44ce12725e09c1e32d82dfe9539ee42065315ac8df5fc8bd8004c0992e6f1bdb.json b/backend/.sqlx/query-44ce12725e09c1e32d82dfe9539ee42065315ac8df5fc8bd8004c0992e6f1bdb.json new file mode 100644 index 0000000000..1369de2911 --- /dev/null +++ b/backend/.sqlx/query-44ce12725e09c1e32d82dfe9539ee42065315ac8df5fc8bd8004c0992e6f1bdb.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_runnable_dependencies SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "44ce12725e09c1e32d82dfe9539ee42065315ac8df5fc8bd8004c0992e6f1bdb" +} diff --git a/backend/.sqlx/query-45d616c92ebcbe30a563e1fa7d2d0e53392e238144b039cfe042587d7fe1dea3.json b/backend/.sqlx/query-45d616c92ebcbe30a563e1fa7d2d0e53392e238144b039cfe042587d7fe1dea3.json deleted file mode 100644 index 052238874a..0000000000 --- a/backend/.sqlx/query-45d616c92ebcbe30a563e1fa7d2d0e53392e238144b039cfe042587d7fe1dea3.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n COALESCE(workflow_as_code_status, '{}'::jsonb),\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'started_at'],\n to_jsonb(now()::text)\n )\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "45d616c92ebcbe30a563e1fa7d2d0e53392e238144b039cfe042587d7fe1dea3" -} diff --git a/backend/.sqlx/query-45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d.json b/backend/.sqlx/query-45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d.json new file mode 100644 index 0000000000..edb113673b --- /dev/null +++ b/backend/.sqlx/query-45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM metrics WHERE created_at < NOW() - INTERVAL '180 day'", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d" +} diff --git a/backend/.sqlx/query-4622d28e2fa09bc60b9d0c79397efe0ca030638ded82c2ffd2155cacdf36ec11.json b/backend/.sqlx/query-4622d28e2fa09bc60b9d0c79397efe0ca030638ded82c2ffd2155cacdf36ec11.json new file mode 100644 index 0000000000..f30ab8d49d --- /dev/null +++ b/backend/.sqlx/query-4622d28e2fa09bc60b9d0c79397efe0ca030638ded82c2ffd2155cacdf36ec11.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "4622d28e2fa09bc60b9d0c79397efe0ca030638ded82c2ffd2155cacdf36ec11" +} diff --git a/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json b/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json index 8622eb9125..561e202d1d 100644 --- a/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json +++ b/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json @@ -68,7 +68,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } diff --git a/backend/.sqlx/query-49943f69ed74bc889120dcd2571e8e868a4f4795933044ff95b20d8df45cd145.json b/backend/.sqlx/query-49943f69ed74bc889120dcd2571e8e868a4f4795933044ff95b20d8df45cd145.json new file mode 100644 index 0000000000..e2001954e1 --- /dev/null +++ b/backend/.sqlx/query-49943f69ed74bc889120dcd2571e8e868a4f4795933044ff95b20d8df45cd145.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_job_completed_completed_at ON v2_job_completed (completed_at DESC)", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "49943f69ed74bc889120dcd2571e8e868a4f4795933044ff95b20d8df45cd145" +} diff --git a/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json b/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json new file mode 100644 index 0000000000..65fa6cc9eb --- /dev/null +++ b/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json @@ -0,0 +1,278 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 2, + "name": "args: sqlx::types::Json>>", + "type_info": "Jsonb" + }, + { + "ordinal": 3, + "name": "parent_job", + "type_info": "Uuid" + }, + { + "ordinal": 4, + "name": "created_by", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "started_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "scheduled_for", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "runnable_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "kind: JobKind", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 9, + "name": "runnable_id: ScriptHash", + "type_info": "Int8" + }, + { + "ordinal": 10, + "name": "canceled_reason", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "canceled_by", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "permissioned_as_email", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "flow_status: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "script_lang: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + } + }, + { + "ordinal": 17, + "name": "same_worker", + "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "pre_run_error", + "type_info": "Text" + }, + { + "ordinal": 19, + "name": "concurrent_limit", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "concurrency_time_window_s", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "flow_innermost_root_job", + "type_info": "Uuid" + }, + { + "ordinal": 22, + "name": "timeout", + "type_info": "Int4" + }, + { + "ordinal": 23, + "name": "flow_step_id", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "cache_ttl", + "type_info": "Int4" + }, + { + "ordinal": 25, + "name": "priority", + "type_info": "Int2" + }, + { + "ordinal": 26, + "name": "preprocessed", + "type_info": "Bool" + }, + { + "ordinal": 27, + "name": "script_entrypoint_override", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "trigger", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "trigger_kind: JobTriggerKind", + "type_info": { + "Custom": { + "name": "job_trigger_kind", + "kind": { + "Enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email", + "nats", + "schedule", + "app", + "ui", + "postgres", + "sqs", + "gcp" + ] + } + } + } + }, + { + "ordinal": 30, + "name": "visible_to_owner", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + true, + true, + false, + true, + false, + true, + false, + true, + true, + true, + false, + false, + true, + false, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ] + }, + "hash": "4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0" +} diff --git a/backend/.sqlx/query-f8f893fb4f6f8c16bffb233a585df0b8c007c24993979fdc013aa57583905f50.json b/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json similarity index 57% rename from backend/.sqlx/query-f8f893fb4f6f8c16bffb233a585df0b8c007c24993979fdc013aa57583905f50.json rename to backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json index 759f91c962..ab5b04afc6 100644 --- a/backend/.sqlx/query-f8f893fb4f6f8c16bffb233a585df0b8c007c24993979fdc013aa57583905f50.json +++ b/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT owner, premium, usage.usage as \"usage?\", workspace_settings.customer_id, workspace_settings.plan, workspace_settings.automatic_billing FROM workspace LEFT JOIN workspace_settings ON workspace_settings.workspace_id = $1 LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1", + "query": "SELECT owner, premium, usage.usage as \"usage?\", workspace_settings.customer_id, workspace_settings.plan FROM workspace LEFT JOIN workspace_settings ON workspace_settings.workspace_id = $1 LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1", "describe": { "columns": [ { @@ -27,11 +27,6 @@ "ordinal": 4, "name": "plan", "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "automatic_billing", - "type_info": "Bool" } ], "parameters": { @@ -44,9 +39,8 @@ false, false, true, - true, - false + true ] }, - "hash": "f8f893fb4f6f8c16bffb233a585df0b8c007c24993979fdc013aa57583905f50" + "hash": "4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25" } diff --git a/backend/.sqlx/query-4c9cf8c3176abc2b8b9a1c3f671949e16830671a458d5a73762cd8545d26172d.json b/backend/.sqlx/query-4c9cf8c3176abc2b8b9a1c3f671949e16830671a458d5a73762cd8545d26172d.json deleted file mode 100644 index 0d108538dc..0000000000 --- a/backend/.sqlx/query-4c9cf8c3176abc2b8b9a1c3f671949e16830671a458d5a73762cd8545d26172d.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'],\n $3\n )\n WHERE id = $4", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "4c9cf8c3176abc2b8b9a1c3f671949e16830671a458d5a73762cd8545d26172d" -} diff --git a/backend/.sqlx/query-4e07dcc1ba4396ada2f1080a400ad9fad00b1d77ea44c8639b7598c5953635ff.json b/backend/.sqlx/query-4e07dcc1ba4396ada2f1080a400ad9fad00b1d77ea44c8639b7598c5953635ff.json new file mode 100644 index 0000000000..506dce48dc --- /dev/null +++ b/backend/.sqlx/query-4e07dcc1ba4396ada2f1080a400ad9fad00b1d77ea44c8639b7598c5953635ff.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "4e07dcc1ba4396ada2f1080a400ad9fad00b1d77ea44c8639b7598c5953635ff" +} diff --git a/backend/.sqlx/query-4e3469185637ad8f06673261bfae1b38c30f7d2689baf647ea4dc6b398c3a651.json b/backend/.sqlx/query-4e3469185637ad8f06673261bfae1b38c30f7d2689baf647ea4dc6b398c3a651.json new file mode 100644 index 0000000000..c6a6f72876 --- /dev/null +++ b/backend/.sqlx/query-4e3469185637ad8f06673261bfae1b38c30f7d2689baf647ea4dc6b398c3a651.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT elem\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n AND (elem->>'installation_id')::bigint = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "elem", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + null + ] + }, + "hash": "4e3469185637ad8f06673261bfae1b38c30f7d2689baf647ea4dc6b398c3a651" +} diff --git a/backend/.sqlx/query-4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25.json b/backend/.sqlx/query-4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25.json new file mode 100644 index 0000000000..8176539a30 --- /dev/null +++ b/backend/.sqlx/query-4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n tag, \n script_lang AS \"script_lang!: _\"\n FROM \n v2_job\n WHERE \n id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "script_lang!: _", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25" +} diff --git a/backend/.sqlx/query-4edf05cbf35325d444de0e74ee070aafd27ef61c940daba186e7e66f668c31ed.json b/backend/.sqlx/query-4edf05cbf35325d444de0e74ee070aafd27ef61c940daba186e7e66f668c31ed.json deleted file mode 100644 index 4eae8c22fb..0000000000 --- a/backend/.sqlx/query-4edf05cbf35325d444de0e74ee070aafd27ef61c940daba186e7e66f668c31ed.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET ai_resource = $1, code_completion_model = $2, ai_models = $3 WHERE workspace_id = $4", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Varchar", - "VarcharArray", - "Text" - ] - }, - "nullable": [] - }, - "hash": "4edf05cbf35325d444de0e74ee070aafd27ef61c940daba186e7e66f668c31ed" -} diff --git a/backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json b/backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json deleted file mode 100644 index 52136dd33e..0000000000 --- a/backend/.sqlx/query-4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json similarity index 57% rename from backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json rename to backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json index 61c4d20b04..15b107811b 100644 --- a/backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json +++ b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as \"payload!: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6", + "query": "\n SELECT \n id, \n created_at, \n trigger_kind AS \"trigger_kind: _\",\n CASE \n WHEN pg_column_size(main_args) < 40000 THEN main_args \n ELSE '\"WINDMILL_TOO_BIG\"'::jsonb \n END AS \"main_args!: _\",\n CASE\n WHEN pg_column_size(preprocessor_args) < 40000 THEN preprocessor_args\n ELSE '\"WINDMILL_TOO_BIG\"'::jsonb\n END AS \"preprocessor_args: _\"\n FROM \n capture\n WHERE \n workspace_id = $1 \n AND path = $2 \n AND is_flow = $3 \n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY \n created_at DESC\n OFFSET $5\n LIMIT $6\n ", "describe": { "columns": [ { @@ -29,7 +29,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -37,12 +38,12 @@ }, { "ordinal": 3, - "name": "payload!: _", + "name": "main_args!: _", "type_info": "Jsonb" }, { "ordinal": 4, - "name": "trigger_extra: _", + "name": "preprocessor_args: _", "type_info": "Jsonb" } ], @@ -64,7 +65,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -78,8 +80,8 @@ false, false, null, - true + null ] }, - "hash": "5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd" + "hash": "4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36" } diff --git a/backend/.sqlx/query-505250098ab003ff0ca30046df283e54bf44be74305070f10a5720a04c4789f3.json b/backend/.sqlx/query-505250098ab003ff0ca30046df283e54bf44be74305070f10a5720a04c4789f3.json new file mode 100644 index 0000000000..fb3fbe4f0e --- /dev/null +++ b/backend/.sqlx/query-505250098ab003ff0ca30046df283e54bf44be74305070f10a5720a04c4789f3.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_queue\n SET canceled_by = $1\n , canceled_reason = $2\nWHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "505250098ab003ff0ca30046df283e54bf44be74305070f10a5720a04c4789f3" +} diff --git a/backend/.sqlx/query-e968e879d3c52f7dd502c3cd15fc8fbd983a4a3ab25648c562497a27c74b5c8c.json b/backend/.sqlx/query-506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773.json similarity index 64% rename from backend/.sqlx/query-e968e879d3c52f7dd502c3cd15fc8fbd983a4a3ab25648c562497a27c74b5c8c.json rename to backend/.sqlx/query-506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773.json index 3bacda16d9..50d4a9595d 100644 --- a/backend/.sqlx/query-e968e879d3c52f7dd502c3cd15fc8fbd983a4a3ab25648c562497a27c74b5c8c.json +++ b/backend/.sqlx/query-506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5", + "query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "e968e879d3c52f7dd502c3cd15fc8fbd983a4a3ab25648c562497a27c74b5c8c" + "hash": "506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773" } diff --git a/backend/.sqlx/query-51ef2ee9dc252f7accc41b89d13a2aa1e4c11d4860894e9e661b6ee3bccd8522.json b/backend/.sqlx/query-51ef2ee9dc252f7accc41b89d13a2aa1e4c11d4860894e9e661b6ee3bccd8522.json new file mode 100644 index 0000000000..7cd9c5ce22 --- /dev/null +++ b/backend/.sqlx/query-51ef2ee9dc252f7accc41b89d13a2aa1e4c11d4860894e9e661b6ee3bccd8522.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_completed c SET\n result = NULL,\n deleted = TRUE\n FROM v2_job j\n WHERE c.id = $1\n AND j.id = c.id\n AND c.workspace_id = $2\n AND ($3::TEXT[] IS NULL OR tag = ANY($3))\n RETURNING c.id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text", + "TextArray" + ] + }, + "nullable": [ + false + ] + }, + "hash": "51ef2ee9dc252f7accc41b89d13a2aa1e4c11d4860894e9e661b6ee3bccd8522" +} diff --git a/backend/.sqlx/query-122090a0f89e5248a0a0f199ebd24582fdb302883aebd2da187ac0084e767ea3.json b/backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json similarity index 52% rename from backend/.sqlx/query-122090a0f89e5248a0a0f199ebd24582fdb302883aebd2da187ac0084e767ea3.json rename to backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json index a329998c95..5f8a8dc277 100644 --- a/backend/.sqlx/query-122090a0f89e5248a0a0f199ebd24582fdb302883aebd2da187ac0084e767ea3.json +++ b/backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT set_config('session.pgroups', $1, true)", + "query": "SELECT COUNT(*) FROM flow WHERE workspace_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "set_config", - "type_info": "Text" + "name": "count", + "type_info": "Int8" } ], "parameters": { @@ -18,5 +18,5 @@ null ] }, - "hash": "122090a0f89e5248a0a0f199ebd24582fdb302883aebd2da187ac0084e767ea3" + "hash": "52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13" } diff --git a/backend/.sqlx/query-2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867.json b/backend/.sqlx/query-5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd.json similarity index 64% rename from backend/.sqlx/query-2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867.json rename to backend/.sqlx/query-5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd.json index 5d1af3380d..f9c6646e3a 100644 --- a/backend/.sqlx/query-2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867.json +++ b/backend/.sqlx/query-5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd.json @@ -1,80 +1,95 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", + "query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", "describe": { "columns": [ { "ordinal": 0, + "name": "aws_auth_resource_type: _", + "type_info": { + "Custom": { + "name": "aws_auth_resource_type", + "kind": { + "Enum": [ + "oidc", + "credentials" + ] + } + } + } + }, + { + "ordinal": 1, "name": "aws_resource_path", "type_info": "Varchar" }, { - "ordinal": 1, + "ordinal": 2, "name": "message_attributes", "type_info": "TextArray" }, { - "ordinal": 2, + "ordinal": 3, "name": "queue_url", "type_info": "Varchar" }, { - "ordinal": 3, + "ordinal": 4, "name": "workspace_id", "type_info": "Varchar" }, { - "ordinal": 4, + "ordinal": 5, "name": "path", "type_info": "Varchar" }, { - "ordinal": 5, + "ordinal": 6, "name": "script_path", "type_info": "Varchar" }, { - "ordinal": 6, + "ordinal": 7, "name": "is_flow", "type_info": "Bool" }, { - "ordinal": 7, + "ordinal": 8, "name": "edited_by", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 9, "name": "email", "type_info": "Varchar" }, { - "ordinal": 9, + "ordinal": 10, "name": "edited_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "server_id", "type_info": "Varchar" }, { - "ordinal": 11, + "ordinal": 12, "name": "last_server_ping", "type_info": "Timestamptz" }, { - "ordinal": 12, + "ordinal": 13, "name": "extra_perms", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "error", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 15, "name": "enabled", "type_info": "Bool" } @@ -86,6 +101,7 @@ ] }, "nullable": [ + false, false, true, false, @@ -103,5 +119,5 @@ false ] }, - "hash": "2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867" + "hash": "5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd" } diff --git a/backend/.sqlx/query-525a9ef57c7d9fac86cb1bf47868fa6fb782e9d589852e51530cdd1a38322a9d.json b/backend/.sqlx/query-525a9ef57c7d9fac86cb1bf47868fa6fb782e9d589852e51530cdd1a38322a9d.json new file mode 100644 index 0000000000..73c1a95797 --- /dev/null +++ b/backend/.sqlx/query-525a9ef57c7d9fac86cb1bf47868fa6fb782e9d589852e51530cdd1a38322a9d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "525a9ef57c7d9fac86cb1bf47868fa6fb782e9d589852e51530cdd1a38322a9d" +} diff --git a/backend/.sqlx/query-5303206bbed76ee3ddc56d8057d8b82359c182ee2a5da6df35a4375d5d2d1ef7.json b/backend/.sqlx/query-5303206bbed76ee3ddc56d8057d8b82359c182ee2a5da6df35a4375d5d2d1ef7.json new file mode 100644 index 0000000000..f010362e6f --- /dev/null +++ b/backend/.sqlx/query-5303206bbed76ee3ddc56d8057d8b82359c182ee2a5da6df35a4375d5d2d1ef7.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n gcp_resource_path, \n script_path,\n is_flow, \n workspace_id,\n path,\n edited_by,\n email,\n delivery_config AS \"delivery_config: _\"\n FROM\n gcp_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n delivery_type = 'push'::DELIVERY_MODE \n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "gcp_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "delivery_config: _", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true + ] + }, + "hash": "5303206bbed76ee3ddc56d8057d8b82359c182ee2a5da6df35a4375d5d2d1ef7" +} diff --git a/backend/.sqlx/query-3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1.json b/backend/.sqlx/query-5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec.json similarity index 68% rename from backend/.sqlx/query-3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1.json rename to backend/.sqlx/query-5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec.json index 66431239db..f3961847f4 100644 --- a/backend/.sqlx/query-3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1.json +++ b/backend/.sqlx/query-5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2", + "query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2", "describe": { "columns": [ { @@ -67,5 +67,5 @@ true ] }, - "hash": "3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1" + "hash": "5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec" } diff --git a/backend/.sqlx/query-54fee31b61d62598c89cf7d0729079ac1721fe7bd1844f339236379211defc78.json b/backend/.sqlx/query-54fee31b61d62598c89cf7d0729079ac1721fe7bd1844f339236379211defc78.json new file mode 100644 index 0000000000..b41cb7bf8c --- /dev/null +++ b/backend/.sqlx/query-54fee31b61d62598c89cf7d0729079ac1721fe7bd1844f339236379211defc78.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM agent_token_blacklist WHERE token = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "54fee31b61d62598c89cf7d0729079ac1721fe7bd1844f339236379211defc78" +} diff --git a/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json b/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json new file mode 100644 index 0000000000..7f1cdf4aa2 --- /dev/null +++ b/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT fv.id\n FROM flow f\n INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)]\n WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9" +} diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index 14685a8bfa..009d9fe5d1 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -65,7 +65,7 @@ }, { "ordinal": 12, - "name": "ai_resource", + "name": "ai_config", "type_info": "Jsonb" }, { @@ -100,58 +100,48 @@ }, { "ordinal": 19, - "name": "automatic_billing", - "type_info": "Bool" - }, - { - "ordinal": 20, "name": "default_scripts", "type_info": "Jsonb" }, { - "ordinal": 21, + "ordinal": 20, "name": "deploy_ui", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 21, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 23, + "ordinal": 22, "name": "color", "type_info": "Varchar" }, { - "ordinal": 24, + "ordinal": 23, "name": "operator_settings", "type_info": "Jsonb" }, { - "ordinal": 25, - "name": "ai_models", - "type_info": "VarcharArray" - }, - { - "ordinal": 26, - "name": "code_completion_model", - "type_info": "Varchar" - }, - { - "ordinal": 27, + "ordinal": 24, "name": "teams_command_script", "type_info": "Text" }, { - "ordinal": 28, + "ordinal": 25, "name": "teams_team_id", "type_info": "Text" }, { - "ordinal": 29, + "ordinal": 26, "name": "teams_team_name", "type_info": "Text" + }, + { + "ordinal": 27, + "name": "git_app_installations", + "type_info": "Jsonb" } ], "parameters": { @@ -179,17 +169,15 @@ true, true, true, - false, true, true, true, true, true, - false, true, true, true, - true + false ] }, "hash": "55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2" diff --git a/backend/.sqlx/query-2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3.json b/backend/.sqlx/query-56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6.json similarity index 62% rename from backend/.sqlx/query-2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3.json rename to backend/.sqlx/query-56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6.json index 326e45bf24..e5e1ecd5de 100644 --- a/backend/.sqlx/query-2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3.json +++ b/backend/.sqlx/query-56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id", + "query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id", "describe": { "columns": [ { @@ -13,12 +13,13 @@ "Left": [ "Int8", "Text", - "Varchar" + "Varchar", + "Bool" ] }, "nullable": [ false ] }, - "hash": "2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3" + "hash": "56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6" } diff --git a/backend/.sqlx/query-56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7.json b/backend/.sqlx/query-56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7.json new file mode 100644 index 0000000000..9da0554266 --- /dev/null +++ b/backend/.sqlx/query-56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "usage", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7" +} diff --git a/backend/.sqlx/query-5820d34be1a7f7b72e656c692f53146f45ad4a6e584e917a0a86280d8f473c10.json b/backend/.sqlx/query-5820d34be1a7f7b72e656c692f53146f45ad4a6e584e917a0a86280d8f473c10.json new file mode 100644 index 0000000000..b83ec1db19 --- /dev/null +++ b/backend/.sqlx/query-5820d34be1a7f7b72e656c692f53146f45ad4a6e584e917a0a86280d8f473c10.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM v2_job_completed c\n WHERE completed_at <= now() - ($1::bigint::text || ' s')::interval \n RETURNING c.id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "5820d34be1a7f7b72e656c692f53146f45ad4a6e584e917a0a86280d8f473c10" +} diff --git a/backend/.sqlx/query-ca8bf3dda133556ee7051d5436a5d76ae2223d59e9f321a8b6d0c27adc09f741.json b/backend/.sqlx/query-5930b2fa72fd15d692bcf3e14d95f85dd67ab1514d7c48668ab2f10aa6b201ba.json similarity index 51% rename from backend/.sqlx/query-ca8bf3dda133556ee7051d5436a5d76ae2223d59e9f321a8b6d0c27adc09f741.json rename to backend/.sqlx/query-5930b2fa72fd15d692bcf3e14d95f85dd67ab1514d7c48668ab2f10aa6b201ba.json index b7a302213a..d616940b6b 100644 --- a/backend/.sqlx/query-ca8bf3dda133556ee7051d5436a5d76ae2223d59e9f321a8b6d0c27adc09f741.json +++ b/backend/.sqlx/query-5930b2fa72fd15d692bcf3e14d95f85dd67ab1514d7c48668ab2f10aa6b201ba.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)", + "query": "UPDATE v2_job_runtime SET ping = now() WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -10,5 +10,5 @@ }, "nullable": [] }, - "hash": "ca8bf3dda133556ee7051d5436a5d76ae2223d59e9f321a8b6d0c27adc09f741" + "hash": "5930b2fa72fd15d692bcf3e14d95f85dd67ab1514d7c48668ab2f10aa6b201ba" } diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json new file mode 100644 index 0000000000..36ddb8ab9f --- /dev/null +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker)\n SELECT worker_ids.worker FROM worker_ids\n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker\n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "worker", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "TextArray" + ] + }, + "nullable": [ + true + ] + }, + "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" +} diff --git a/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json b/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json new file mode 100644 index 0000000000..a6133a5d59 --- /dev/null +++ b/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM variable WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52" +} diff --git a/backend/.sqlx/query-5a4fa8ff2148d92946e6ac95f70585d082435e0e79a09821a2045e3b550c3276.json b/backend/.sqlx/query-5a4fa8ff2148d92946e6ac95f70585d082435e0e79a09821a2045e3b550c3276.json new file mode 100644 index 0000000000..bec0922efc --- /dev/null +++ b/backend/.sqlx/query-5a4fa8ff2148d92946e6ac95f70585d082435e0e79a09821a2045e3b550c3276.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1),\n ARRAY['step'],\n $2\n )\n WHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "5a4fa8ff2148d92946e6ac95f70585d082435e0e79a09821a2045e3b550c3276" +} diff --git a/backend/.sqlx/query-5e8ba1850b2520bd4bf030f53f1f2e6606bfc0f440fbdbbdf4dd3234068c9345.json b/backend/.sqlx/query-5e8ba1850b2520bd4bf030f53f1f2e6606bfc0f440fbdbbdf4dd3234068c9345.json new file mode 100644 index 0000000000..a3751998fb --- /dev/null +++ b/backend/.sqlx/query-5e8ba1850b2520bd4bf030f53f1f2e6606bfc0f440fbdbbdf4dd3234068c9345.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\nWITH RECURSIVE job_tree AS (\n -- Base case: direct children of the given parent job\n SELECT id, parent_job, 1 AS depth\n FROM v2_job_queue \n INNER JOIN v2_job USING (id)\n WHERE parent_job = $1 AND v2_job.workspace_id = $2\n\n UNION ALL\n\n -- Recursive case: fetch children of previously found jobs\n SELECT q.id, j.parent_job, t.depth + 1\n FROM v2_job_queue q\n INNER JOIN v2_job j USING (id)\n INNER JOIN job_tree t ON t.id = j.parent_job\n WHERE j.workspace_id = $2 AND t.depth < 500 -- Limit recursion depth to 500\n)\nSELECT id AS id, depth\nFROM job_tree\nORDER BY depth, id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "depth", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "5e8ba1850b2520bd4bf030f53f1f2e6606bfc0f440fbdbbdf4dd3234068c9345" +} diff --git a/backend/.sqlx/query-0a9dd1addaf48eeb46eed59abb6daf9819d07b08cf7ca442ea7ec78a9b2b63b2.json b/backend/.sqlx/query-5faba1042763a308c64e36cc2a331d90bc0aa384262e473c7006340dcb6e959c.json similarity index 59% rename from backend/.sqlx/query-0a9dd1addaf48eeb46eed59abb6daf9819d07b08cf7ca442ea7ec78a9b2b63b2.json rename to backend/.sqlx/query-5faba1042763a308c64e36cc2a331d90bc0aa384262e473c7006340dcb6e959c.json index a0f52168b9..9b7f1962a4 100644 --- a/backend/.sqlx/query-0a9dd1addaf48eeb46eed59abb6daf9819d07b08cf7ca442ea7ec78a9b2b63b2.json +++ b/backend/.sqlx/query-5faba1042763a308c64e36cc2a331d90bc0aa384262e473c7006340dcb6e959c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT value\n FROM resource\n WHERE path = $1 AND workspace_id = $2", + "query": "SELECT value FROM resource WHERE workspace_id = $1 AND path = $2", "describe": { "columns": [ { @@ -19,5 +19,5 @@ true ] }, - "hash": "0a9dd1addaf48eeb46eed59abb6daf9819d07b08cf7ca442ea7ec78a9b2b63b2" + "hash": "5faba1042763a308c64e36cc2a331d90bc0aa384262e473c7006340dcb6e959c" } diff --git a/backend/.sqlx/query-5ff7df54c7908a7de494ddae5fc7bb9be8106a79e0683cd34459585bbd920ce4.json b/backend/.sqlx/query-5ff7df54c7908a7de494ddae5fc7bb9be8106a79e0683cd34459585bbd920ce4.json deleted file mode 100644 index 45bca31fd0..0000000000 --- a/backend/.sqlx/query-5ff7df54c7908a7de494ddae5fc7bb9be8106a79e0683cd34459585bbd920ce4.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT flow_version.value->>'concurrency_key'\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 AND flow.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "5ff7df54c7908a7de494ddae5fc7bb9be8106a79e0683cd34459585bbd920ce4" -} diff --git a/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json b/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json index 6354dae16b..1a30ad011f 100644 --- a/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json +++ b/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json @@ -59,7 +59,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } diff --git a/backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json b/backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json deleted file mode 100644 index 6a66bb6f22..0000000000 --- a/backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "usage", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Varchar" - ] - }, - "nullable": [ - false - ] - }, - "hash": "621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e" -} diff --git a/backend/.sqlx/query-64adf72c19023b03a47d48444b1cf970559b3ef735274ef0fef6f47b4c68da7e.json b/backend/.sqlx/query-64adf72c19023b03a47d48444b1cf970559b3ef735274ef0fef6f47b4c68da7e.json index d90022a58d..d6fbd2ff35 100644 --- a/backend/.sqlx/query-64adf72c19023b03a47d48444b1cf970559b3ef735274ef0fef6f47b4c68da7e.json +++ b/backend/.sqlx/query-64adf72c19023b03a47d48444b1cf970559b3ef735274ef0fef6f47b4c68da7e.json @@ -13,7 +13,7 @@ "Left": [ "Bool", "Varchar", - "Json", + "Jsonb", "Int4", "Bool", "Text" diff --git a/backend/.sqlx/query-6513c1433dbfe03f7c778963a05d964fda13a1091a8206ee174ed3a161248126.json b/backend/.sqlx/query-6513c1433dbfe03f7c778963a05d964fda13a1091a8206ee174ed3a161248126.json deleted file mode 100644 index e50d2f1154..0000000000 --- a/backend/.sqlx/query-6513c1433dbfe03f7c778963a05d964fda13a1091a8206ee174ed3a161248126.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id\n FROM v2_job j JOIN v2_job_queue USING (id)\n WHERE j.workspace_id = $2 AND trigger_kind = 'schedule' AND trigger = $1 AND runnable_path = $4\n AND parent_job IS NULL\n AND j.id != $3\n AND running = true", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Uuid", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "6513c1433dbfe03f7c778963a05d964fda13a1091a8206ee174ed3a161248126" -} diff --git a/backend/.sqlx/query-6664be80f0d72ea7b8b184c5348063db3bbfea67f4a056d6e3be1fc4255cfc06.json b/backend/.sqlx/query-6664be80f0d72ea7b8b184c5348063db3bbfea67f4a056d6e3be1fc4255cfc06.json deleted file mode 100644 index 0e7ae566fd..0000000000 --- a/backend/.sqlx/query-6664be80f0d72ea7b8b184c5348063db3bbfea67f4a056d6e3be1fc4255cfc06.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_leaf_jobs = JSONB_SET(coalesce(flow_leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $3), $3) = id", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "6664be80f0d72ea7b8b184c5348063db3bbfea67f4a056d6e3be1fc4255cfc06" -} diff --git a/backend/.sqlx/query-673564e6c4dcf30dae3d7a75c397998ea800860ede856e5fc6cd1f57d5408333.json b/backend/.sqlx/query-673564e6c4dcf30dae3d7a75c397998ea800860ede856e5fc6cd1f57d5408333.json new file mode 100644 index 0000000000..8b9a46c4d0 --- /dev/null +++ b/backend/.sqlx/query-673564e6c4dcf30dae3d7a75c397998ea800860ede856e5fc6cd1f57d5408333.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \n capture_config \n SET \n last_server_ping = NULL \n WHERE \n workspace_id = $1 AND \n path = $2 AND \n is_flow = $3 AND \n trigger_kind = 'gcp' AND \n server_id IS NULL\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "673564e6c4dcf30dae3d7a75c397998ea800860ede856e5fc6cd1f57d5408333" +} diff --git a/backend/.sqlx/query-a96ff22bc78b74d7234550a12d9fb5c555c1187b276f1353dae1b2ac0670a92a.json b/backend/.sqlx/query-679a9159a5fca976a3de99fe26806faded2cc63e8f16c201e99ab1725dcff294.json similarity index 58% rename from backend/.sqlx/query-a96ff22bc78b74d7234550a12d9fb5c555c1187b276f1353dae1b2ac0670a92a.json rename to backend/.sqlx/query-679a9159a5fca976a3de99fe26806faded2cc63e8f16c201e99ab1725dcff294.json index 060d87a941..1ce9e58cd4 100644 --- a/backend/.sqlx/query-a96ff22bc78b74d7234550a12d9fb5c555c1187b276f1353dae1b2ac0670a92a.json +++ b/backend/.sqlx/query-679a9159a5fca976a3de99fe26806faded2cc63e8f16c201e99ab1725dcff294.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND workspace_id = $2 AND http_method = $3 AND ($4::TEXT IS NULL OR path != $4))", + "query": "\n SELECT EXISTS(\n SELECT 1 \n FROM http_trigger \n WHERE \n route_path_key = $1\n AND workspace_id = $2 \n AND http_method = $3 \n AND ($4::TEXT IS NULL OR path != $4)\n )\n ", "describe": { "columns": [ { @@ -34,5 +34,5 @@ null ] }, - "hash": "a96ff22bc78b74d7234550a12d9fb5c555c1187b276f1353dae1b2ac0670a92a" + "hash": "679a9159a5fca976a3de99fe26806faded2cc63e8f16c201e99ab1725dcff294" } diff --git a/backend/.sqlx/query-69518153298f8ca6f112bed156b520758656ce6bcd1e694a62e140e0af8c59fa.json b/backend/.sqlx/query-69518153298f8ca6f112bed156b520758656ce6bcd1e694a62e140e0af8c59fa.json new file mode 100644 index 0000000000..f96e0e349a --- /dev/null +++ b/backend/.sqlx/query-69518153298f8ca6f112bed156b520758656ce6bcd1e694a62e140e0af8c59fa.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET git_app_installations = COALESCE(\n (\n SELECT jsonb_agg(elem)\n FROM (\n SELECT elem\n FROM jsonb_array_elements(git_app_installations) AS elem\n WHERE (elem->>'installation_id')::bigint != ($1::jsonb->>'installation_id')::bigint\n UNION ALL\n SELECT $1::jsonb\n ) sub\n ),\n jsonb_build_array($1::jsonb)\n )\n WHERE workspace_id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "69518153298f8ca6f112bed156b520758656ce6bcd1e694a62e140e0af8c59fa" +} diff --git a/backend/.sqlx/query-69db5305aadd911b06ecdc4eeb9610c0e233bca35edebe2ad1772a4a1a1d5bbe.json b/backend/.sqlx/query-69db5305aadd911b06ecdc4eeb9610c0e233bca35edebe2ad1772a4a1a1d5bbe.json new file mode 100644 index 0000000000..ec22d86a79 --- /dev/null +++ b/backend/.sqlx/query-69db5305aadd911b06ecdc4eeb9610c0e233bca35edebe2ad1772a4a1a1d5bbe.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "int4", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Uuid" + ] + }, + "nullable": [ + null + ] + }, + "hash": "69db5305aadd911b06ecdc4eeb9610c0e233bca35edebe2ad1772a4a1a1d5bbe" +} diff --git a/backend/.sqlx/query-69ff806066a24c60188d7547a7776c160ee65eaa01b259c996f3f96ed44fcaaf.json b/backend/.sqlx/query-69ff806066a24c60188d7547a7776c160ee65eaa01b259c996f3f96ed44fcaaf.json deleted file mode 100644 index 75ef2996a0..0000000000 --- a/backend/.sqlx/query-69ff806066a24c60188d7547a7776c160ee65eaa01b259c996f3f96ed44fcaaf.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "69ff806066a24c60188d7547a7776c160ee65eaa01b259c996f3f96ed44fcaaf" -} diff --git a/backend/.sqlx/query-6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9.json b/backend/.sqlx/query-6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9.json new file mode 100644 index 0000000000..4ea7dabd04 --- /dev/null +++ b/backend/.sqlx/query-6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9.json @@ -0,0 +1,24 @@ +{ + "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" +} diff --git a/backend/.sqlx/query-6aa04cc83e746ebca45959294b6184d17c5100a3f3dc9fda02b1a7acc0b73fa5.json b/backend/.sqlx/query-6aa04cc83e746ebca45959294b6184d17c5100a3f3dc9fda02b1a7acc0b73fa5.json new file mode 100644 index 0000000000..44d1cfc7a5 --- /dev/null +++ b/backend/.sqlx/query-6aa04cc83e746ebca45959294b6184d17c5100a3f3dc9fda02b1a7acc0b73fa5.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT importer_path FROM dependency_map \n WHERE workspace_id = $1 AND imported_path = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "importer_path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6aa04cc83e746ebca45959294b6184d17c5100a3f3dc9fda02b1a7acc0b73fa5" +} diff --git a/backend/.sqlx/query-1b58b90c184ca21d777ea4e264c79aecc2361134a4817c2b9580f2680425352d.json b/backend/.sqlx/query-6b0347da54d1b8646ece08a5ce78ed7e7c98780fef56f6ea6e1e1fd0458ae32f.json similarity index 71% rename from backend/.sqlx/query-1b58b90c184ca21d777ea4e264c79aecc2361134a4817c2b9580f2680425352d.json rename to backend/.sqlx/query-6b0347da54d1b8646ece08a5ce78ed7e7c98780fef56f6ea6e1e1fd0458ae32f.json index d0b582d17b..8259c07cd0 100644 --- a/backend/.sqlx/query-1b58b90c184ca21d777ea4e264c79aecc2361134a4817c2b9580f2680425352d.json +++ b/backend/.sqlx/query-6b0347da54d1b8646ece08a5ce78ed7e7c98780fef56f6ea6e1e1fd0458ae32f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT args AS \"args: Json>>\"\n FROM v2_job WHERE id = $1 AND workspace_id = $2", + "query": "SELECT args AS \"args: Json>>\"\n FROM v2_job WHERE id = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -19,5 +19,5 @@ true ] }, - "hash": "1b58b90c184ca21d777ea4e264c79aecc2361134a4817c2b9580f2680425352d" + "hash": "6b0347da54d1b8646ece08a5ce78ed7e7c98780fef56f6ea6e1e1fd0458ae32f" } diff --git a/backend/.sqlx/query-ab9e47e5b510e7df5a41db12896675393a6bb27f8e14245410751961218a7df5.json b/backend/.sqlx/query-6b6f8f7b4a6b6e7e41a9da8b6dfdbcae842ff252cc355bd91aeeb5e26dcc74f3.json similarity index 54% rename from backend/.sqlx/query-ab9e47e5b510e7df5a41db12896675393a6bb27f8e14245410751961218a7df5.json rename to backend/.sqlx/query-6b6f8f7b4a6b6e7e41a9da8b6dfdbcae842ff252cc355bd91aeeb5e26dcc74f3.json index 868541a175..80619c8b2a 100644 --- a/backend/.sqlx/query-ab9e47e5b510e7df5a41db12896675393a6bb27f8e14245410751961218a7df5.json +++ b/backend/.sqlx/query-6b6f8f7b4a6b6e7e41a9da8b6dfdbcae842ff252cc355bd91aeeb5e26dcc74f3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM v2_as_queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now", + "query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id\n WHERE v2_job.runnable_path = $1 AND v2_job.kind != 'dependencies' AND v2_job_queue.running = true AND v2_job_queue.workspace_id = $2 AND v2_job_queue.canceled_by IS NULL AND v2_job.concurrent_limit > 0), $3) as min_started_at, now() AS now", "describe": { "columns": [ { @@ -26,5 +26,5 @@ null ] }, - "hash": "ab9e47e5b510e7df5a41db12896675393a6bb27f8e14245410751961218a7df5" + "hash": "6b6f8f7b4a6b6e7e41a9da8b6dfdbcae842ff252cc355bd91aeeb5e26dcc74f3" } diff --git a/backend/.sqlx/query-6d36da815795d5cac2e76b4d34a7f1f9f836bd5b6866d3db26b25428d39c0b23.json b/backend/.sqlx/query-6d36da815795d5cac2e76b4d34a7f1f9f836bd5b6866d3db26b25428d39c0b23.json deleted file mode 100644 index 009c8a0e26..0000000000 --- a/backend/.sqlx/query-6d36da815795d5cac2e76b4d34a7f1f9f836bd5b6866d3db26b25428d39c0b23.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT on_behalf_of_email, edited_by FROM flow WHERE path = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "on_behalf_of_email", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "edited_by", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - true, - false - ] - }, - "hash": "6d36da815795d5cac2e76b4d34a7f1f9f836bd5b6866d3db26b25428d39c0b23" -} diff --git a/backend/.sqlx/query-6e2564dc37ee967c634deade67ceabc4c516418e595dee9cd7d651acc1f4af6e.json b/backend/.sqlx/query-6e2564dc37ee967c634deade67ceabc4c516418e595dee9cd7d651acc1f4af6e.json new file mode 100644 index 0000000000..39ebee28b4 --- /dev/null +++ b/backend/.sqlx/query-6e2564dc37ee967c634deade67ceabc4c516418e595dee9cd7d651acc1f4af6e.json @@ -0,0 +1,249 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n cj.id AS \"id!\",\n cj.workspace_id AS \"workspace_id!\",\n cj.parent_job,\n cj.created_by AS \"created_by!\",\n cj.duration_ms AS \"duration_ms!\",\n cj.success AS \"success!\",\n cj.script_hash AS \"script_hash!: Option\",\n cj.script_path,\n cj.args AS \"args: sqlx::types::Json>>\",\n cj.result AS \"result: sqlx::types::Json>\",\n cj.deleted AS \"deleted!\",\n cj.canceled AS \"canceled!\",\n cj.canceled_by,\n cj.canceled_reason,\n cj.job_kind AS \"job_kind!: JobKind\",\n cj.schedule_path,\n cj.permissioned_as AS \"permissioned_as!\",\n cj.is_flow_step AS \"is_flow_step!\",\n cj.language AS \"language: ScriptLang\",\n cj.is_skipped AS \"is_skipped!\",\n cj.email AS \"email!\",\n cj.visible_to_owner AS \"visible_to_owner!\",\n cj.mem_peak,\n cj.tag AS \"tag!\",\n cj.created_at AS \"created_at!\",\n cj.started_at,\n job_logs.logs,\n job_logs.log_offset AS \"log_offset?\",\n job_logs.log_file_index\n\n FROM v2_as_completed_job AS cj\n LEFT JOIN job_logs ON cj.id = job_logs.job_id\n WHERE (cj.created_at > $1 AND cj.created_at < $3)\n OR cj.id = ANY($2)\n ORDER BY cj.created_at ASC LIMIT $4", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id!", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "workspace_id!", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "parent_job", + "type_info": "Uuid" + }, + { + "ordinal": 3, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "duration_ms!", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "success!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "script_hash!: Option", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "args: sqlx::types::Json>>", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "deleted!", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "canceled!", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "canceled_by", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "canceled_reason", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "job_kind!: JobKind", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 15, + "name": "schedule_path", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "permissioned_as!", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "is_flow_step!", + "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + } + }, + { + "ordinal": 19, + "name": "is_skipped!", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "email!", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "visible_to_owner!", + "type_info": "Bool" + }, + { + "ordinal": 22, + "name": "mem_peak", + "type_info": "Int4" + }, + { + "ordinal": 23, + "name": "tag!", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "created_at!", + "type_info": "Timestamptz" + }, + { + "ordinal": 25, + "name": "started_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 26, + "name": "logs", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "log_offset?", + "type_info": "Int4" + }, + { + "ordinal": 28, + "name": "log_file_index", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Timestamptz", + "UuidArray", + "Timestamptz", + "Int8" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true + ] + }, + "hash": "6e2564dc37ee967c634deade67ceabc4c516418e595dee9cd7d651acc1f4af6e" +} diff --git a/backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json b/backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json deleted file mode 100644 index f3bda55eec..0000000000 --- a/backend/.sqlx/query-6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-7023184bfeece73252aac9e3e89c5954f40cf46905b40b40ba97a21558a1ac01.json b/backend/.sqlx/query-7023184bfeece73252aac9e3e89c5954f40cf46905b40b40ba97a21558a1ac01.json new file mode 100644 index 0000000000..e1d8d14548 --- /dev/null +++ b/backend/.sqlx/query-7023184bfeece73252aac9e3e89c5954f40cf46905b40b40ba97a21558a1ac01.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM workspace_runnable_dependencies WHERE flow_path = $1 AND workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7023184bfeece73252aac9e3e89c5954f40cf46905b40b40ba97a21558a1ac01" +} diff --git a/backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json b/backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json deleted file mode 100644 index edec8c5053..0000000000 --- a/backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH ping AS (UPDATE v2_job_runtime SET ping = NULL WHERE id = $2 RETURNING id)\n UPDATE v2_job_queue SET\n running = false,\n started_at = null,\n scheduled_for = $1\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Timestamptz", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3" -} diff --git a/backend/.sqlx/query-714fb0f66ceb536aee8cb9ae0144757b999d25870fda37fe904e09dd5c742015.json b/backend/.sqlx/query-714fb0f66ceb536aee8cb9ae0144757b999d25870fda37fe904e09dd5c742015.json new file mode 100644 index 0000000000..9f3f4b0c8c --- /dev/null +++ b/backend/.sqlx/query-714fb0f66ceb536aee8cb9ae0144757b999d25870fda37fe904e09dd5c742015.json @@ -0,0 +1,93 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n route_path,\n http_method AS \"http_method: _\",\n is_async,\n workspaced_route,\n summary,\n description,\n authentication_method AS \"authentication_method: _\",\n authentication_resource_path\n FROM\n http_trigger\n WHERE\n path ~ ANY($1) AND\n route_path ~ ANY($2) AND\n workspace_id = $3\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "route_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "http_method: _", + "type_info": { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "is_async", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "workspaced_route", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "authentication_method: _", + "type_info": { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + } + }, + { + "ordinal": 7, + "name": "authentication_resource_path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "TextArray", + "TextArray", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + false, + true + ] + }, + "hash": "714fb0f66ceb536aee8cb9ae0144757b999d25870fda37fe904e09dd5c742015" +} diff --git a/backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json b/backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json similarity index 81% rename from backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json rename to backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json index 3dd496788f..d17cf3c524 100644 --- a/backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json +++ b/backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_resource, ai_models, code_completion_model, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, automatic_billing, default_scripts, mute_critical_alerts, color, operator_settings FROM workspace_settings WHERE workspace_id = $1", + "query": "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_config, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, default_scripts, mute_critical_alerts, color, operator_settings, git_app_installations FROM workspace_settings WHERE workspace_id = $1", "describe": { "columns": [ { @@ -80,78 +80,68 @@ }, { "ordinal": 15, - "name": "ai_resource", + "name": "ai_config", "type_info": "Jsonb" }, { "ordinal": 16, - "name": "ai_models", - "type_info": "VarcharArray" - }, - { - "ordinal": 17, - "name": "code_completion_model", - "type_info": "Varchar" - }, - { - "ordinal": 18, "name": "error_handler", "type_info": "Varchar" }, { - "ordinal": 19, + "ordinal": 17, "name": "error_handler_extra_args", "type_info": "Json" }, { - "ordinal": 20, + "ordinal": 18, "name": "error_handler_muted_on_cancel", "type_info": "Bool" }, { - "ordinal": 21, + "ordinal": 19, "name": "large_file_storage", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 20, "name": "git_sync", "type_info": "Jsonb" }, { - "ordinal": 23, + "ordinal": 21, "name": "deploy_ui", "type_info": "Jsonb" }, { - "ordinal": 24, + "ordinal": 22, "name": "default_app", "type_info": "Varchar" }, { - "ordinal": 25, - "name": "automatic_billing", - "type_info": "Bool" - }, - { - "ordinal": 26, + "ordinal": 23, "name": "default_scripts", "type_info": "Jsonb" }, { - "ordinal": 27, + "ordinal": 24, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 28, + "ordinal": 25, "name": "color", "type_info": "Varchar" }, { - "ordinal": 29, + "ordinal": 26, "name": "operator_settings", "type_info": "Jsonb" + }, + { + "ordinal": 27, + "name": "git_app_installations", + "type_info": "Jsonb" } ], "parameters": { @@ -176,21 +166,19 @@ true, true, true, - false, - true, - true, - true, - false, - true, - true, true, true, false, true, true, true, - true + true, + true, + true, + true, + true, + false ] }, - "hash": "4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1" + "hash": "71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950" } diff --git a/backend/.sqlx/query-362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675.json b/backend/.sqlx/query-726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de.json similarity index 82% rename from backend/.sqlx/query-362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675.json rename to backend/.sqlx/query-726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de.json index 183062aa6d..4baf932023 100644 --- a/backend/.sqlx/query-362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675.json +++ b/backend/.sqlx/query-726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by created_at DESC", + "query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC", "describe": { "columns": [ { @@ -25,5 +25,5 @@ true ] }, - "hash": "362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675" + "hash": "726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de" } diff --git a/backend/.sqlx/query-7470e7067b948509d14828c24a8725da747e6b967554eb7e088ee3a018ec1f8b.json b/backend/.sqlx/query-7470e7067b948509d14828c24a8725da747e6b967554eb7e088ee3a018ec1f8b.json new file mode 100644 index 0000000000..a6979f19db --- /dev/null +++ b/backend/.sqlx/query-7470e7067b948509d14828c24a8725da747e6b967554eb7e088ee3a018ec1f8b.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id\n FROM v2_job j JOIN v2_job_queue USING (id)\n WHERE j.workspace_id = $2 AND trigger_kind = 'schedule' AND trigger = $1 AND runnable_path = $4\n AND parent_job IS NULL\n AND j.id != $3\n AND running = true", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Uuid", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "7470e7067b948509d14828c24a8725da747e6b967554eb7e088ee3a018ec1f8b" +} diff --git a/backend/.sqlx/query-74dbd5a09255c30991078492ba3850e02ffbef25fbfd29cbedc041b0e439e580.json b/backend/.sqlx/query-74dbd5a09255c30991078492ba3850e02ffbef25fbfd29cbedc041b0e439e580.json deleted file mode 100644 index 77515a838f..0000000000 --- a/backend/.sqlx/query-74dbd5a09255c30991078492ba3850e02ffbef25fbfd29cbedc041b0e439e580.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id AS \"id!\", flow_status AS \"flow_status!: Json\"\n FROM v2_as_completed_job\n WHERE parent_job = $1 AND workspace_id = $2 AND flow_status IS NOT NULL", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id!", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "flow_status!: Json", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - true, - true - ] - }, - "hash": "74dbd5a09255c30991078492ba3850e02ffbef25fbfd29cbedc041b0e439e580" -} diff --git a/backend/.sqlx/query-75451b6d48e4c26812ae64981d0d968b8fb0bf4374a2fccc167fa879bad7078f.json b/backend/.sqlx/query-75451b6d48e4c26812ae64981d0d968b8fb0bf4374a2fccc167fa879bad7078f.json deleted file mode 100644 index 287284b86b..0000000000 --- a/backend/.sqlx/query-75451b6d48e4c26812ae64981d0d968b8fb0bf4374a2fccc167fa879bad7078f.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by from script where hash = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "concurrency_key", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "concurrent_limit", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "concurrency_time_window_s", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "cache_ttl", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "language: ScriptLang", - "type_info": { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb" - ] - } - } - } - }, - { - "ordinal": 7, - "name": "dedicated_worker", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "priority", - "type_info": "Int2" - }, - { - "ordinal": 9, - "name": "delete_after_use", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "timeout", - "type_info": "Int4" - }, - { - "ordinal": 11, - "name": "has_preprocessor", - "type_info": "Bool" - }, - { - "ordinal": 12, - "name": "on_behalf_of_email", - "type_info": "Text" - }, - { - "ordinal": 13, - "name": "created_by", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - }, - "nullable": [ - false, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - true, - false - ] - }, - "hash": "75451b6d48e4c26812ae64981d0d968b8fb0bf4374a2fccc167fa879bad7078f" -} diff --git a/backend/.sqlx/query-776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6.json b/backend/.sqlx/query-776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6.json new file mode 100644 index 0000000000..5c7405cec6 --- /dev/null +++ b/backend/.sqlx/query-776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO sqs_trigger (\n aws_auth_resource_type,\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10,\n $11\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "aws_auth_resource_type", + "kind": { + "Enum": [ + "oidc", + "credentials" + ] + } + } + }, + "Varchar", + "Varchar", + "TextArray", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Bool", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6" +} diff --git a/backend/.sqlx/query-0a1c95c4376b944661bab13271091cf3ea0afe68fb8e08e7aea239dc735c625c.json b/backend/.sqlx/query-77701b16ee1f6dd827372835db59bbffc7254af47a8d48b7ba3cf969c2f8398c.json similarity index 53% rename from backend/.sqlx/query-0a1c95c4376b944661bab13271091cf3ea0afe68fb8e08e7aea239dc735c625c.json rename to backend/.sqlx/query-77701b16ee1f6dd827372835db59bbffc7254af47a8d48b7ba3cf969c2f8398c.json index b8f224dbc2..e6e754579f 100644 --- a/backend/.sqlx/query-0a1c95c4376b944661bab13271091cf3ea0afe68fb8e08e7aea239dc735c625c.json +++ b/backend/.sqlx/query-77701b16ee1f6dd827372835db59bbffc7254af47a8d48b7ba3cf969c2f8398c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO concurrency_key(key, job_id) VALUES ($1, $2)", + "query": "UPDATE v2_job_queue SET tag = $1, running = false WHERE id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "0a1c95c4376b944661bab13271091cf3ea0afe68fb8e08e7aea239dc735c625c" + "hash": "77701b16ee1f6dd827372835db59bbffc7254af47a8d48b7ba3cf969c2f8398c" } diff --git a/backend/.sqlx/query-77f12f22d7c9da1799e5720c9a8fab3b6f26ccbd822fc05b7a0fb3d6a17c5435.json b/backend/.sqlx/query-77f12f22d7c9da1799e5720c9a8fab3b6f26ccbd822fc05b7a0fb3d6a17c5435.json new file mode 100644 index 0000000000..42289a7791 --- /dev/null +++ b/backend/.sqlx/query-77f12f22d7c9da1799e5720c9a8fab3b6f26ccbd822fc05b7a0fb3d6a17c5435.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT jc.id, jc.flow_status AS \"flow_status!: Json\", j.created_at\n FROM v2_job_completed jc\n JOIN v2_job j ON j.id = jc.id\n WHERE jc.id = $1 AND jc.workspace_id = $2 AND jc.flow_status IS NOT NULL", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "flow_status!: Json", + "type_info": "Jsonb" + }, + { + "ordinal": 2, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [ + false, + true, + false + ] + }, + "hash": "77f12f22d7c9da1799e5720c9a8fab3b6f26ccbd822fc05b7a0fb3d6a17c5435" +} diff --git a/backend/.sqlx/query-b57a188ca137162a2848ebf81fa3aeca9a14ac628c61a358e2c6612c57249b0f.json b/backend/.sqlx/query-7aa589db3199d7f727cc69e63e1281b7ed329ff0c9d1617747f4ccd6014720cf.json similarity index 58% rename from backend/.sqlx/query-b57a188ca137162a2848ebf81fa3aeca9a14ac628c61a358e2c6612c57249b0f.json rename to backend/.sqlx/query-7aa589db3199d7f727cc69e63e1281b7ed329ff0c9d1617747f4ccd6014720cf.json index a730d4b588..b2101cc0d4 100644 --- a/backend/.sqlx/query-b57a188ca137162a2848ebf81fa3aeca9a14ac628c61a358e2c6612c57249b0f.json +++ b/backend/.sqlx/query-7aa589db3199d7f727cc69e63e1281b7ed329ff0c9d1617747f4ccd6014720cf.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1 AND path = $2)", + "query": "SELECT EXISTS(SELECT 1 FROM variable WHERE account = $1 AND workspace_id = $2)", "describe": { "columns": [ { @@ -11,7 +11,7 @@ ], "parameters": { "Left": [ - "Text", + "Int4", "Text" ] }, @@ -19,5 +19,5 @@ null ] }, - "hash": "b57a188ca137162a2848ebf81fa3aeca9a14ac628c61a358e2c6612c57249b0f" + "hash": "7aa589db3199d7f727cc69e63e1281b7ed329ff0c9d1617747f4ccd6014720cf" } diff --git a/backend/.sqlx/query-7b084617bf2de5ed9a657d2bd2cbc9de1246427bf0302c032f8bd26ff93313f7.json b/backend/.sqlx/query-7b084617bf2de5ed9a657d2bd2cbc9de1246427bf0302c032f8bd26ff93313f7.json deleted file mode 100644 index 3f72b7b760..0000000000 --- a/backend/.sqlx/query-7b084617bf2de5ed9a657d2bd2cbc9de1246427bf0302c032f8bd26ff93313f7.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "7b084617bf2de5ed9a657d2bd2cbc9de1246427bf0302c032f8bd26ff93313f7" -} diff --git a/backend/.sqlx/query-7bd7505b008954aae6152554c1f9bb0611d0ec451b48aa1a4de309dadeb53315.json b/backend/.sqlx/query-7bd7505b008954aae6152554c1f9bb0611d0ec451b48aa1a4de309dadeb53315.json deleted file mode 100644 index c7f3146594..0000000000 --- a/backend/.sqlx/query-7bd7505b008954aae6152554c1f9bb0611d0ec451b48aa1a4de309dadeb53315.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = flow_status - 'approval_conditions'\n WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "7bd7505b008954aae6152554c1f9bb0611d0ec451b48aa1a4de309dadeb53315" -} diff --git a/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json b/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json new file mode 100644 index 0000000000..090ac08404 --- /dev/null +++ b/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM resource WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3" +} diff --git a/backend/.sqlx/query-7cd070aaca3b4f95bb6e669ac07f288bd1cc8a85625255d86e45f63e8267d34c.json b/backend/.sqlx/query-7cd070aaca3b4f95bb6e669ac07f288bd1cc8a85625255d86e45f63e8267d34c.json new file mode 100644 index 0000000000..99095e3fcc --- /dev/null +++ b/backend/.sqlx/query-7cd070aaca3b4f95bb6e669ac07f288bd1cc8a85625255d86e45f63e8267d34c.json @@ -0,0 +1,199 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "schedule", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "timezone", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "on_failure", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "on_failure_times", + "type_info": "Int4" + }, + { + "ordinal": 15, + "name": "on_failure_exact", + "type_info": "Bool" + }, + { + "ordinal": 16, + "name": "on_failure_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "on_recovery", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "on_recovery_times", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "on_recovery_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "on_success", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "on_success_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "ws_error_handler_muted", + "type_info": "Bool" + }, + { + "ordinal": 23, + "name": "retry", + "type_info": "Jsonb" + }, + { + "ordinal": 24, + "name": "no_flow_overlap", + "type_info": "Bool" + }, + { + "ordinal": 25, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "paused_until", + "type_info": "Timestamptz" + }, + { + "ordinal": 29, + "name": "cron_version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Bool", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + false, + true, + true, + true, + true, + true + ] + }, + "hash": "7cd070aaca3b4f95bb6e669ac07f288bd1cc8a85625255d86e45f63e8267d34c" +} diff --git a/backend/.sqlx/query-7d5a161d916cb8d1485f8d72e6b4044f505f49edcd2ebeee4a77eae737f48795.json b/backend/.sqlx/query-7d5a161d916cb8d1485f8d72e6b4044f505f49edcd2ebeee4a77eae737f48795.json new file mode 100644 index 0000000000..e1ab284262 --- /dev/null +++ b/backend/.sqlx/query-7d5a161d916cb8d1485f8d72e6b4044f505f49edcd2ebeee4a77eae737f48795.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM \n capture\n WHERE \n id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "7d5a161d916cb8d1485f8d72e6b4044f505f49edcd2ebeee4a77eae737f48795" +} diff --git a/backend/.sqlx/query-7ecb01893c46823a207bf59aa57d6f4c6e63f7b7b6d4cf0b4ae1237da0e17b4b.json b/backend/.sqlx/query-7ecb01893c46823a207bf59aa57d6f4c6e63f7b7b6d4cf0b4ae1237da0e17b4b.json new file mode 100644 index 0000000000..628794aae4 --- /dev/null +++ b/backend/.sqlx/query-7ecb01893c46823a207bf59aa57d6f4c6e63f7b7b6d4cf0b4ae1237da0e17b4b.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n jsonb_array_elements(git_sync->'repositories')->>'script_path' AS script_path,\n jsonb_array_elements(git_sync->'repositories')->>'git_repo_resource_path' AS git_repo_resource_path\n FROM workspace_settings\n WHERE workspace_id = $1;\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "script_path", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "git_repo_resource_path", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "7ecb01893c46823a207bf59aa57d6f4c6e63f7b7b6d4cf0b4ae1237da0e17b4b" +} diff --git a/backend/.sqlx/query-7f6d6952abee71fb6bb5604766c91e9605494a797b9c01c54fbdbe8949ab625d.json b/backend/.sqlx/query-7f6d6952abee71fb6bb5604766c91e9605494a797b9c01c54fbdbe8949ab625d.json new file mode 100644 index 0000000000..0667ac7d65 --- /dev/null +++ b/backend/.sqlx/query-7f6d6952abee71fb6bb5604766c91e9605494a797b9c01c54fbdbe8949ab625d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n WITH ping AS (\n UPDATE v2_job_runtime SET ping = null WHERE id = $2\n )\n UPDATE v2_job_queue SET\n running = false,\n started_at = null,\n scheduled_for = $1\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Timestamptz", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "7f6d6952abee71fb6bb5604766c91e9605494a797b9c01c54fbdbe8949ab625d" +} diff --git a/backend/.sqlx/query-7f772ceace10008d7d40afd331a585bfa19b70315840585c81b8e7a3911ab5e7.json b/backend/.sqlx/query-7f772ceace10008d7d40afd331a585bfa19b70315840585c81b8e7a3911ab5e7.json new file mode 100644 index 0000000000..1d7b5ddba5 --- /dev/null +++ b/backend/.sqlx/query-7f772ceace10008d7d40afd331a585bfa19b70315840585c81b8e7a3911ab5e7.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET git_app_installations = COALESCE(\n (\n SELECT jsonb_agg(elem)\n FROM jsonb_array_elements(git_app_installations) AS elem\n WHERE (elem->>'installation_id')::bigint != $1\n ),\n '[]'::jsonb\n )\n WHERE workspace_id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7f772ceace10008d7d40afd331a585bfa19b70315840585c81b8e7a3911ab5e7" +} diff --git a/backend/.sqlx/query-c223f8b7fa4ef1aa06e1ba2a56d677774aa237508d5610714efd2e9b8b93c7b8.json b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json similarity index 68% rename from backend/.sqlx/query-c223f8b7fa4ef1aa06e1ba2a56d677774aa237508d5610714efd2e9b8b93c7b8.json rename to backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json index 45ce8d3f95..ca02b5c648 100644 --- a/backend/.sqlx/query-c223f8b7fa4ef1aa06e1ba2a56d677774aa237508d5610714efd2e9b8b93c7b8.json +++ b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT trigger_config as \"trigger_config: _\", trigger_kind as \"trigger_kind: _\", error, last_server_ping\n FROM capture_config\n WHERE workspace_id = $1 AND path = $2 AND is_flow = $3", + "query": "\n SELECT \n trigger_config AS \"trigger_config: _\", \n trigger_kind AS \"trigger_kind: _\", \n error, \n last_server_ping\n FROM \n capture_config\n WHERE \n workspace_id = $1 \n AND path = $2 \n AND is_flow = $3\n ", "describe": { "columns": [ { @@ -24,7 +24,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -55,5 +56,5 @@ true ] }, - "hash": "c223f8b7fa4ef1aa06e1ba2a56d677774aa237508d5610714efd2e9b8b93c7b8" + "hash": "7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa" } diff --git a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json b/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json index 2179199223..7933311a4c 100644 --- a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json +++ b/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json @@ -32,7 +32,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } diff --git a/backend/.sqlx/query-808790cc01ec68be41dfeb80dc560d447fd719f2a56ea954c8cce49ffabd4245.json b/backend/.sqlx/query-808790cc01ec68be41dfeb80dc560d447fd719f2a56ea954c8cce49ffabd4245.json new file mode 100644 index 0000000000..846f94dcbb --- /dev/null +++ b/backend/.sqlx/query-808790cc01ec68be41dfeb80dc560d447fd719f2a56ea954c8cce49ffabd4245.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT f.path\n FROM workspace_runnable_dependencies wru \n JOIN flow f\n ON wru.flow_path = f.path AND wru.workspace_id = f.workspace_id\n WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Bool", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "808790cc01ec68be41dfeb80dc560d447fd719f2a56ea954c8cce49ffabd4245" +} diff --git a/backend/.sqlx/query-8123ba05f6e7b9bd395175ee4ec0c36c3726b2da9ff3592589ac7e83df1c537c.json b/backend/.sqlx/query-8123ba05f6e7b9bd395175ee4ec0c36c3726b2da9ff3592589ac7e83df1c537c.json deleted file mode 100644 index 1126431b46..0000000000 --- a/backend/.sqlx/query-8123ba05f6e7b9bd395175ee4ec0c36c3726b2da9ff3592589ac7e83df1c537c.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, flow_status AS \"flow_status!: Json\"\n FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "flow_status!: Json", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "8123ba05f6e7b9bd395175ee4ec0c36c3726b2da9ff3592589ac7e83df1c537c" -} diff --git a/backend/.sqlx/query-8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06.json b/backend/.sqlx/query-8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06.json new file mode 100644 index 0000000000..3df7f08318 --- /dev/null +++ b/backend/.sqlx/query-8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT distinct(path) FROM script WHERE workspace_id = $1 AND archived = true", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06" +} diff --git a/backend/.sqlx/query-83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9.json b/backend/.sqlx/query-83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9.json new file mode 100644 index 0000000000..3ca69e27dd --- /dev/null +++ b/backend/.sqlx/query-83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "usage", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9" +} diff --git a/backend/.sqlx/query-848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd.json b/backend/.sqlx/query-848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd.json new file mode 100644 index 0000000000..7751c14adb --- /dev/null +++ b/backend/.sqlx/query-848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE script \n SET ws_error_handler_muted = $3 \n WHERE ctid = (\n SELECT ctid FROM script\n WHERE path = $1 AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1\n )\n", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd" +} diff --git a/backend/.sqlx/query-8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3.json b/backend/.sqlx/query-8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3.json new file mode 100644 index 0000000000..9700ab141f --- /dev/null +++ b/backend/.sqlx/query-8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n sqs_trigger \n SET \n aws_auth_resource_type = $1,\n aws_resource_path = $2,\n queue_url = $3,\n message_attributes = $4, \n is_flow = $5, \n edited_by = $6, \n email = $7,\n script_path = $8,\n path = $9,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $10 AND \n path = $11\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "Custom": { + "name": "aws_auth_resource_type", + "kind": { + "Enum": [ + "oidc", + "credentials" + ] + } + } + }, + "Varchar", + "Varchar", + "TextArray", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3" +} diff --git a/backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json b/backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json deleted file mode 100644 index cbf85f6b0d..0000000000 --- a/backend/.sqlx/query-86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-71d51bbc35da7b9930e3ea3a634451217ccb9f1bc35b1ad6e10d16bc19c41447.json b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json similarity index 63% rename from backend/.sqlx/query-71d51bbc35da7b9930e3ea3a634451217ccb9f1bc35b1ad6e10d16bc19c41447.json rename to backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json index 643e0854bb..e2acac048f 100644 --- a/backend/.sqlx/query-71d51bbc35da7b9930e3ea3a634451217ccb9f1bc35b1ad6e10d16bc19c41447.json +++ b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT owner, email\n FROM capture_config\n WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4 AND last_client_ping > NOW() - INTERVAL '10 seconds'", + "query": "\n SELECT \n owner, \n email\n FROM \n capture_config\n WHERE \n workspace_id = $1 \n AND path = $2 \n AND is_flow = $3 \n AND trigger_kind = $4 \n AND last_client_ping > NOW() - INTERVAL '10 seconds'\n ", "describe": { "columns": [ { @@ -32,7 +32,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -44,5 +45,5 @@ false ] }, - "hash": "71d51bbc35da7b9930e3ea3a634451217ccb9f1bc35b1ad6e10d16bc19c41447" + "hash": "87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29" } diff --git a/backend/.sqlx/query-d988e91087695742d75946100cf2b7593cb8eed2a97411697819849958c022b3.json b/backend/.sqlx/query-8780a8cd6781f86041ae8df58477913c01b5d8d27dd4251f0cabbe3e974c0b0a.json similarity index 70% rename from backend/.sqlx/query-d988e91087695742d75946100cf2b7593cb8eed2a97411697819849958c022b3.json rename to backend/.sqlx/query-8780a8cd6781f86041ae8df58477913c01b5d8d27dd4251f0cabbe3e974c0b0a.json index 15625b26c2..2e19aa5a0e 100644 --- a/backend/.sqlx/query-d988e91087695742d75946100cf2b7593cb8eed2a97411697819849958c022b3.json +++ b/backend/.sqlx/query-8780a8cd6781f86041ae8df58477913c01b5d8d27dd4251f0cabbe3e974c0b0a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT result AS \"result!: Json>\"\n FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", + "query": "SELECT result AS \"result!: Json>\"\n FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -19,5 +19,5 @@ true ] }, - "hash": "d988e91087695742d75946100cf2b7593cb8eed2a97411697819849958c022b3" + "hash": "8780a8cd6781f86041ae8df58477913c01b5d8d27dd4251f0cabbe3e974c0b0a" } diff --git a/backend/.sqlx/query-884039108b98daa6279975e65348e460f99a0d2155fe2ff7b1d2840c5d9b76d0.json b/backend/.sqlx/query-884039108b98daa6279975e65348e460f99a0d2155fe2ff7b1d2840c5d9b76d0.json new file mode 100644 index 0000000000..64048e92f8 --- /dev/null +++ b/backend/.sqlx/query-884039108b98daa6279975e65348e460f99a0d2155fe2ff7b1d2840c5d9b76d0.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT elem\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n AND (elem->>'installation_id')::bigint = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "elem", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + null + ] + }, + "hash": "884039108b98daa6279975e65348e460f99a0d2155fe2ff7b1d2840c5d9b76d0" +} diff --git a/backend/.sqlx/query-89c08575afb31b70984f6b2b7dd4297af93b5b83ffdfb3ec91eba5df7ad3fd95.json b/backend/.sqlx/query-89c08575afb31b70984f6b2b7dd4297af93b5b83ffdfb3ec91eba5df7ad3fd95.json deleted file mode 100644 index a463eafdb7..0000000000 --- a/backend/.sqlx/query-89c08575afb31b70984f6b2b7dd4297af93b5b83ffdfb3ec91eba5df7ad3fd95.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, static_asset_config = $5, edited_by = $6, email = $7, is_async = $8, requires_auth = $9, edited_at = now(), is_static_website = $10\n WHERE workspace_id = $11 AND path = $12", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Bool", - { - "Custom": { - "name": "http_method", - "kind": { - "Enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] - } - } - }, - "Jsonb", - "Varchar", - "Varchar", - "Bool", - "Bool", - "Bool", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "89c08575afb31b70984f6b2b7dd4297af93b5b83ffdfb3ec91eba5df7ad3fd95" -} diff --git a/backend/.sqlx/query-8be1ddb20ffd8c375b7d1ecb14bdb3a7c2f0c8f9308946b9262e14b8c584dd99.json b/backend/.sqlx/query-8be1ddb20ffd8c375b7d1ecb14bdb3a7c2f0c8f9308946b9262e14b8c584dd99.json deleted file mode 100644 index 60ca066b36..0000000000 --- a/backend/.sqlx/query-8be1ddb20ffd8c375b7d1ecb14bdb3a7c2f0c8f9308946b9262e14b8c584dd99.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = flow_status - 'retry'\n WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "8be1ddb20ffd8c375b7d1ecb14bdb3a7c2f0c8f9308946b9262e14b8c584dd99" -} diff --git a/backend/.sqlx/query-7b1e6b67a20ae1128118d5f5cc0db4007fb9dc6fd20582a46ebb951fca3a7abd.json b/backend/.sqlx/query-8c2541cdfb84bfdbdc28285641166fe4c284dd6ed5245fbb90650d99afbf3812.json similarity index 58% rename from backend/.sqlx/query-7b1e6b67a20ae1128118d5f5cc0db4007fb9dc6fd20582a46ebb951fca3a7abd.json rename to backend/.sqlx/query-8c2541cdfb84bfdbdc28285641166fe4c284dd6ed5245fbb90650d99afbf3812.json index a094b8580f..88dcd47312 100644 --- a/backend/.sqlx/query-7b1e6b67a20ae1128118d5f5cc0db4007fb9dc6fd20582a46ebb951fca3a7abd.json +++ b/backend/.sqlx/query-8c2541cdfb84bfdbdc28285641166fe4c284dd6ed5245fbb90650d99afbf3812.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT CAST(ROUND(AVG(duration_ms), 0) AS BIGINT) AS avg_duration_s FROM\n (SELECT duration_ms FROM concurrency_key LEFT JOIN v2_as_completed_job ON v2_as_completed_job.id = concurrency_key.job_id WHERE key = $1 AND ended_at IS NOT NULL\n ORDER BY ended_at\n DESC LIMIT 10) AS t", + "query": "SELECT CAST(ROUND(AVG(duration_ms), 0) AS BIGINT) AS avg_duration_s FROM\n (SELECT duration_ms FROM concurrency_key LEFT JOIN v2_job_completed ON v2_job_completed.id = concurrency_key.job_id WHERE key = $1 AND ended_at IS NOT NULL\n ORDER BY ended_at\n DESC LIMIT 10) AS t", "describe": { "columns": [ { @@ -18,5 +18,5 @@ null ] }, - "hash": "7b1e6b67a20ae1128118d5f5cc0db4007fb9dc6fd20582a46ebb951fca3a7abd" + "hash": "8c2541cdfb84bfdbdc28285641166fe4c284dd6ed5245fbb90650d99afbf3812" } diff --git a/backend/.sqlx/query-8cb755510f2cfb23bdd0d1cf66b69949549a44855529f77a530f681a6e714646.json b/backend/.sqlx/query-8cb755510f2cfb23bdd0d1cf66b69949549a44855529f77a530f681a6e714646.json deleted file mode 100644 index 82b70ebe92..0000000000 --- a/backend/.sqlx/query-8cb755510f2cfb23bdd0d1cf66b69949549a44855529f77a530f681a6e714646.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT],\n $4\n )\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Uuid", - "Text", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "8cb755510f2cfb23bdd0d1cf66b69949549a44855529f77a530f681a6e714646" -} diff --git a/backend/.sqlx/query-1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d.json b/backend/.sqlx/query-8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc.json similarity index 70% rename from backend/.sqlx/query-1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d.json rename to backend/.sqlx/query-8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc.json index a73c4cf681..9302c273c3 100644 --- a/backend/.sqlx/query-1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d.json +++ b/backend/.sqlx/query-8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger\n WHERE workspace_id = $1", + "query": "\n SELECT \n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n filters AS \"filters: _\",\n initial_messages AS \"initial_messages: _\",\n url_runnable_args AS \"url_runnable_args: _\",\n can_return_message\n FROM \n websocket_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { @@ -114,5 +114,5 @@ false ] }, - "hash": "1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d" + "hash": "8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc" } diff --git a/backend/.sqlx/query-8efd06387ded837d7849adafe5bc93acb882ef90fc58b023650c875e0fd17047.json b/backend/.sqlx/query-8efd06387ded837d7849adafe5bc93acb882ef90fc58b023650c875e0fd17047.json deleted file mode 100644 index 005e8fedc9..0000000000 --- a/backend/.sqlx/query-8efd06387ded837d7849adafe5bc93acb882ef90fc58b023650c875e0fd17047.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $2, $3, $4, $5, $6, $7, $8) \n ON CONFLICT (job_id) DO UPDATE SET email = $2, username = $3, is_admin = $4, is_operator = $5, folders = $6, groups = $7, workspace_id = $8", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Varchar", - "Varchar", - "Bool", - "Bool", - "JsonbArray", - "TextArray", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "8efd06387ded837d7849adafe5bc93acb882ef90fc58b023650c875e0fd17047" -} diff --git a/backend/.sqlx/query-8f3ed45a0290cd9989f40f34775de5e8c3762597e6f55f8b9575a54ccc31e085.json b/backend/.sqlx/query-8f3ed45a0290cd9989f40f34775de5e8c3762597e6f55f8b9575a54ccc31e085.json deleted file mode 100644 index 4fd9cd8a4f..0000000000 --- a/backend/.sqlx/query-8f3ed45a0290cd9989f40f34775de5e8c3762597e6f55f8b9575a54ccc31e085.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "int4", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Uuid", - "Text", - "Jsonb" - ] - }, - "nullable": [ - null - ] - }, - "hash": "8f3ed45a0290cd9989f40f34775de5e8c3762597e6f55f8b9575a54ccc31e085" -} diff --git a/backend/.sqlx/query-8fb2581a439c26391e66ae7fac32c6cd2932f28ab6490ace027ed3a790b2a0f7.json b/backend/.sqlx/query-8fb2581a439c26391e66ae7fac32c6cd2932f28ab6490ace027ed3a790b2a0f7.json deleted file mode 100644 index 0ae81ab6dd..0000000000 --- a/backend/.sqlx/query-8fb2581a439c26391e66ae7fac32c6cd2932f28ab6490ace027ed3a790b2a0f7.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT edited_by, on_behalf_of_email FROM flow WHERE path = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "on_behalf_of_email", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "8fb2581a439c26391e66ae7fac32c6cd2932f28ab6490ace027ed3a790b2a0f7" -} diff --git a/backend/.sqlx/query-8fcf755b4a57ed4ebf10a57c0c82589075c240b16d872576a048349b56f468e5.json b/backend/.sqlx/query-8fcf755b4a57ed4ebf10a57c0c82589075c240b16d872576a048349b56f468e5.json new file mode 100644 index 0000000000..49153b05aa --- /dev/null +++ b/backend/.sqlx/query-8fcf755b4a57ed4ebf10a57c0c82589075c240b16d872576a048349b56f468e5.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_queue SET\n suspend = $1,\n suspend_until = now() + interval '14 day',\n running = true\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "8fcf755b4a57ed4ebf10a57c0c82589075c240b16d872576a048349b56f468e5" +} diff --git a/backend/.sqlx/query-8ff25d890d3f7019c5c6f2b47f29f4c7b2c91094036d9b5037ef9a4ac986ee53.json b/backend/.sqlx/query-8ff25d890d3f7019c5c6f2b47f29f4c7b2c91094036d9b5037ef9a4ac986ee53.json deleted file mode 100644 index de933d99e1..0000000000 --- a/backend/.sqlx/query-8ff25d890d3f7019c5c6f2b47f29f4c7b2c91094036d9b5037ef9a4ac986ee53.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger WHERE workspace_id = $1 AND http_method = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "route_path", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "is_async", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "requires_auth", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "static_asset_config: _", - "type_info": "Jsonb" - }, - { - "ordinal": 10, - "name": "is_static_website", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - { - "Custom": { - "name": "http_method", - "kind": { - "Enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] - } - } - } - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false - ] - }, - "hash": "8ff25d890d3f7019c5c6f2b47f29f4c7b2c91094036d9b5037ef9a4ac986ee53" -} diff --git a/backend/.sqlx/query-fdedd3909a97db5d43d9c46ff77b800b8efd647121b538deb023f96dbaac3715.json b/backend/.sqlx/query-903cf23d6b620388c645d5b8ac7d106bb6eea8af03e350d4ba19a4aba2cb9625.json similarity index 72% rename from backend/.sqlx/query-fdedd3909a97db5d43d9c46ff77b800b8efd647121b538deb023f96dbaac3715.json rename to backend/.sqlx/query-903cf23d6b620388c645d5b8ac7d106bb6eea8af03e350d4ba19a4aba2cb9625.json index 905707255f..abef34ebc0 100644 --- a/backend/.sqlx/query-fdedd3909a97db5d43d9c46ff77b800b8efd647121b538deb023f96dbaac3715.json +++ b/backend/.sqlx/query-903cf23d6b620388c645d5b8ac7d106bb6eea8af03e350d4ba19a4aba2cb9625.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT args AS \"args: Json>>\"\n FROM v2_job WHERE id = $1", + "query": "SELECT args AS \"args: Json>>\"\n FROM v2_job WHERE id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ true ] }, - "hash": "fdedd3909a97db5d43d9c46ff77b800b8efd647121b538deb023f96dbaac3715" + "hash": "903cf23d6b620388c645d5b8ac7d106bb6eea8af03e350d4ba19a4aba2cb9625" } diff --git a/backend/.sqlx/query-90635149190c59396ca557bf1670554a1e40d0ce9cc686ad09adca0904324cd8.json b/backend/.sqlx/query-90635149190c59396ca557bf1670554a1e40d0ce9cc686ad09adca0904324cd8.json deleted file mode 100644 index 9dce5f08b4..0000000000 --- a/backend/.sqlx/query-90635149190c59396ca557bf1670554a1e40d0ce9cc686ad09adca0904324cd8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE parent_job = $1\n AND f.id = j.id AND q.id = j.id\n AND suspend = $2 AND (f.flow_status->'step')::int = 0", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "90635149190c59396ca557bf1670554a1e40d0ce9cc686ad09adca0904324cd8" -} diff --git a/backend/.sqlx/query-91f23fcc27777c279c79e2682fc15c026e55f9ec3799be65a2e8920fe6174a17.json b/backend/.sqlx/query-91f23fcc27777c279c79e2682fc15c026e55f9ec3799be65a2e8920fe6174a17.json new file mode 100644 index 0000000000..5ac64ecaed --- /dev/null +++ b/backend/.sqlx/query-91f23fcc27777c279c79e2682fc15c026e55f9ec3799be65a2e8920fe6174a17.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT result as \"result: Json>>\"\n FROM v2_job_completed \n WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "result: Json>>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "91f23fcc27777c279c79e2682fc15c026e55f9ec3799be65a2e8920fe6174a17" +} diff --git a/backend/.sqlx/query-92c2b66eb6287449f5f8cc9f8d1329f748006ab131489d2fcac21d32641bb633.json b/backend/.sqlx/query-92c2b66eb6287449f5f8cc9f8d1329f748006ab131489d2fcac21d32641bb633.json new file mode 100644 index 0000000000..14d37809d5 --- /dev/null +++ b/backend/.sqlx/query-92c2b66eb6287449f5f8cc9f8d1329f748006ab131489d2fcac21d32641bb633.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger \n SET \n server_id = $1, \n last_server_ping = now(),\n error = 'Connecting...'\n WHERE \n enabled IS TRUE \n AND workspace_id = $2 \n AND path = $3 \n AND (last_server_ping IS NULL \n OR last_server_ping < now() - INTERVAL '15 seconds'\n ) \n RETURNING true\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "92c2b66eb6287449f5f8cc9f8d1329f748006ab131489d2fcac21d32641bb633" +} diff --git a/backend/.sqlx/query-4535c8effd1bae49894d13293a37e1ee949cf9108239032cb3addbf350fb33de.json b/backend/.sqlx/query-92c7c961198e506426bf3f97a8ddbb34af450041c675b30b708fed3ef9e01d2d.json similarity index 72% rename from backend/.sqlx/query-4535c8effd1bae49894d13293a37e1ee949cf9108239032cb3addbf350fb33de.json rename to backend/.sqlx/query-92c7c961198e506426bf3f97a8ddbb34af450041c675b30b708fed3ef9e01d2d.json index a500b04785..fcb9657c8a 100644 --- a/backend/.sqlx/query-4535c8effd1bae49894d13293a37e1ee949cf9108239032cb3addbf350fb33de.json +++ b/backend/.sqlx/query-92c7c961198e506426bf3f97a8ddbb34af450041c675b30b708fed3ef9e01d2d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n job_kind AS \"job_kind!: JobKind\",\n script_hash AS \"script_hash: ScriptHash\",\n flow_status AS \"flow_status!: Json>\",\n raw_flow AS \"raw_flow: Json>\"\n FROM v2_as_queue WHERE id = $1 AND workspace_id = $2 LIMIT 1", + "query": "SELECT\n kind AS \"job_kind!: JobKind\",\n runnable_id AS \"script_hash: ScriptHash\",\n flow_status AS \"flow_status!: Json>\",\n raw_flow AS \"raw_flow: Json>\"\n FROM v2_job INNER JOIN v2_job_status ON v2_job.id = v2_job_status.id WHERE v2_job.id = $1 AND v2_job.workspace_id = $2 LIMIT 1", "describe": { "columns": [ { @@ -57,11 +57,11 @@ ] }, "nullable": [ - true, + false, true, true, true ] }, - "hash": "4535c8effd1bae49894d13293a37e1ee949cf9108239032cb3addbf350fb33de" + "hash": "92c7c961198e506426bf3f97a8ddbb34af450041c675b30b708fed3ef9e01d2d" } diff --git a/backend/.sqlx/query-33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe.json b/backend/.sqlx/query-92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b.json similarity index 51% rename from backend/.sqlx/query-33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe.json rename to backend/.sqlx/query-92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b.json index 8728e35a0c..7df22ca7b7 100644 --- a/backend/.sqlx/query-33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe.json +++ b/backend/.sqlx/query-92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)", + "query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)\n VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)\n ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7", "describe": { "columns": [], "parameters": { @@ -17,5 +17,5 @@ }, "nullable": [] }, - "hash": "33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe" + "hash": "92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b" } diff --git a/backend/.sqlx/query-94f11d70062eebce384fe0fde527f3d6cebca1aa84a6f792c2a962b798f8da22.json b/backend/.sqlx/query-94f11d70062eebce384fe0fde527f3d6cebca1aa84a6f792c2a962b798f8da22.json new file mode 100644 index 0000000000..79d7fdcf4d --- /dev/null +++ b/backend/.sqlx/query-94f11d70062eebce384fe0fde527f3d6cebca1aa84a6f792c2a962b798f8da22.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "94f11d70062eebce384fe0fde527f3d6cebca1aa84a6f792c2a962b798f8da22" +} diff --git a/backend/.sqlx/query-96c0e34708bbba29db162e7289a942addd4581dddc88663b6c2cbae87ec205fc.json b/backend/.sqlx/query-96c0e34708bbba29db162e7289a942addd4581dddc88663b6c2cbae87ec205fc.json new file mode 100644 index 0000000000..0f6659264a --- /dev/null +++ b/backend/.sqlx/query-96c0e34708bbba29db162e7289a942addd4581dddc88663b6c2cbae87ec205fc.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "int4", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Uuid", + "Text", + "Jsonb" + ] + }, + "nullable": [ + null + ] + }, + "hash": "96c0e34708bbba29db162e7289a942addd4581dddc88663b6c2cbae87ec205fc" +} diff --git a/backend/.sqlx/query-97048ce0bcabb9baecb80cde5ab3c989e1575fbd20ef22766d2887a86dce15e1.json b/backend/.sqlx/query-97048ce0bcabb9baecb80cde5ab3c989e1575fbd20ef22766d2887a86dce15e1.json deleted file mode 100644 index c8ab870c76..0000000000 --- a/backend/.sqlx/query-97048ce0bcabb9baecb80cde5ab3c989e1575fbd20ef22766d2887a86dce15e1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT flow.versions[array_upper(flow.versions, 1)] AS \"version!: i64\"\n FROM flow WHERE path = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "version!: i64", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "97048ce0bcabb9baecb80cde5ab3c989e1575fbd20ef22766d2887a86dce15e1" -} diff --git a/backend/.sqlx/query-97942578df746c8c8103b403cfc4e44ef5a0f082bdde854900064325adc4dd77.json b/backend/.sqlx/query-97942578df746c8c8103b403cfc4e44ef5a0f082bdde854900064325adc4dd77.json deleted file mode 100644 index 2014d9e6e4..0000000000 --- a/backend/.sqlx/query-97942578df746c8c8103b403cfc4e44ef5a0f082bdde854900064325adc4dd77.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM capture\n WHERE workspace_id = $1\n AND created_at <=\n (\n SELECT created_at\n FROM capture\n WHERE workspace_id = $1\n ORDER BY created_at DESC\n OFFSET $2\n LIMIT 1\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "97942578df746c8c8103b403cfc4e44ef5a0f082bdde854900064325adc4dd77" -} diff --git a/backend/.sqlx/query-995b194da28092d5aa053df936e7a9ee4b80cf3ade038a032c57ecff8fa3c6cf.json b/backend/.sqlx/query-995b194da28092d5aa053df936e7a9ee4b80cf3ade038a032c57ecff8fa3c6cf.json new file mode 100644 index 0000000000..d5946b5114 --- /dev/null +++ b/backend/.sqlx/query-995b194da28092d5aa053df936e7a9ee4b80cf3ade038a032c57ecff8fa3c6cf.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM agent_token_blacklist WHERE expires_at <= now() RETURNING token", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "token", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "995b194da28092d5aa053df936e7a9ee4b80cf3ade038a032c57ecff8fa3c6cf" +} diff --git a/backend/.sqlx/query-997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606.json b/backend/.sqlx/query-997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606.json new file mode 100644 index 0000000000..1edf26100f --- /dev/null +++ b/backend/.sqlx/query-997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE worker_ping SET\nping_at = now(),\njobs_executed = 1,\ncurrent_job_id = $1,\ncurrent_job_workspace_id = 'admins'\nWHERE worker = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [] + }, + "hash": "997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606" +} diff --git a/backend/.sqlx/query-999edc6f54a9efb6dc6237992dad59f418ed6fc2a98ddb9bbc33dce5f029d904.json b/backend/.sqlx/query-999edc6f54a9efb6dc6237992dad59f418ed6fc2a98ddb9bbc33dce5f029d904.json new file mode 100644 index 0000000000..8748572af7 --- /dev/null +++ b/backend/.sqlx/query-999edc6f54a9efb6dc6237992dad59f418ed6fc2a98ddb9bbc33dce5f029d904.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger \n SET\n last_server_ping = NULL \n WHERE \n workspace_id = $1 \n AND path = $2 \n AND server_id IS NULL", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "999edc6f54a9efb6dc6237992dad59f418ed6fc2a98ddb9bbc33dce5f029d904" +} diff --git a/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json b/backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json similarity index 66% rename from backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json rename to backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json index 9709a354cf..4a85852957 100644 --- a/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json +++ b/backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c" + "hash": "9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1" } diff --git a/backend/.sqlx/query-9aeee333b1dbe58ba819ba3b2713242b54d77b46b5f785ae66b8104f89f43219.json b/backend/.sqlx/query-9aeee333b1dbe58ba819ba3b2713242b54d77b46b5f785ae66b8104f89f43219.json deleted file mode 100644 index 271a508a83..0000000000 --- a/backend/.sqlx/query-9aeee333b1dbe58ba819ba3b2713242b54d77b46b5f785ae66b8104f89f43219.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT f.path\n FROM flow_workspace_runnables fwr \n JOIN flow f \n ON fwr.flow_path = f.path AND fwr.workspace_id = f.workspace_id\n WHERE fwr.runnable_path = $1 AND fwr.runnable_is_flow = $2 AND fwr.workspace_id = $3", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Bool", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9aeee333b1dbe58ba819ba3b2713242b54d77b46b5f785ae66b8104f89f43219" -} diff --git a/backend/.sqlx/query-a0833b9899833166891c5de926f78632fae1123e736d728bf92cb2de004b6826.json b/backend/.sqlx/query-9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3.json similarity index 69% rename from backend/.sqlx/query-a0833b9899833166891c5de926f78632fae1123e736d728bf92cb2de004b6826.json rename to backend/.sqlx/query-9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3.json index a837158a37..f4251250be 100644 --- a/backend/.sqlx/query-a0833b9899833166891c5de926f78632fae1123e736d728bf92cb2de004b6826.json +++ b/backend/.sqlx/query-9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2", + "query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2", "describe": { "columns": [ { @@ -32,12 +32,18 @@ "ordinal": 5, "name": "edited_by", "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "version", + "type_info": "Int8" } ], "parameters": { "Left": [ "Text", - "Text" + "Text", + "Int8" ] }, "nullable": [ @@ -46,8 +52,9 @@ null, null, true, + false, false ] }, - "hash": "a0833b9899833166891c5de926f78632fae1123e736d728bf92cb2de004b6826" + "hash": "9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3" } diff --git a/backend/.sqlx/query-9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8.json b/backend/.sqlx/query-9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8.json deleted file mode 100644 index de9b6ea295..0000000000 --- a/backend/.sqlx/query-9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sqs_trigger (\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "TextArray", - "Varchar", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Bool", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8" -} diff --git a/backend/.sqlx/query-9c19ad9ab14325587d662539c04e18e8dfbdb0bf1dd4c0dc07a55f4eeb4eb5f8.json b/backend/.sqlx/query-9c19ad9ab14325587d662539c04e18e8dfbdb0bf1dd4c0dc07a55f4eeb4eb5f8.json deleted file mode 100644 index 89368ea0c8..0000000000 --- a/backend/.sqlx/query-9c19ad9ab14325587d662539c04e18e8dfbdb0bf1dd4c0dc07a55f4eeb4eb5f8.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_as_queue LEFT JOIN concurrency_key ON concurrency_key.job_id = v2_as_queue.id\n WHERE key = $1 AND running = false AND canceled = false AND scheduled_for >= $2 AND scheduled_for < $3", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Timestamptz", - "Timestamptz" - ] - }, - "nullable": [ - null - ] - }, - "hash": "9c19ad9ab14325587d662539c04e18e8dfbdb0bf1dd4c0dc07a55f4eeb4eb5f8" -} diff --git a/backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json similarity index 65% rename from backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json rename to backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json index 60332810b2..c21c30c012 100644 --- a/backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json +++ b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload!: _\", trigger_extra as \"trigger_extra: _\" FROM capture WHERE id = $1 AND workspace_id = $2", + "query": "\n SELECT \n id, \n created_at, \n trigger_kind AS \"trigger_kind: _\", \n main_args AS \"main_args!: _\", \n preprocessor_args AS \"preprocessor_args: _\"\n FROM \n capture\n WHERE \n id = $1 \n AND workspace_id = $2\n ", "describe": { "columns": [ { @@ -29,7 +29,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -37,12 +38,12 @@ }, { "ordinal": 3, - "name": "payload!: _", + "name": "main_args!: _", "type_info": "Jsonb" }, { "ordinal": 4, - "name": "trigger_extra: _", + "name": "preprocessor_args: _", "type_info": "Jsonb" } ], @@ -60,5 +61,5 @@ true ] }, - "hash": "e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7" + "hash": "9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf" } diff --git a/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json b/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json new file mode 100644 index 0000000000..5f28f48eda --- /dev/null +++ b/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM workspace WHERE owner = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f" +} diff --git a/backend/.sqlx/query-9dec888d4b0666d1843fbbc4fb2475fd947f047a9965bc110d7338208b77783d.json b/backend/.sqlx/query-9dec888d4b0666d1843fbbc4fb2475fd947f047a9965bc110d7338208b77783d.json new file mode 100644 index 0000000000..7c49fe890e --- /dev/null +++ b/backend/.sqlx/query-9dec888d4b0666d1843fbbc4fb2475fd947f047a9965bc110d7338208b77783d.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT flow_version.id from flow\n INNER JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "9dec888d4b0666d1843fbbc4fb2475fd947f047a9965bc110d7338208b77783d" +} diff --git a/backend/.sqlx/query-9dfc48c84c52b8b027594b1c6638080f1174e06c8621245fd82ea1515e2d9a96.json b/backend/.sqlx/query-9dfc48c84c52b8b027594b1c6638080f1174e06c8621245fd82ea1515e2d9a96.json index 737090f5b8..b35ca6aa5b 100644 --- a/backend/.sqlx/query-9dfc48c84c52b8b027594b1c6638080f1174e06c8621245fd82ea1515e2d9a96.json +++ b/backend/.sqlx/query-9dfc48c84c52b8b027594b1c6638080f1174e06c8621245fd82ea1515e2d9a96.json @@ -12,7 +12,7 @@ "parameters": { "Left": [ "Varchar", - "Json", + "Jsonb", "Int4", "Text" ] diff --git a/backend/.sqlx/query-a0c20436e0506bf9e0e50bd3dcabdd35131cc374edac0a08c5492954f08c9d98.json b/backend/.sqlx/query-a0c20436e0506bf9e0e50bd3dcabdd35131cc374edac0a08c5492954f08c9d98.json new file mode 100644 index 0000000000..1724d969cf --- /dev/null +++ b/backend/.sqlx/query-a0c20436e0506bf9e0e50bd3dcabdd35131cc374edac0a08c5492954f08c9d98.json @@ -0,0 +1,222 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10,\n $11, $12, $13, $14,\n $15, $16, $17,\n $18, $19,\n $20, $21, $22, $23,\n $24, $25, $26, $27\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "schedule", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "timezone", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "on_failure", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "on_failure_times", + "type_info": "Int4" + }, + { + "ordinal": 15, + "name": "on_failure_exact", + "type_info": "Bool" + }, + { + "ordinal": 16, + "name": "on_failure_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "on_recovery", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "on_recovery_times", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "on_recovery_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "on_success", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "on_success_extra_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "ws_error_handler_muted", + "type_info": "Bool" + }, + { + "ordinal": 23, + "name": "retry", + "type_info": "Jsonb" + }, + { + "ordinal": 24, + "name": "no_flow_overlap", + "type_info": "Bool" + }, + { + "ordinal": 25, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "paused_until", + "type_info": "Timestamptz" + }, + { + "ordinal": 29, + "name": "cron_version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Jsonb", + "Bool", + "Varchar", + "Varchar", + "Int4", + "Bool", + "Jsonb", + "Varchar", + "Int4", + "Jsonb", + "Varchar", + "Jsonb", + "Bool", + "Jsonb", + "Varchar", + "Bool", + "Varchar", + "Timestamptz", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + false, + true, + true, + true, + true, + true + ] + }, + "hash": "a0c20436e0506bf9e0e50bd3dcabdd35131cc374edac0a08c5492954f08c9d98" +} diff --git a/backend/.sqlx/query-a10ec229d7ed89f563b6b33e70e8ede5135a849e7b9108c37bfd90990a4be780.json b/backend/.sqlx/query-a10ec229d7ed89f563b6b33e70e8ede5135a849e7b9108c37bfd90990a4be780.json new file mode 100644 index 0000000000..9c49c0794c --- /dev/null +++ b/backend/.sqlx/query-a10ec229d7ed89f563b6b33e70e8ede5135a849e7b9108c37bfd90990a4be780.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'iterator', 'index'],\n ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "int4", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Uuid" + ] + }, + "nullable": [ + null + ] + }, + "hash": "a10ec229d7ed89f563b6b33e70e8ede5135a849e7b9108c37bfd90990a4be780" +} diff --git a/backend/.sqlx/query-0cc3618495d5d024b2a173c58a3a8bb2a9d69b7b6e7ed6b0d0064fa2ce9c2e31.json b/backend/.sqlx/query-a21a16064b51580a8f5c2505cb0c701281dbfa94e40994fdd1cadc86a26c294e.json similarity index 54% rename from backend/.sqlx/query-0cc3618495d5d024b2a173c58a3a8bb2a9d69b7b6e7ed6b0d0064fa2ce9c2e31.json rename to backend/.sqlx/query-a21a16064b51580a8f5c2505cb0c701281dbfa94e40994fdd1cadc86a26c294e.json index 79e36950f6..dc20d9d86b 100644 --- a/backend/.sqlx/query-0cc3618495d5d024b2a173c58a3a8bb2a9d69b7b6e7ed6b0d0064fa2ce9c2e31.json +++ b/backend/.sqlx/query-a21a16064b51580a8f5c2505cb0c701281dbfa94e40994fdd1cadc86a26c294e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT value\n FROM global_settings\n WHERE name = 'openai_azure_base_path'", + "query": "SELECT value\n FROM global_settings\n WHERE name = 'openai_azure_base_path'", "describe": { "columns": [ { @@ -16,5 +16,5 @@ false ] }, - "hash": "0cc3618495d5d024b2a173c58a3a8bb2a9d69b7b6e7ed6b0d0064fa2ce9c2e31" + "hash": "a21a16064b51580a8f5c2505cb0c701281dbfa94e40994fdd1cadc86a26c294e" } diff --git a/backend/.sqlx/query-a269c388056eabe4b045948f451ea74ffbb4c0ed7e694f8f03d92f2a7c118af9.json b/backend/.sqlx/query-a269c388056eabe4b045948f451ea74ffbb4c0ed7e694f8f03d92f2a7c118af9.json new file mode 100644 index 0000000000..e81acc3584 --- /dev/null +++ b/backend/.sqlx/query-a269c388056eabe4b045948f451ea74ffbb4c0ed7e694f8f03d92f2a7c118af9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM http_trigger \n WHERE workspace_id = $1 \n AND path = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "a269c388056eabe4b045948f451ea74ffbb4c0ed7e694f8f03d92f2a7c118af9" +} diff --git a/backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json b/backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json new file mode 100644 index 0000000000..4e0d53b0f3 --- /dev/null +++ b/backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text) RETURNING length(logs)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "length", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Varchar" + ] + }, + "nullable": [ + null + ] + }, + "hash": "a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c" +} diff --git a/backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json b/backend/.sqlx/query-a3debece1a4171881431640f6af264d402d32e2b6ce925d1ebf1f60f3b688207.json similarity index 51% rename from backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json rename to backend/.sqlx/query-a3debece1a4171881431640f6af264d402d32e2b6ce925d1ebf1f60f3b688207.json index 3603ed5c5b..9448456dbb 100644 --- a/backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json +++ b/backend/.sqlx/query-a3debece1a4171881431640f6af264d402d32e2b6ce925d1ebf1f60f3b688207.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3\n RETURNING flow_status AS \"flow_status: Json>\"", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3\n RETURNING flow_status AS \"flow_status: Json>\"", "describe": { "columns": [ { @@ -20,5 +20,5 @@ true ] }, - "hash": "30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266" + "hash": "a3debece1a4171881431640f6af264d402d32e2b6ce925d1ebf1f60f3b688207" } diff --git a/backend/.sqlx/query-a3f315fdae54e51b56b0681fab2bbff779a4a62d129916dd4c3054b45e0b654e.json b/backend/.sqlx/query-a3f315fdae54e51b56b0681fab2bbff779a4a62d129916dd4c3054b45e0b654e.json new file mode 100644 index 0000000000..dc0b37ca8c --- /dev/null +++ b/backend/.sqlx/query-a3f315fdae54e51b56b0681fab2bbff779a4a62d129916dd4c3054b45e0b654e.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2)\n WHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "a3f315fdae54e51b56b0681fab2bbff779a4a62d129916dd4c3054b45e0b654e" +} diff --git a/backend/.sqlx/query-2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7.json b/backend/.sqlx/query-a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0.json similarity index 61% rename from backend/.sqlx/query-2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7.json rename to backend/.sqlx/query-a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0.json index be9f392048..39ba075077 100644 --- a/backend/.sqlx/query-2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7.json +++ b/backend/.sqlx/query-a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0.json @@ -1,80 +1,95 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM sqs_trigger\n WHERE workspace_id = $1", + "query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "path", - "type_info": "Varchar" + "name": "aws_auth_resource_type: _", + "type_info": { + "Custom": { + "name": "aws_auth_resource_type", + "kind": { + "Enum": [ + "oidc", + "credentials" + ] + } + } + } }, { "ordinal": 1, - "name": "queue_url", - "type_info": "Varchar" - }, - { - "ordinal": 2, "name": "aws_resource_path", "type_info": "Varchar" }, { - "ordinal": 3, + "ordinal": 2, "name": "message_attributes", "type_info": "TextArray" }, { - "ordinal": 4, - "name": "script_path", + "ordinal": 3, + "name": "queue_url", "type_info": "Varchar" }, { - "ordinal": 5, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 6, + "ordinal": 4, "name": "workspace_id", "type_info": "Varchar" }, + { + "ordinal": 5, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "script_path", + "type_info": "Varchar" + }, { "ordinal": 7, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 8, "name": "edited_by", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 9, "name": "email", "type_info": "Varchar" }, { - "ordinal": 9, + "ordinal": 10, "name": "edited_at", "type_info": "Timestamptz" }, - { - "ordinal": 10, - "name": "extra_perms", - "type_info": "Jsonb" - }, { "ordinal": 11, - "name": "error", - "type_info": "Text" - }, - { - "ordinal": 12, "name": "server_id", "type_info": "Varchar" }, { - "ordinal": 13, + "ordinal": 12, "name": "last_server_ping", "type_info": "Timestamptz" }, + { + "ordinal": 13, + "name": "extra_perms", + "type_info": "Jsonb" + }, { "ordinal": 14, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 15, "name": "enabled", "type_info": "Bool" } @@ -85,7 +100,6 @@ ] }, "nullable": [ - false, false, false, true, @@ -95,6 +109,8 @@ false, false, false, + false, + false, true, true, true, @@ -102,5 +118,5 @@ false ] }, - "hash": "2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7" + "hash": "a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0" } diff --git a/backend/.sqlx/query-a7ffc5b983d365159ef379ec6a2ab0dc7217d1b3f86f362c213982984d2cf652.json b/backend/.sqlx/query-a7ffc5b983d365159ef379ec6a2ab0dc7217d1b3f86f362c213982984d2cf652.json new file mode 100644 index 0000000000..03b05af6fb --- /dev/null +++ b/backend/.sqlx/query-a7ffc5b983d365159ef379ec6a2ab0dc7217d1b3f86f362c213982984d2cf652.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n capture_config \n SET \n last_server_ping = now(), \n error = $1 \n WHERE \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'gcp' AND \n server_id = $5 AND \n last_client_ping > NOW() - INTERVAL '10 seconds' \n RETURNING 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Bool", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "a7ffc5b983d365159ef379ec6a2ab0dc7217d1b3f86f362c213982984d2cf652" +} diff --git a/backend/.sqlx/query-a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d.json b/backend/.sqlx/query-a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d.json new file mode 100644 index 0000000000..6ac4ee6755 --- /dev/null +++ b/backend/.sqlx/query-a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d.json @@ -0,0 +1,24 @@ +{ + "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" +} diff --git a/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json b/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json new file mode 100644 index 0000000000..a3cb5a82b9 --- /dev/null +++ b/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json @@ -0,0 +1,71 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT has_preprocessor, language as \"language: _\", content, schema as \"schema: _\" FROM script WHERE workspace_id = $1 AND hash = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "has_preprocessor", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "language: _", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "content", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "schema: _", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + true, + false, + false, + true + ] + }, + "hash": "a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b" +} diff --git a/backend/.sqlx/query-a8ca4e588e0bf3c4bba2fe4b68a5364e4cba99964513599f8a012a5680d3dca8.json b/backend/.sqlx/query-a8ca4e588e0bf3c4bba2fe4b68a5364e4cba99964513599f8a012a5680d3dca8.json new file mode 100644 index 0000000000..1d4913e8d9 --- /dev/null +++ b/backend/.sqlx/query-a8ca4e588e0bf3c4bba2fe4b68a5364e4cba99964513599f8a012a5680d3dca8.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS alerts_by_workspace ON alerts (workspace_id);", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "a8ca4e588e0bf3c4bba2fe4b68a5364e4cba99964513599f8a012a5680d3dca8" +} diff --git a/backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json b/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json similarity index 68% rename from backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json rename to backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json index 93a338c13f..16fea8720d 100644 --- a/backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json +++ b/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1", + "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", schema AS \"schema: String\", schema_validation AS \"schema_validation: bool\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1", "describe": { "columns": [ { @@ -39,7 +39,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } @@ -52,6 +55,16 @@ }, { "ordinal": 4, + "name": "schema: String", + "type_info": "Json" + }, + { + "ordinal": 5, + "name": "schema_validation: bool", + "type_info": "Bool" + }, + { + "ordinal": 6, "name": "use_tar", "type_info": "Bool" } @@ -66,8 +79,10 @@ true, false, true, + true, + false, null ] }, - "hash": "d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799" + "hash": "a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a" } diff --git a/backend/.sqlx/query-83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5.json b/backend/.sqlx/query-a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20.json similarity index 63% rename from backend/.sqlx/query-83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5.json rename to backend/.sqlx/query-a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20.json index 24cabff562..0310f2197b 100644 --- a/backend/.sqlx/query-83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5.json +++ b/backend/.sqlx/query-a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id", + "query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id", "describe": { "columns": [ { @@ -13,12 +13,13 @@ "Left": [ "Int8", "Text", - "Varchar" + "Varchar", + "Bool" ] }, "nullable": [ false ] }, - "hash": "83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5" + "hash": "a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20" } diff --git a/backend/.sqlx/query-aa0215d4174c1aeda8631bcd582c895329d2daf722d360fbcbdef6f04bb1400f.json b/backend/.sqlx/query-aa0215d4174c1aeda8631bcd582c895329d2daf722d360fbcbdef6f04bb1400f.json new file mode 100644 index 0000000000..9526b72f55 --- /dev/null +++ b/backend/.sqlx/query-aa0215d4174c1aeda8631bcd582c895329d2daf722d360fbcbdef6f04bb1400f.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\",\n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\",\n EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS \"gcp_used!\"\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "websocket_used!", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "http_routes_used!", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "kafka_used!", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "nats_used!", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "postgres_used!", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "mqtt_used!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "sqs_used!", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "gcp_used!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "hash": "aa0215d4174c1aeda8631bcd582c895329d2daf722d360fbcbdef6f04bb1400f" +} diff --git a/backend/.sqlx/query-4507c3907bf49f93f6c17956d9cf9495f4538b20ce0299acde7578386db4278c.json b/backend/.sqlx/query-aa4ecf6b7ab078544c280957ad6614c4ccd1bc9b4695ca56f56ba0899b23562f.json similarity index 66% rename from backend/.sqlx/query-4507c3907bf49f93f6c17956d9cf9495f4538b20ce0299acde7578386db4278c.json rename to backend/.sqlx/query-aa4ecf6b7ab078544c280957ad6614c4ccd1bc9b4695ca56f56ba0899b23562f.json index dc57aefcdc..c231ff2bd0 100644 --- a/backend/.sqlx/query-4507c3907bf49f93f6c17956d9cf9495f4538b20ce0299acde7578386db4278c.json +++ b/backend/.sqlx/query-aa4ecf6b7ab078544c280957ad6614c4ccd1bc9b4695ca56f56ba0899b23562f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO parallel_monitor_lock (parent_flow_id, job_id)\n VALUES ($1, $2)", + "query": "INSERT INTO parallel_monitor_lock (parent_flow_id, job_id)\n VALUES ($1, $2)", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "4507c3907bf49f93f6c17956d9cf9495f4538b20ce0299acde7578386db4278c" + "hash": "aa4ecf6b7ab078544c280957ad6614c4ccd1bc9b4695ca56f56ba0899b23562f" } diff --git a/backend/.sqlx/query-a439552f74ed0ba305e3d9cb99ae9e5d24834082ebf2fe9fd3964fdd80b69ccb.json b/backend/.sqlx/query-aa523c363186575b4bd2537b8e2430e6938e7cc35f8c9e2d1c5459a85443cbdd.json similarity index 52% rename from backend/.sqlx/query-a439552f74ed0ba305e3d9cb99ae9e5d24834082ebf2fe9fd3964fdd80b69ccb.json rename to backend/.sqlx/query-aa523c363186575b4bd2537b8e2430e6938e7cc35f8c9e2d1c5459a85443cbdd.json index 691a6d5e31..520fb2c559 100644 --- a/backend/.sqlx/query-a439552f74ed0ba305e3d9cb99ae9e5d24834082ebf2fe9fd3964fdd80b69ccb.json +++ b/backend/.sqlx/query-aa523c363186575b4bd2537b8e2430e6938e7cc35f8c9e2d1c5459a85443cbdd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2,\n occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus),\n memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", + "query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2,\n occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus),\n memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", "describe": { "columns": [], "parameters": { @@ -20,5 +20,5 @@ }, "nullable": [] }, - "hash": "a439552f74ed0ba305e3d9cb99ae9e5d24834082ebf2fe9fd3964fdd80b69ccb" + "hash": "aa523c363186575b4bd2537b8e2430e6938e7cc35f8c9e2d1c5459a85443cbdd" } diff --git a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json b/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json index 3284fb1846..c8cbb8e10d 100644 --- a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json +++ b/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json @@ -63,7 +63,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } diff --git a/backend/.sqlx/query-ac01e45d3335015f53f3d63fe159e631efb65c3d326b6b6ae8361a2116bff145.json b/backend/.sqlx/query-ac01e45d3335015f53f3d63fe159e631efb65c3d326b6b6ae8361a2116bff145.json deleted file mode 100644 index 929157b5d7..0000000000 --- a/backend/.sqlx/query-ac01e45d3335015f53f3d63fe159e631efb65c3d326b6b6ae8361a2116bff145.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT content FROM script WHERE path = $1 AND workspace_id = $2\n AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND\n workspace_id = $2)\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "content", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "ac01e45d3335015f53f3d63fe159e631efb65c3d326b6b6ae8361a2116bff145" -} diff --git a/backend/.sqlx/query-acbea8740b28c26942c50edcf5618cd141e68cf83a7dcae7d3c1b8a7ba94425b.json b/backend/.sqlx/query-acbea8740b28c26942c50edcf5618cd141e68cf83a7dcae7d3c1b8a7ba94425b.json deleted file mode 100644 index 410aed9cc5..0000000000 --- a/backend/.sqlx/query-acbea8740b28c26942c50edcf5618cd141e68cf83a7dcae7d3c1b8a7ba94425b.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM flow_workspace_runnables WHERE flow_path = $1 AND workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "acbea8740b28c26942c50edcf5618cd141e68cf83a7dcae7d3c1b8a7ba94425b" -} diff --git a/backend/.sqlx/query-ad88831c9021b79c9037a925c2b53be3a93dbe2a67bdc4d4342c84f439b5eaf9.json b/backend/.sqlx/query-ad88831c9021b79c9037a925c2b53be3a93dbe2a67bdc4d4342c84f439b5eaf9.json new file mode 100644 index 0000000000..8d51b798e9 --- /dev/null +++ b/backend/.sqlx/query-ad88831c9021b79c9037a925c2b53be3a93dbe2a67bdc4d4342c84f439b5eaf9.json @@ -0,0 +1,85 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n j.id,\n j.kind AS \"kind: _\",\n COALESCE(s.path, f.path) AS \"script_path!\",\n COALESCE(s.hash, f.id) AS \"script_hash!: _\",\n COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS \"scheduled_for!: _\",\n args AS input,\n COALESCE(s.schema, f.schema) AS \"schema: _\"\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n LEFT JOIN v2_job_completed jc ON jc.id = j.id\n LEFT JOIN v2_job_queue jq ON jq.id = j.id\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "kind: _", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "script_path!", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "script_hash!: _", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "scheduled_for!: _", + "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "input", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "schema: _", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "UuidArray", + "Text" + ] + }, + "nullable": [ + false, + false, + null, + null, + null, + true, + null + ] + }, + "hash": "ad88831c9021b79c9037a925c2b53be3a93dbe2a67bdc4d4342c84f439b5eaf9" +} diff --git a/backend/.sqlx/query-9a050ab74cfc13a4b855408e61ebcb0a9d27e5563fa7af5e00a4916b03a62ddb.json b/backend/.sqlx/query-adb0090afd3ce918d8b80ff51d9f6104a430a11d7c5cb9447025d11506585708.json similarity index 57% rename from backend/.sqlx/query-9a050ab74cfc13a4b855408e61ebcb0a9d27e5563fa7af5e00a4916b03a62ddb.json rename to backend/.sqlx/query-adb0090afd3ce918d8b80ff51d9f6104a430a11d7c5cb9447025d11506585708.json index a0c5bd7642..bbe9b8bb86 100644 --- a/backend/.sqlx/query-9a050ab74cfc13a4b855408e61ebcb0a9d27e5563fa7af5e00a4916b03a62ddb.json +++ b/backend/.sqlx/query-adb0090afd3ce918d8b80ff51d9f6104a430a11d7c5cb9447025d11506585708.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE path = $1 AND workspace_id = $2)", + "query": "SELECT EXISTS(\n SELECT 1 FROM http_trigger \n WHERE path = $1 AND workspace_id = $2\n )", "describe": { "columns": [ { @@ -19,5 +19,5 @@ null ] }, - "hash": "9a050ab74cfc13a4b855408e61ebcb0a9d27e5563fa7af5e00a4916b03a62ddb" + "hash": "adb0090afd3ce918d8b80ff51d9f6104a430a11d7c5cb9447025d11506585708" } diff --git a/backend/.sqlx/query-aeca239a2997efc514ee56a6b3766fa5f32b5a56e28100a2487b478e5dc3eaec.json b/backend/.sqlx/query-aeca239a2997efc514ee56a6b3766fa5f32b5a56e28100a2487b478e5dc3eaec.json new file mode 100644 index 0000000000..db6ed5a29a --- /dev/null +++ b/backend/.sqlx/query-aeca239a2997efc514ee56a6b3766fa5f32b5a56e28100a2487b478e5dc3eaec.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Int8", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "aeca239a2997efc514ee56a6b3766fa5f32b5a56e28100a2487b478e5dc3eaec" +} diff --git a/backend/.sqlx/query-aed8bd751c3e988f422216e74acfb77dc03469355d2a0da0b2d6b4aeeea37d3e.json b/backend/.sqlx/query-aed8bd751c3e988f422216e74acfb77dc03469355d2a0da0b2d6b4aeeea37d3e.json new file mode 100644 index 0000000000..d8f93f74c4 --- /dev/null +++ b/backend/.sqlx/query-aed8bd751c3e988f422216e74acfb77dc03469355d2a0da0b2d6b4aeeea37d3e.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_leaf_jobs = JSONB_SET(coalesce(flow_leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $3), $3) = id", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "aed8bd751c3e988f422216e74acfb77dc03469355d2a0da0b2d6b4aeeea37d3e" +} diff --git a/backend/.sqlx/query-af925931f3217bbd32313678989ad1a66bbd8dacd12dea36608cc20197df358f.json b/backend/.sqlx/query-af925931f3217bbd32313678989ad1a66bbd8dacd12dea36608cc20197df358f.json deleted file mode 100644 index b90300787b..0000000000 --- a/backend/.sqlx/query-af925931f3217bbd32313678989ad1a66bbd8dacd12dea36608cc20197df358f.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1),\n ARRAY['step'],\n $2\n )\n WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "af925931f3217bbd32313678989ad1a66bbd8dacd12dea36608cc20197df358f" -} diff --git a/backend/.sqlx/query-b01160fe44d69834ac08bbf60feacb3e3caa02a04b084da44cdcb9103794b39e.json b/backend/.sqlx/query-b01160fe44d69834ac08bbf60feacb3e3caa02a04b084da44cdcb9103794b39e.json new file mode 100644 index 0000000000..0e87ff0632 --- /dev/null +++ b/backend/.sqlx/query-b01160fe44d69834ac08bbf60feacb3e3caa02a04b084da44cdcb9103794b39e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['cleanup_module', 'flow_jobs_to_clean'], COALESCE(flow_status->'cleanup_module'->'flow_jobs_to_clean', '[]'::jsonb) || $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "b01160fe44d69834ac08bbf60feacb3e3caa02a04b084da44cdcb9103794b39e" +} diff --git a/backend/.sqlx/query-b186efe51e7bb1924efbd7e9b36085502a1e77e30e59c7310c78976f77a810a3.json b/backend/.sqlx/query-b186efe51e7bb1924efbd7e9b36085502a1e77e30e59c7310c78976f77a810a3.json deleted file mode 100644 index d856ab0109..0000000000 --- a/backend/.sqlx/query-b186efe51e7bb1924efbd7e9b36085502a1e77e30e59c7310c78976f77a810a3.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT ai_resource, code_completion_model, ai_models FROM workspace_settings WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "ai_resource", - "type_info": "Jsonb" - }, - { - "ordinal": 1, - "name": "code_completion_model", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "ai_models", - "type_info": "VarcharArray" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true, - true, - false - ] - }, - "hash": "b186efe51e7bb1924efbd7e9b36085502a1e77e30e59c7310c78976f77a810a3" -} diff --git a/backend/.sqlx/query-b190ad25e22367f71c1e16e34bfbbe4303249c8d8664962d479056971f0409a2.json b/backend/.sqlx/query-b190ad25e22367f71c1e16e34bfbbe4303249c8d8664962d479056971f0409a2.json new file mode 100644 index 0000000000..def6dfdd14 --- /dev/null +++ b/backend/.sqlx/query-b190ad25e22367f71c1e16e34bfbbe4303249c8d8664962d479056971f0409a2.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM workspace_runnable_dependencies WHERE app_path = $1 AND workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "b190ad25e22367f71c1e16e34bfbbe4303249c8d8664962d479056971f0409a2" +} diff --git a/backend/.sqlx/query-47e6b25cc092ec8718a6581c76aca10b275653e10ea4aa17a8ef5091ca09294a.json b/backend/.sqlx/query-b1c96c527c4b263b5155d689eb88894ea93f0eaba37874f828a733062af17640.json similarity index 67% rename from backend/.sqlx/query-47e6b25cc092ec8718a6581c76aca10b275653e10ea4aa17a8ef5091ca09294a.json rename to backend/.sqlx/query-b1c96c527c4b263b5155d689eb88894ea93f0eaba37874f828a733062af17640.json index 3206f22bc5..8fd4c311c5 100644 --- a/backend/.sqlx/query-47e6b25cc092ec8718a6581c76aca10b275653e10ea4aa17a8ef5091ca09294a.json +++ b/backend/.sqlx/query-b1c96c527c4b263b5155d689eb88894ea93f0eaba37874f828a733062af17640.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT result, id\n FROM v2_job_completed\n WHERE id = ANY($1) AND workspace_id = $2", + "query": "SELECT result, id\n FROM v2_job_completed\n WHERE id = ANY($1) AND workspace_id = $2", "describe": { "columns": [ { @@ -25,5 +25,5 @@ false ] }, - "hash": "47e6b25cc092ec8718a6581c76aca10b275653e10ea4aa17a8ef5091ca09294a" + "hash": "b1c96c527c4b263b5155d689eb88894ea93f0eaba37874f828a733062af17640" } diff --git a/backend/.sqlx/query-62475252dcf54f32433b97ae011daf5d4205d160d2aedf463c7dfe944e93257a.json b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json similarity index 51% rename from backend/.sqlx/query-62475252dcf54f32433b97ae011daf5d4205d160d2aedf463c7dfe944e93257a.json rename to backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json index d2eab003f2..a20f4e47ad 100644 --- a/backend/.sqlx/query-62475252dcf54f32433b97ae011daf5d4205d160d2aedf463c7dfe944e93257a.json +++ b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO capture_config\n (workspace_id, path, is_flow, trigger_kind, trigger_config, owner, email)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path, is_flow, trigger_kind)\n DO UPDATE SET trigger_config = $5, owner = $6, email = $7, server_id = NULL, error = NULL", + "query": "\n INSERT INTO capture_config (\n workspace_id, path, is_flow, trigger_kind, trigger_config, owner, email\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n ON CONFLICT (workspace_id, path, is_flow, trigger_kind)\n DO UPDATE \n SET \n trigger_config = $5, \n owner = $6, \n email = $7, \n server_id = NULL, \n error = NULL\n ", "describe": { "columns": [], "parameters": { @@ -21,7 +21,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -33,5 +34,5 @@ }, "nullable": [] }, - "hash": "62475252dcf54f32433b97ae011daf5d4205d160d2aedf463c7dfe944e93257a" + "hash": "b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d" } diff --git a/backend/.sqlx/query-b41cef713e822bbd89b49b1f35cc662539d5e4af1dd0b5923d8ee17b772c5677.json b/backend/.sqlx/query-b41cef713e822bbd89b49b1f35cc662539d5e4af1dd0b5923d8ee17b772c5677.json deleted file mode 100644 index 8475175c69..0000000000 --- a/backend/.sqlx/query-b41cef713e822bbd89b49b1f35cc662539d5e4af1dd0b5923d8ee17b772c5677.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, static_asset_config, edited_by, email, edited_at, is_static_website) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), $13)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Bool", - "Bool", - "Bool", - { - "Custom": { - "name": "http_method", - "kind": { - "Enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] - } - } - }, - "Jsonb", - "Varchar", - "Varchar", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "b41cef713e822bbd89b49b1f35cc662539d5e4af1dd0b5923d8ee17b772c5677" -} diff --git a/backend/.sqlx/query-597b148ff09a1e0f369bb04781ee4e429ebce64a4d5c16b0f136142ad213cdb1.json b/backend/.sqlx/query-b41fa341e65ee348f468ed04ac1160770b19c0a00cd333abc48b29c54f863149.json similarity index 52% rename from backend/.sqlx/query-597b148ff09a1e0f369bb04781ee4e429ebce64a4d5c16b0f136142ad213cdb1.json rename to backend/.sqlx/query-b41fa341e65ee348f468ed04ac1160770b19c0a00cd333abc48b29c54f863149.json index 4a704e58d8..f37bea0531 100644 --- a/backend/.sqlx/query-597b148ff09a1e0f369bb04781ee4e429ebce64a4d5c16b0f136142ad213cdb1.json +++ b/backend/.sqlx/query-b41fa341e65ee348f468ed04ac1160770b19c0a00cd333abc48b29c54f863149.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n args AS \"args: Json>>\"\n FROM v2_job\n WHERE id = $1", + "query": "SELECT\n args AS \"args: Json>>\"\n FROM v2_job\n WHERE id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ true ] }, - "hash": "597b148ff09a1e0f369bb04781ee4e429ebce64a4d5c16b0f136142ad213cdb1" + "hash": "b41fa341e65ee348f468ed04ac1160770b19c0a00cd333abc48b29c54f863149" } diff --git a/backend/.sqlx/query-cf12a70e7b75ae471a0944de34502384be156cf25129f9c52bda34b240cf469a.json b/backend/.sqlx/query-b46a0fbebdc8e5e9852a06444b0aeaa4eaf67959e68b69eb2f0896ebe9244691.json similarity index 51% rename from backend/.sqlx/query-cf12a70e7b75ae471a0944de34502384be156cf25129f9c52bda34b240cf469a.json rename to backend/.sqlx/query-b46a0fbebdc8e5e9852a06444b0aeaa4eaf67959e68b69eb2f0896ebe9244691.json index 962572fa3d..2f6737bfb2 100644 --- a/backend/.sqlx/query-cf12a70e7b75ae471a0944de34502384be156cf25129f9c52bda34b240cf469a.json +++ b/backend/.sqlx/query-b46a0fbebdc8e5e9852a06444b0aeaa4eaf67959e68b69eb2f0896ebe9244691.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT leaf_jobs->$1::text AS \"leaf_jobs: Json>\", parent_job\n FROM v2_as_queue\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $2), $2) = id AND workspace_id = $3", + "query": "SELECT flow_leaf_jobs->$1::text AS \"leaf_jobs: Json>\", v2_job.parent_job\n FROM v2_job_status\n LEFT JOIN v2_job ON v2_job.id = v2_job_status.id AND v2_job.workspace_id = $3\n WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $2), $2) = v2_job_status.id", "describe": { "columns": [ { @@ -26,5 +26,5 @@ true ] }, - "hash": "cf12a70e7b75ae471a0944de34502384be156cf25129f9c52bda34b240cf469a" + "hash": "b46a0fbebdc8e5e9852a06444b0aeaa4eaf67959e68b69eb2f0896ebe9244691" } diff --git a/backend/.sqlx/query-b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc.json b/backend/.sqlx/query-b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc.json new file mode 100644 index 0000000000..0efca7e867 --- /dev/null +++ b/backend/.sqlx/query-b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET git_app_installations = (\n SELECT jsonb_agg(\n CASE\n WHEN (elem->>'installation_id')::bigint = $2 THEN $1::jsonb\n ELSE elem\n END\n )\n FROM jsonb_array_elements(git_app_installations) AS elem\n )\n WHERE workspace_id = $3\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Int8", + "Text" + ] + }, + "nullable": [] + }, + "hash": "b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc" +} diff --git a/backend/.sqlx/query-a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb.json b/backend/.sqlx/query-b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac.json similarity index 62% rename from backend/.sqlx/query-a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb.json rename to backend/.sqlx/query-b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac.json index 5f8386be37..a08c31e743 100644 --- a/backend/.sqlx/query-a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb.json +++ b/backend/.sqlx/query-b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND workspace_id = $2)", + "query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1", "describe": { "columns": [ { @@ -19,5 +19,5 @@ false ] }, - "hash": "a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb" + "hash": "b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac" } diff --git a/backend/.sqlx/query-b656927cd70b6667f3c72186ec04f0bf040da3af9e2eac3229264ec95b4755d8.json b/backend/.sqlx/query-b656927cd70b6667f3c72186ec04f0bf040da3af9e2eac3229264ec95b4755d8.json deleted file mode 100644 index c05edddc9f..0000000000 --- a/backend/.sqlx/query-b656927cd70b6667f3c72186ec04f0bf040da3af9e2eac3229264ec95b4755d8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE flow_workspace_runnables SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "b656927cd70b6667f3c72186ec04f0bf040da3af9e2eac3229264ec95b4755d8" -} diff --git a/backend/.sqlx/query-b7335ac24702c86fbb4ab95916a6aa1648082287b09122755df2462dc71ce831.json b/backend/.sqlx/query-b7335ac24702c86fbb4ab95916a6aa1648082287b09122755df2462dc71ce831.json new file mode 100644 index 0000000000..7f90d5ac1f --- /dev/null +++ b/backend/.sqlx/query-b7335ac24702c86fbb4ab95916a6aa1648082287b09122755df2462dc71ce831.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n WITH j AS (\n SELECT \n raw_flow->>'concurrency_key' as concurrency_key, \n raw_flow->>'concurrency_time_window_s' as concurrency_time_window_s,\n raw_flow->>'concurrency_limit' as concurrent_limit,\n runnable_path, \n runnable_id as version FROM v2_job\n WHERE id = $1\n )\n SELECT tag, j.concurrency_key, j.concurrency_time_window_s::int, j.concurrent_limit::int, j.version\n FROM flow, j\n WHERE path = j.runnable_path\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "concurrency_key", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "concurrency_time_window_s", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "concurrent_limit", + "type_info": "Int4" + }, + { + "ordinal": 4, + "name": "version", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true, + null, + null, + null, + true + ] + }, + "hash": "b7335ac24702c86fbb4ab95916a6aa1648082287b09122755df2462dc71ce831" +} diff --git a/backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json b/backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json new file mode 100644 index 0000000000..e6ecb2b7d6 --- /dev/null +++ b/backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json @@ -0,0 +1,107 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26,\n CASE WHEN $14::VARCHAR IS NOT NULL THEN 'schedule'::job_trigger_kind END,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $32, $33, $34, $35, $36, $37, $2) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 THEN now() END, $30, $31)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Varchar", + "Text", + "Text", + "Jsonb", + "Varchar", + "Uuid", + "Varchar", + "Varchar", + "Int8", + "Varchar", + "Jsonb", + { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + }, + "Varchar", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + }, + "Bool", + "Text", + "Varchar", + "Bool", + "Uuid", + "Int4", + "Int4", + "Int4", + "Varchar", + "Int4", + "Int2", + "Bool", + "Bool", + "Timestamptz", + "Varchar", + "Int2", + "Varchar", + "Varchar", + "Bool", + "Bool", + "JsonbArray", + "TextArray", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d" +} diff --git a/backend/.sqlx/query-ba117e89b5da7ef46a9e702a2f80c624cd1b00bcab9855edb9284d8afec46ff0.json b/backend/.sqlx/query-ba117e89b5da7ef46a9e702a2f80c624cd1b00bcab9855edb9284d8afec46ff0.json new file mode 100644 index 0000000000..773dc5d24d --- /dev/null +++ b/backend/.sqlx/query-ba117e89b5da7ef46a9e702a2f80c624cd1b00bcab9855edb9284d8afec46ff0.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET ai_config = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "ba117e89b5da7ef46a9e702a2f80c624cd1b00bcab9855edb9284d8afec46ff0" +} diff --git a/backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json b/backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json deleted file mode 100644 index e772354ea8..0000000000 --- a/backend/.sqlx/query-baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-bbc2c0769bf833f4e95bfc7908897ecbfe662efb13ffdd8ee3f1930bff4cd9c4.json b/backend/.sqlx/query-bbc2c0769bf833f4e95bfc7908897ecbfe662efb13ffdd8ee3f1930bff4cd9c4.json new file mode 100644 index 0000000000..e8f8de3ef8 --- /dev/null +++ b/backend/.sqlx/query-bbc2c0769bf833f4e95bfc7908897ecbfe662efb13ffdd8ee3f1930bff4cd9c4.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'],\n $3\n )\n WHERE id = $4", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "bbc2c0769bf833f4e95bfc7908897ecbfe662efb13ffdd8ee3f1930bff4cd9c4" +} diff --git a/backend/.sqlx/query-e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a.json b/backend/.sqlx/query-bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36.json similarity index 60% rename from backend/.sqlx/query-e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a.json rename to backend/.sqlx/query-bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36.json index 9715083ee6..2ccfd91e32 100644 --- a/backend/.sqlx/query-e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a.json +++ b/backend/.sqlx/query-bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36.json @@ -1,80 +1,95 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ", + "query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ", "describe": { "columns": [ { "ordinal": 0, + "name": "aws_auth_resource_type: _", + "type_info": { + "Custom": { + "name": "aws_auth_resource_type", + "kind": { + "Enum": [ + "oidc", + "credentials" + ] + } + } + } + }, + { + "ordinal": 1, "name": "queue_url", "type_info": "Varchar" }, { - "ordinal": 1, + "ordinal": 2, "name": "aws_resource_path", "type_info": "Varchar" }, { - "ordinal": 2, + "ordinal": 3, "name": "message_attributes", "type_info": "TextArray" }, { - "ordinal": 3, + "ordinal": 4, "name": "workspace_id", "type_info": "Varchar" }, { - "ordinal": 4, + "ordinal": 5, "name": "path", "type_info": "Varchar" }, { - "ordinal": 5, + "ordinal": 6, "name": "script_path", "type_info": "Varchar" }, { - "ordinal": 6, + "ordinal": 7, "name": "is_flow", "type_info": "Bool" }, { - "ordinal": 7, + "ordinal": 8, "name": "edited_by", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 9, "name": "email", "type_info": "Varchar" }, { - "ordinal": 9, + "ordinal": 10, "name": "edited_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "server_id", "type_info": "Varchar" }, { - "ordinal": 11, + "ordinal": 12, "name": "last_server_ping", "type_info": "Timestamptz" }, { - "ordinal": 12, + "ordinal": 13, "name": "extra_perms", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "error", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 15, "name": "enabled", "type_info": "Bool" } @@ -83,6 +98,7 @@ "Left": [] }, "nullable": [ + false, false, false, true, @@ -100,5 +116,5 @@ false ] }, - "hash": "e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a" + "hash": "bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36" } diff --git a/backend/.sqlx/query-bcfe877749ff7b944fef302ea37481b170c221349a793c9608c4ccd52ba8a5af.json b/backend/.sqlx/query-bcfe877749ff7b944fef302ea37481b170c221349a793c9608c4ccd52ba8a5af.json new file mode 100644 index 0000000000..4460ab7b36 --- /dev/null +++ b/backend/.sqlx/query-bcfe877749ff7b944fef302ea37481b170c221349a793c9608c4ccd52ba8a5af.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['approval_conditions'], $1)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "bcfe877749ff7b944fef302ea37481b170c221349a793c9608c4ccd52ba8a5af" +} diff --git a/backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json b/backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json new file mode 100644 index 0000000000..d75ffd0339 --- /dev/null +++ b/backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT scheduled_for FROM v2_job_queue INNER JOIN concurrency_key ON concurrency_key.job_id = v2_job_queue.id\n WHERE key = $1 AND running = false AND canceled_by IS NULL AND scheduled_for >= $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "scheduled_for", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Timestamptz" + ] + }, + "nullable": [ + false + ] + }, + "hash": "bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a" +} diff --git a/backend/.sqlx/query-c00bae0d8c9bee37cbad4de4cb02c80d00f52a3fc32bf32271ebc90f7837abda.json b/backend/.sqlx/query-c00bae0d8c9bee37cbad4de4cb02c80d00f52a3fc32bf32271ebc90f7837abda.json deleted file mode 100644 index 74aa6c4ffb..0000000000 --- a/backend/.sqlx/query-c00bae0d8c9bee37cbad4de4cb02c80d00f52a3fc32bf32271ebc90f7837abda.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_queue\n SET canceled_by = 'timeout'\n , canceled_reason = $1\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "c00bae0d8c9bee37cbad4de4cb02c80d00f52a3fc32bf32271ebc90f7837abda" -} diff --git a/backend/.sqlx/query-c0e6dbce7a401b06e1bf45155c3f81572818177a6024e743b12f558b46edf74c.json b/backend/.sqlx/query-c0e6dbce7a401b06e1bf45155c3f81572818177a6024e743b12f558b46edf74c.json new file mode 100644 index 0000000000..e65e3cb60e --- /dev/null +++ b/backend/.sqlx/query-c0e6dbce7a401b06e1bf45155c3f81572818177a6024e743b12f558b46edf74c.json @@ -0,0 +1,50 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n path,\n is_flow,\n workspace_id,\n owner,\n email,\n trigger_config as \"trigger_config!: _\"\n FROM\n capture_config\n WHERE\n trigger_kind = 'gcp' AND\n last_client_ping > NOW() - INTERVAL '10 seconds' AND\n trigger_config IS NOT NULL AND\n trigger_config->>'delivery_type' IS DISTINCT FROM 'push' AND\n (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "owner", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "trigger_config!: _", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + true + ] + }, + "hash": "c0e6dbce7a401b06e1bf45155c3f81572818177a6024e743b12f558b46edf74c" +} diff --git a/backend/.sqlx/query-dc165e2d3e6cfc52d92b48500b5ca7dd94b46263c58163071c0ded1c54535727.json b/backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json similarity index 56% rename from backend/.sqlx/query-dc165e2d3e6cfc52d92b48500b5ca7dd94b46263c58163071c0ded1c54535727.json rename to backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json index 3a607fbf31..ca8fd862d2 100644 --- a/backend/.sqlx/query-dc165e2d3e6cfc52d92b48500b5ca7dd94b46263c58163071c0ded1c54535727.json +++ b/backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n ai_models,\n code_completion_model,\n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name,\n mute_critical_alerts,\n color,\n operator_settings\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", + "query": "SELECT\n -- slack_team_id,\n -- slack_name,\n -- slack_command_script,\n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\",\n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\",\n webhook,\n deploy_to,\n error_handler,\n ai_config,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n git_sync,\n default_app,\n default_scripts,\n workspace.name,\n mute_critical_alerts,\n color,\n operator_settings\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", "describe": { "columns": [ { @@ -35,66 +35,56 @@ }, { "ordinal": 6, - "name": "ai_resource", + "name": "ai_config", "type_info": "Jsonb" }, { "ordinal": 7, - "name": "ai_models", - "type_info": "VarcharArray" - }, - { - "ordinal": 8, - "name": "code_completion_model", - "type_info": "Varchar" - }, - { - "ordinal": 9, "name": "error_handler_extra_args", "type_info": "Json" }, { - "ordinal": 10, + "ordinal": 8, "name": "error_handler_muted_on_cancel", "type_info": "Bool" }, { - "ordinal": 11, + "ordinal": 9, "name": "large_file_storage", "type_info": "Jsonb" }, { - "ordinal": 12, + "ordinal": 10, "name": "git_sync", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 11, "name": "default_app", "type_info": "Varchar" }, { - "ordinal": 14, + "ordinal": 12, "name": "default_scripts", "type_info": "Jsonb" }, { - "ordinal": 15, + "ordinal": 13, "name": "name", "type_info": "Varchar" }, { - "ordinal": 16, + "ordinal": 14, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 17, + "ordinal": 15, "name": "color", "type_info": "Varchar" }, { - "ordinal": 18, + "ordinal": 16, "name": "operator_settings", "type_info": "Jsonb" } @@ -112,8 +102,6 @@ true, true, true, - false, - true, true, false, true, @@ -126,5 +114,5 @@ true ] }, - "hash": "dc165e2d3e6cfc52d92b48500b5ca7dd94b46263c58163071c0ded1c54535727" + "hash": "c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a" } diff --git a/backend/.sqlx/query-c14cc34a5350865b8a5b57205b6099e8f0ea697e279f6a72484ab031b7e1e952.json b/backend/.sqlx/query-c14cc34a5350865b8a5b57205b6099e8f0ea697e279f6a72484ab031b7e1e952.json new file mode 100644 index 0000000000..828dc6f04a --- /dev/null +++ b/backend/.sqlx/query-c14cc34a5350865b8a5b57205b6099e8f0ea697e279f6a72484ab031b7e1e952.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_runnable_dependencies SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c14cc34a5350865b8a5b57205b6099e8f0ea697e279f6a72484ab031b7e1e952" +} diff --git a/backend/.sqlx/query-c19a60a9dc3f95af218baf40c62f14572ac204cfe377166aa1d91cf58f731f50.json b/backend/.sqlx/query-c19a60a9dc3f95af218baf40c62f14572ac204cfe377166aa1d91cf58f731f50.json new file mode 100644 index 0000000000..65cc991931 --- /dev/null +++ b/backend/.sqlx/query-c19a60a9dc3f95af218baf40c62f14572ac204cfe377166aa1d91cf58f731f50.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE \n FROM \n gcp_trigger \n WHERE \n workspace_id = $1 AND \n path = $2\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c19a60a9dc3f95af218baf40c62f14572ac204cfe377166aa1d91cf58f731f50" +} diff --git a/backend/.sqlx/query-c202f6fbae6a727f88f3ac692985c70e6ebc68e4a16d02e4e36b79f3cfb1c661.json b/backend/.sqlx/query-c202f6fbae6a727f88f3ac692985c70e6ebc68e4a16d02e4e36b79f3cfb1c661.json deleted file mode 100644 index e1094f38ef..0000000000 --- a/backend/.sqlx/query-c202f6fbae6a727f88f3ac692985c70e6ebc68e4a16d02e4e36b79f3cfb1c661.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH suspend AS (\n UPDATE v2_job_queue SET suspend = $2, suspend_until = now() + $3\n WHERE id = $4\n RETURNING id\n ) UPDATE v2_job_status SET flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', flow_status->>'step'::TEXT],\n $1\n ) WHERE id = (SELECT id FROM suspend)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Int4", - "Interval", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "c202f6fbae6a727f88f3ac692985c70e6ebc68e4a16d02e4e36b79f3cfb1c661" -} diff --git a/backend/.sqlx/query-c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac.json b/backend/.sqlx/query-c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac.json new file mode 100644 index 0000000000..eec208dbbb --- /dev/null +++ b/backend/.sqlx/query-c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac.json @@ -0,0 +1,144 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "gcp_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "subscription_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "topic_id", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "delivery_type: _", + "type_info": { + "Custom": { + "name": "delivery_mode", + "kind": { + "Enum": [ + "push", + "pull" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "delivery_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "subscription_mode: _", + "type_info": { + "Custom": { + "name": "gcp_subscription_mode", + "kind": { + "Enum": [ + "create_update", + "existing" + ] + } + } + } + }, + { + "ordinal": 7, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 13, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + true, + false + ] + }, + "hash": "c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac" +} diff --git a/backend/.sqlx/query-c353aa9abe673749f0836fd16894d91ba89aab2318d85621ad2868017adfb48a.json b/backend/.sqlx/query-c353aa9abe673749f0836fd16894d91ba89aab2318d85621ad2868017adfb48a.json new file mode 100644 index 0000000000..5a67ebda84 --- /dev/null +++ b/backend/.sqlx/query-c353aa9abe673749f0836fd16894d91ba89aab2318d85621ad2868017adfb48a.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n capture_config\n SET \n path = $1\n WHERE \n path = $2 \n AND workspace_id = $3 \n AND is_flow = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "c353aa9abe673749f0836fd16894d91ba89aab2318d85621ad2868017adfb48a" +} diff --git a/backend/.sqlx/query-c35f44f91b08fa57e29a2b4a685706f62e700695810f23108e975dfcd1fee7a3.json b/backend/.sqlx/query-c35f44f91b08fa57e29a2b4a685706f62e700695810f23108e975dfcd1fee7a3.json deleted file mode 100644 index 77e89c52a8..0000000000 --- a/backend/.sqlx/query-c35f44f91b08fa57e29a2b4a685706f62e700695810f23108e975dfcd1fee7a3.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow_workspace_runnables (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Int8", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "c35f44f91b08fa57e29a2b4a685706f62e700695810f23108e975dfcd1fee7a3" -} diff --git a/backend/.sqlx/query-c51f9ad5133c46fd7c499b8339dbbf3f3059bbb85de07ee3b4b4cea971984a52.json b/backend/.sqlx/query-c51f9ad5133c46fd7c499b8339dbbf3f3059bbb85de07ee3b4b4cea971984a52.json new file mode 100644 index 0000000000..bbbd318787 --- /dev/null +++ b/backend/.sqlx/query-c51f9ad5133c46fd7c499b8339dbbf3f3059bbb85de07ee3b4b4cea971984a52.json @@ -0,0 +1,132 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n mqtt_resource_path,\n subscribe_topics as \"subscribe_topics: _\",\n v3_config as \"v3_config: _\",\n v5_config as \"v5_config: _\",\n client_version AS \"client_version: _\",\n client_id,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n mqtt_trigger\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "mqtt_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "subscribe_topics: _", + "type_info": "JsonbArray" + }, + { + "ordinal": 2, + "name": "v3_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 3, + "name": "v5_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "client_version: _", + "type_info": { + "Custom": { + "name": "mqtt_client_version", + "kind": { + "Enum": [ + "v3", + "v5" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "client_id", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 13, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + true, + true, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + true, + false + ] + }, + "hash": "c51f9ad5133c46fd7c499b8339dbbf3f3059bbb85de07ee3b4b4cea971984a52" +} diff --git a/backend/.sqlx/query-f367a1c8f80dd414dcbcd949374eeb5770796f00b5b3d547163bcfdaed65d8ae.json b/backend/.sqlx/query-c53e1c7133c8ae187656eef5999509fae17fb0ba43e084327accbb5b24c3dfbd.json similarity index 53% rename from backend/.sqlx/query-f367a1c8f80dd414dcbcd949374eeb5770796f00b5b3d547163bcfdaed65d8ae.json rename to backend/.sqlx/query-c53e1c7133c8ae187656eef5999509fae17fb0ba43e084327accbb5b24c3dfbd.json index 776443782e..e343b8b863 100644 --- a/backend/.sqlx/query-f367a1c8f80dd414dcbcd949374eeb5770796f00b5b3d547163bcfdaed65d8ae.json +++ b/backend/.sqlx/query-c53e1c7133c8ae187656eef5999509fae17fb0ba43e084327accbb5b24c3dfbd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO outstanding_wait_time(job_id, self_wait_time_ms) VALUES ($1, $2)\n ON CONFLICT (job_id) DO UPDATE SET self_wait_time_ms = EXCLUDED.self_wait_time_ms", + "query": "INSERT INTO outstanding_wait_time(job_id, self_wait_time_ms) VALUES ($1, $2)\n ON CONFLICT (job_id) DO UPDATE SET self_wait_time_ms = EXCLUDED.self_wait_time_ms", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "f367a1c8f80dd414dcbcd949374eeb5770796f00b5b3d547163bcfdaed65d8ae" + "hash": "c53e1c7133c8ae187656eef5999509fae17fb0ba43e084327accbb5b24c3dfbd" } diff --git a/backend/.sqlx/query-c71e12ec9d0054dd5605a4ea2ef77fa54ce686da9c93790420a91a0735589ac7.json b/backend/.sqlx/query-c71e12ec9d0054dd5605a4ea2ef77fa54ce686da9c93790420a91a0735589ac7.json deleted file mode 100644 index ecc62b1682..0000000000 --- a/backend/.sqlx/query-c71e12ec9d0054dd5605a4ea2ef77fa54ce686da9c93790420a91a0735589ac7.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(JSONB_SET(flow_status, ARRAY['retry'], $1), ARRAY['modules', $3::TEXT, 'failed_retries'], $4)\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Jsonb", - "Uuid", - "Text", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "c71e12ec9d0054dd5605a4ea2ef77fa54ce686da9c93790420a91a0735589ac7" -} diff --git a/backend/.sqlx/query-c794ff5e14429a1bc56f5e70cfd10e2160d1b7e123c7ab226dc77d1789f164f9.json b/backend/.sqlx/query-c794ff5e14429a1bc56f5e70cfd10e2160d1b7e123c7ab226dc77d1789f164f9.json deleted file mode 100644 index c4810efe68..0000000000 --- a/backend/.sqlx/query-c794ff5e14429a1bc56f5e70cfd10e2160d1b7e123c7ab226dc77d1789f164f9.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "dedicated_worker", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "has_preprocessor", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "on_behalf_of_email", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "edited_by", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - true, - true, - null, - true, - false - ] - }, - "hash": "c794ff5e14429a1bc56f5e70cfd10e2160d1b7e123c7ab226dc77d1789f164f9" -} diff --git a/backend/.sqlx/query-c925264b7b0fd44ea7ab01c9af1514b9a9f2200e5a5db0a741697b28cd8b505f.json b/backend/.sqlx/query-c925264b7b0fd44ea7ab01c9af1514b9a9f2200e5a5db0a741697b28cd8b505f.json new file mode 100644 index 0000000000..37025d99d8 --- /dev/null +++ b/backend/.sqlx/query-c925264b7b0fd44ea7ab01c9af1514b9a9f2200e5a5db0a741697b28cd8b505f.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT account FROM variable WHERE path = $1 AND workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "account", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "c925264b7b0fd44ea7ab01c9af1514b9a9f2200e5a5db0a741697b28cd8b505f" +} diff --git a/backend/.sqlx/query-c9c040ec228a8fe4fda08439420141bee63339d4e3d5e2d68aabb12009f691c6.json b/backend/.sqlx/query-c9c040ec228a8fe4fda08439420141bee63339d4e3d5e2d68aabb12009f691c6.json new file mode 100644 index 0000000000..c03a00eec3 --- /dev/null +++ b/backend/.sqlx/query-c9c040ec228a8fe4fda08439420141bee63339d4e3d5e2d68aabb12009f691c6.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO agent_token_blacklist (token, expires_at, blacklisted_by) \n VALUES ($1, $2, $3) \n ON CONFLICT (token) DO UPDATE SET \n expires_at = EXCLUDED.expires_at,\n blacklisted_at = NOW(),\n blacklisted_by = EXCLUDED.blacklisted_by", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Timestamp", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "c9c040ec228a8fe4fda08439420141bee63339d4e3d5e2d68aabb12009f691c6" +} diff --git a/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json b/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json new file mode 100644 index 0000000000..6d44fb2840 --- /dev/null +++ b/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8" +} diff --git a/backend/.sqlx/query-cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c.json b/backend/.sqlx/query-cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c.json new file mode 100644 index 0000000000..8ccf623719 --- /dev/null +++ b/backend/.sqlx/query-cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "authors", + "type_info": "VarcharArray" + }, + { + "ordinal": 1, + "name": "operators", + "type_info": "VarcharArray" + }, + { + "ordinal": 2, + "name": "author_count", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "operator_count", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null, + null, + null, + null + ] + }, + "hash": "cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c" +} diff --git a/backend/.sqlx/query-29fbc3a8c35845a997cd548ed417b9cc3c82d815d99b3d435adcfbb5a9246124.json b/backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json similarity index 52% rename from backend/.sqlx/query-29fbc3a8c35845a997cd548ed417b9cc3c82d815d99b3d435adcfbb5a9246124.json rename to backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json index 72f3f1f469..f9d5456b77 100644 --- a/backend/.sqlx/query-29fbc3a8c35845a997cd548ed417b9cc3c82d815d99b3d435adcfbb5a9246124.json +++ b/backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json @@ -1,12 +1,12 @@ { "db_name": "PostgreSQL", - "query": "SELECT set_config('session.folders_read', $1, true)", + "query": "SELECT COUNT(*) FROM script WHERE workspace_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "set_config", - "type_info": "Text" + "name": "count", + "type_info": "Int8" } ], "parameters": { @@ -18,5 +18,5 @@ null ] }, - "hash": "29fbc3a8c35845a997cd548ed417b9cc3c82d815d99b3d435adcfbb5a9246124" + "hash": "cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279" } diff --git a/backend/.sqlx/query-cbb93da0b7719a27d2ae1ec0f653322cae965dc5d8ebc98f69d5922fa1192561.json b/backend/.sqlx/query-cbb93da0b7719a27d2ae1ec0f653322cae965dc5d8ebc98f69d5922fa1192561.json new file mode 100644 index 0000000000..684e857290 --- /dev/null +++ b/backend/.sqlx/query-cbb93da0b7719a27d2ae1ec0f653322cae965dc5d8ebc98f69d5922fa1192561.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT COALESCE(\n (SELECT COUNT(*) FROM jsonb_object_keys(job_uuids)),\n 0\n )\n FROM concurrency_counter \n WHERE concurrency_id = $1\n FOR UPDATE", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "cbb93da0b7719a27d2ae1ec0f653322cae965dc5d8ebc98f69d5922fa1192561" +} diff --git a/backend/.sqlx/query-ccb7aea162fa8781675d547fb82f2996dede4de49c5bd9cca2928209dc40b8f1.json b/backend/.sqlx/query-ccb7aea162fa8781675d547fb82f2996dede4de49c5bd9cca2928209dc40b8f1.json deleted file mode 100644 index f4bc3f2174..0000000000 --- a/backend/.sqlx/query-ccb7aea162fa8781675d547fb82f2996dede4de49c5bd9cca2928209dc40b8f1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE http_trigger \n SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, static_asset_config = $7, edited_by = $8, email = $9, is_async = $10, requires_auth = $11, edited_at = now(), is_static_website = $12\n WHERE workspace_id = $13 AND path = $14", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Bool", - { - "Custom": { - "name": "http_method", - "kind": { - "Enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] - } - } - }, - "Jsonb", - "Varchar", - "Varchar", - "Bool", - "Bool", - "Bool", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "ccb7aea162fa8781675d547fb82f2996dede4de49c5bd9cca2928209dc40b8f1" -} diff --git a/backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json b/backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json deleted file mode 100644 index f5d905a157..0000000000 --- a/backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "authors", - "type_info": "VarcharArray" - }, - { - "ordinal": 1, - "name": "operators", - "type_info": "VarcharArray" - }, - { - "ordinal": 2, - "name": "author_count", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "operator_count", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null, - null, - null, - null - ] - }, - "hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c" -} diff --git a/backend/.sqlx/query-cd79f4dc6a426f1c4c19c2a86dc877a6d5bc5771b27d9e208a219a63add200f3.json b/backend/.sqlx/query-cd79f4dc6a426f1c4c19c2a86dc877a6d5bc5771b27d9e208a219a63add200f3.json new file mode 100644 index 0000000000..279ff511bd --- /dev/null +++ b/backend/.sqlx/query-cd79f4dc6a426f1c4c19c2a86dc877a6d5bc5771b27d9e208a219a63add200f3.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['failure_module'], $1),\n ARRAY['step'],\n $2\n )\n WHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Jsonb", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "cd79f4dc6a426f1c4c19c2a86dc877a6d5bc5771b27d9e208a219a63add200f3" +} diff --git a/backend/.sqlx/query-cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec.json b/backend/.sqlx/query-cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec.json new file mode 100644 index 0000000000..4e17af3ce2 --- /dev/null +++ b/backend/.sqlx/query-cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hash", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "deployment_msg", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec" +} diff --git a/backend/.sqlx/query-d0bd4f43cb1feabe7ee9e017a0d6af22d25369a33247cec6e9f2d1a5eb851412.json b/backend/.sqlx/query-d0bd4f43cb1feabe7ee9e017a0d6af22d25369a33247cec6e9f2d1a5eb851412.json new file mode 100644 index 0000000000..4e3ee705a3 --- /dev/null +++ b/backend/.sqlx/query-d0bd4f43cb1feabe7ee9e017a0d6af22d25369a33247cec6e9f2d1a5eb851412.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET git_app_installations = (\n SELECT jsonb_agg(updated)\n FROM (\n -- For each element, if the account_id matches, replace it\n SELECT\n CASE\n WHEN elem->>'account_id' = ($1::jsonb)->>'account_id' THEN $1::jsonb\n ELSE elem\n END AS updated\n FROM jsonb_array_elements(git_app_installations) AS elem\n UNION ALL\n -- Append new installation if no element with the same account_id exists\n SELECT $1::jsonb\n WHERE NOT EXISTS (\n SELECT 1\n FROM jsonb_array_elements(git_app_installations) AS elem\n WHERE elem->>'account_id' = ($1::jsonb)->>'account_id'\n )\n ) sub\n )\n WHERE workspace_id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d0bd4f43cb1feabe7ee9e017a0d6af22d25369a33247cec6e9f2d1a5eb851412" +} diff --git a/backend/.sqlx/query-bc00efe12901bc7b49c7892d2d9da675e0eaf5e6cc013149740bc778987a1006.json b/backend/.sqlx/query-d15f02f090b8d1a7e816fe11b2e0867540ab6bb02ac6bf82decc220dce0ab048.json similarity index 83% rename from backend/.sqlx/query-bc00efe12901bc7b49c7892d2d9da675e0eaf5e6cc013149740bc778987a1006.json rename to backend/.sqlx/query-d15f02f090b8d1a7e816fe11b2e0867540ab6bb02ac6bf82decc220dce0ab048.json index 67611f00dc..3a23232dcc 100644 --- a/backend/.sqlx/query-bc00efe12901bc7b49c7892d2d9da675e0eaf5e6cc013149740bc778987a1006.json +++ b/backend/.sqlx/query-d15f02f090b8d1a7e816fe11b2e0867540ab6bb02ac6bf82decc220dce0ab048.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)", + "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33)", "describe": { "columns": [], "parameters": { @@ -40,7 +40,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } @@ -77,10 +80,11 @@ "Bool", "Varchar", "Bool", - "Text" + "Text", + "Bool" ] }, "nullable": [] }, - "hash": "bc00efe12901bc7b49c7892d2d9da675e0eaf5e6cc013149740bc778987a1006" + "hash": "d15f02f090b8d1a7e816fe11b2e0867540ab6bb02ac6bf82decc220dce0ab048" } diff --git a/backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json b/backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json new file mode 100644 index 0000000000..4a0592701c --- /dev/null +++ b/backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT args as \"args: sqlx::types::Json>\"\n FROM v2_job\n WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "args: sqlx::types::Json>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141" +} diff --git a/backend/.sqlx/query-d1fdfabd4f3bbe93cf9667e8e76bd8f537e68fabceb094fe535693fbd146eff2.json b/backend/.sqlx/query-d1fdfabd4f3bbe93cf9667e8e76bd8f537e68fabceb094fe535693fbd146eff2.json new file mode 100644 index 0000000000..5ebf571855 --- /dev/null +++ b/backend/.sqlx/query-d1fdfabd4f3bbe93cf9667e8e76bd8f537e68fabceb094fe535693fbd146eff2.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')\n ) FROM v2_job jb\n WHERE jb.id = $1 AND jb.workspace_id = $2\n GROUP BY jb.kind, jb.runnable_path", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "d1fdfabd4f3bbe93cf9667e8e76bd8f537e68fabceb094fe535693fbd146eff2" +} diff --git a/backend/.sqlx/query-d56722c25877222af9affd5da5bb83b28fa8cbb528a2cfc90684cb10a69e4375.json b/backend/.sqlx/query-d56722c25877222af9affd5da5bb83b28fa8cbb528a2cfc90684cb10a69e4375.json new file mode 100644 index 0000000000..a8256a8060 --- /dev/null +++ b/backend/.sqlx/query-d56722c25877222af9affd5da5bb83b28fa8cbb528a2cfc90684cb10a69e4375.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT token, expires_at, blacklisted_at, blacklisted_by \n FROM agent_token_blacklist \n WHERE expires_at > $1 \n ORDER BY blacklisted_at DESC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "token", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "expires_at", + "type_info": "Timestamp" + }, + { + "ordinal": 2, + "name": "blacklisted_at", + "type_info": "Timestamp" + }, + { + "ordinal": 3, + "name": "blacklisted_by", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Timestamp" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "d56722c25877222af9affd5da5bb83b28fa8cbb528a2cfc90684cb10a69e4375" +} diff --git a/backend/.sqlx/query-d6db1103fee4bad6831656d77a28254c9a832b4f660ec755f4fa14f6f7bb3353.json b/backend/.sqlx/query-d6db1103fee4bad6831656d77a28254c9a832b4f660ec755f4fa14f6f7bb3353.json new file mode 100644 index 0000000000..09f24968f3 --- /dev/null +++ b/backend/.sqlx/query-d6db1103fee4bad6831656d77a28254c9a832b4f660ec755f4fa14f6f7bb3353.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status SET\n flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'iterator', 'index'],\n ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n )\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "int4", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Uuid", + "Text", + "Jsonb" + ] + }, + "nullable": [ + null + ] + }, + "hash": "d6db1103fee4bad6831656d77a28254c9a832b4f660ec755f4fa14f6f7bb3353" +} diff --git a/backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json b/backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json similarity index 76% rename from backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json rename to backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json index 55039055c8..2e2a9ba027 100644 --- a/backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json +++ b/backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE \n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update", + "query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE\n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update", "describe": { "columns": [ { @@ -37,5 +37,5 @@ null ] }, - "hash": "abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5" + "hash": "daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134" } diff --git a/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json b/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json new file mode 100644 index 0000000000..57f859d416 --- /dev/null +++ b/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n path,\n summary,\n description\n FROM\n script\n WHERE\n path ~ ANY($1) AND\n workspace_id = $2 AND\n archived is FALSE\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "TextArray", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd" +} diff --git a/backend/.sqlx/query-dc58e5b4715601a93b3c01a2564a4420f232867a23cacb9a62b386f129a86a4b.json b/backend/.sqlx/query-dc58e5b4715601a93b3c01a2564a4420f232867a23cacb9a62b386f129a86a4b.json deleted file mode 100644 index d3b8f33ff0..0000000000 --- a/backend/.sqlx/query-dc58e5b4715601a93b3c01a2564a4420f232867a23cacb9a62b386f129a86a4b.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE flow_workspace_runnables SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "dc58e5b4715601a93b3c01a2564a4420f232867a23cacb9a62b386f129a86a4b" -} diff --git a/backend/.sqlx/query-dc9a906d6c6156a84fccf4e3a2a7c08d8ed4984409b669162f0a1fc1aa48e188.json b/backend/.sqlx/query-dc9a906d6c6156a84fccf4e3a2a7c08d8ed4984409b669162f0a1fc1aa48e188.json deleted file mode 100644 index 03b0c561e1..0000000000 --- a/backend/.sqlx/query-dc9a906d6c6156a84fccf4e3a2a7c08d8ed4984409b669162f0a1fc1aa48e188.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM http_trigger WHERE workspace_id = $1 AND path = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "dc9a906d6c6156a84fccf4e3a2a7c08d8ed4984409b669162f0a1fc1aa48e188" -} diff --git a/backend/.sqlx/query-dd06bdc09968add6a7c09f124f1f9b717990371d98e0784e96eb31cfdd17885a.json b/backend/.sqlx/query-dd06bdc09968add6a7c09f124f1f9b717990371d98e0784e96eb31cfdd17885a.json new file mode 100644 index 0000000000..0d2df00658 --- /dev/null +++ b/backend/.sqlx/query-dd06bdc09968add6a7c09f124f1f9b717990371d98e0784e96eb31cfdd17885a.json @@ -0,0 +1,56 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n http_trigger \n SET \n workspaced_route = $1,\n wrap_body = $2,\n raw_string = $3,\n authentication_resource_path = $4,\n script_path = $5, \n path = $6, \n is_flow = $7, \n http_method = $8, \n static_asset_config = $9, \n edited_by = $10, \n email = $11, \n is_async = $12, \n authentication_method = $13, \n edited_at = now(), \n is_static_website = $14\n WHERE \n workspace_id = $15 AND \n path = $16\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Bool", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Bool", + { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + }, + "Jsonb", + "Varchar", + "Varchar", + "Bool", + { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + }, + "Bool", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "dd06bdc09968add6a7c09f124f1f9b717990371d98e0784e96eb31cfdd17885a" +} diff --git a/backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json b/backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json deleted file mode 100644 index c2dfed73a2..0000000000 --- a/backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) \n SELECT worker_ids.worker FROM worker_ids \n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker \n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "worker", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "TextArray" - ] - }, - "nullable": [ - null - ] - }, - "hash": "ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927" -} diff --git a/backend/.sqlx/query-de2a213bc5a08d08bcd52ad630559cc0f16d7286c3f07c27d962258022f0e04d.json b/backend/.sqlx/query-de2a213bc5a08d08bcd52ad630559cc0f16d7286c3f07c27d962258022f0e04d.json new file mode 100644 index 0000000000..f3ef9ff937 --- /dev/null +++ b/backend/.sqlx/query-de2a213bc5a08d08bcd52ad630559cc0f16d7286c3f07c27d962258022f0e04d.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(JSONB_SET(flow_status, ARRAY['retry'], $1), ARRAY['modules', $3::TEXT, 'failed_retries'], $4)\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Uuid", + "Text", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "de2a213bc5a08d08bcd52ad630559cc0f16d7286c3f07c27d962258022f0e04d" +} diff --git a/backend/.sqlx/query-defd99dd2427cdc54bb662d1ba3a1aea7f410ef204ec3465f4fb6c9acd256c95.json b/backend/.sqlx/query-defd99dd2427cdc54bb662d1ba3a1aea7f410ef204ec3465f4fb6c9acd256c95.json deleted file mode 100644 index 6aab7bc049..0000000000 --- a/backend/.sqlx/query-defd99dd2427cdc54bb662d1ba3a1aea7f410ef204ec3465f4fb6c9acd256c95.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_queue SET\n suspend = $1,\n suspend_until = now() + interval '14 day',\n running = true\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "defd99dd2427cdc54bb662d1ba3a1aea7f410ef204ec3465f4fb6c9acd256c95" -} diff --git a/backend/.sqlx/query-df2a426658f0a36683cc2163fe70912d0221d191b8ab091601cb53cdd932ad58.json b/backend/.sqlx/query-df2a426658f0a36683cc2163fe70912d0221d191b8ab091601cb53cdd932ad58.json new file mode 100644 index 0000000000..f98dd01404 --- /dev/null +++ b/backend/.sqlx/query-df2a426658f0a36683cc2163fe70912d0221d191b8ab091601cb53cdd932ad58.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger\n SET \n last_server_ping = now(),\n error = $1\n WHERE\n workspace_id = $2\n AND path = $3\n AND server_id = $4 \n AND enabled IS TRUE\n RETURNING 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "df2a426658f0a36683cc2163fe70912d0221d191b8ab091601cb53cdd932ad58" +} diff --git a/backend/.sqlx/query-e04a8a9f1e9cc3bb5c990194585e08f4c248e1af1c6580bdf8f2735ae1388981.json b/backend/.sqlx/query-e04a8a9f1e9cc3bb5c990194585e08f4c248e1af1c6580bdf8f2735ae1388981.json new file mode 100644 index 0000000000..926e5c2474 --- /dev/null +++ b/backend/.sqlx/query-e04a8a9f1e9cc3bb5c990194585e08f4c248e1af1c6580bdf8f2735ae1388981.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM \n capture\n WHERE \n workspace_id = $1\n AND created_at <= (\n SELECT \n created_at\n FROM \n capture\n WHERE \n workspace_id = $1\n ORDER BY \n created_at DESC\n OFFSET $2\n LIMIT 1\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "e04a8a9f1e9cc3bb5c990194585e08f4c248e1af1c6580bdf8f2735ae1388981" +} diff --git a/backend/.sqlx/query-e07660e8d2a265cb6a83f3a2bb8e7e6330f09ab116e9837f6f16f8fdef938004.json b/backend/.sqlx/query-e07660e8d2a265cb6a83f3a2bb8e7e6330f09ab116e9837f6f16f8fdef938004.json new file mode 100644 index 0000000000..f03f5c06f6 --- /dev/null +++ b/backend/.sqlx/query-e07660e8d2a265cb6a83f3a2bb8e7e6330f09ab116e9837f6f16f8fdef938004.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH job_result AS (\n SELECT result \n FROM v2_job_completed \n WHERE id = $1\n ),\n updated_queue AS (\n UPDATE v2_job_queue\n SET running = false,\n tag = COALESCE($3, tag)\n WHERE id = $2\n )\n UPDATE v2_job \n SET \n tag = COALESCE($3, tag),\n concurrent_limit = COALESCE($4, concurrent_limit),\n concurrency_time_window_s = COALESCE($5, concurrency_time_window_s),\n args = COALESCE(\n CASE \n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object' \n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END, \n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Uuid", + "Varchar", + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "e07660e8d2a265cb6a83f3a2bb8e7e6330f09ab116e9837f6f16f8fdef938004" +} diff --git a/backend/.sqlx/query-e19fab3d594c8b9d38f9caa8f71b16ffcb2bc8d94a8240143a913ad125ad6eb8.json b/backend/.sqlx/query-e19fab3d594c8b9d38f9caa8f71b16ffcb2bc8d94a8240143a913ad125ad6eb8.json new file mode 100644 index 0000000000..f75e6eaa56 --- /dev/null +++ b/backend/.sqlx/query-e19fab3d594c8b9d38f9caa8f71b16ffcb2bc8d94a8240143a913ad125ad6eb8.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "e19fab3d594c8b9d38f9caa8f71b16ffcb2bc8d94a8240143a913ad125ad6eb8" +} diff --git a/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json b/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json deleted file mode 100644 index 1ff35d7227..0000000000 --- a/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c" -} diff --git a/backend/.sqlx/query-cc6e21bf16d7b92764aa2b261cee94f6daf6a1eedd8a68742a2f510f0452cc32.json b/backend/.sqlx/query-e3c8219420bb859de3f1c322978c5859c924cffa61074562ac6d33106d02d7d6.json similarity index 66% rename from backend/.sqlx/query-cc6e21bf16d7b92764aa2b261cee94f6daf6a1eedd8a68742a2f510f0452cc32.json rename to backend/.sqlx/query-e3c8219420bb859de3f1c322978c5859c924cffa61074562ac6d33106d02d7d6.json index 54ae52423d..8ee4e7890e 100644 --- a/backend/.sqlx/query-cc6e21bf16d7b92764aa2b261cee94f6daf6a1eedd8a68742a2f510f0452cc32.json +++ b/backend/.sqlx/query-e3c8219420bb859de3f1c322978c5859c924cffa61074562ac6d33106d02d7d6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT g_.workspace_id, name, summary, extra_perms, array_agg(u2g.usr) filter (where u2g.usr is not null) as members \n FROM usr u\n JOIN usr_to_group u2g ON u2g.usr = u.username AND u2g.workspace_id = u.workspace_id\n RIGHT JOIN group_ g_ ON g_.workspace_id = u.workspace_id AND g_.name = u2g.group_\n WHERE g_.workspace_id = $1 AND g_.name != 'all'\n GROUP BY g_.workspace_id, name, summary, extra_perms", + "query": "SELECT g_.workspace_id, name, summary, extra_perms, array_agg(u2g.usr) filter (where u2g.usr is not null) as members\n FROM usr u\n JOIN usr_to_group u2g ON u2g.usr = u.username AND u2g.workspace_id = u.workspace_id\n RIGHT JOIN group_ g_ ON g_.workspace_id = u.workspace_id AND g_.name = u2g.group_\n WHERE g_.workspace_id = $1 AND g_.name != 'all'\n GROUP BY g_.workspace_id, name, summary, extra_perms", "describe": { "columns": [ { @@ -42,5 +42,5 @@ null ] }, - "hash": "cc6e21bf16d7b92764aa2b261cee94f6daf6a1eedd8a68742a2f510f0452cc32" + "hash": "e3c8219420bb859de3f1c322978c5859c924cffa61074562ac6d33106d02d7d6" } diff --git a/backend/.sqlx/query-e3f545460bf317c3e2f34f4cbd12740141eb8b5ed07c090ae9356a970caeadd0.json b/backend/.sqlx/query-e3f545460bf317c3e2f34f4cbd12740141eb8b5ed07c090ae9356a970caeadd0.json new file mode 100644 index 0000000000..34df271cc8 --- /dev/null +++ b/backend/.sqlx/query-e3f545460bf317c3e2f34f4cbd12740141eb8b5ed07c090ae9356a970caeadd0.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH suspend AS (\n UPDATE v2_job_queue SET suspend = $2, suspend_until = now() + $3\n WHERE id = $4\n RETURNING id\n ) UPDATE v2_job_status SET flow_status = JSONB_SET(\n flow_status,\n ARRAY['modules', flow_status->>'step'::TEXT],\n $1\n ) WHERE id = (SELECT id FROM suspend)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Int4", + "Interval", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "e3f545460bf317c3e2f34f4cbd12740141eb8b5ed07c090ae9356a970caeadd0" +} diff --git a/backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json b/backend/.sqlx/query-e5f1e1e74daeabf410991e2484c1fb565f04539fc7eb141ebfaa957456016841.json similarity index 50% rename from backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json rename to backend/.sqlx/query-e5f1e1e74daeabf410991e2484c1fb565f04539fc7eb141ebfaa957456016841.json index 880e79a957..ddd216acc1 100644 --- a/backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json +++ b/backend/.sqlx/query-e5f1e1e74daeabf410991e2484c1fb565f04539fc7eb141ebfaa957456016841.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = true \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "query": "\n SELECT usage.usage FROM usage\n WHERE is_workspace = true\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8" + "hash": "e5f1e1e74daeabf410991e2484c1fb565f04539fc7eb141ebfaa957456016841" } diff --git a/backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json b/backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json new file mode 100644 index 0000000000..000329b19f --- /dev/null +++ b/backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE \n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "preprocessor_module: _", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "schema: _", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null, + true + ] + }, + "hash": "e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d" +} diff --git a/backend/.sqlx/query-07da723ce5c9ee2d7c236e8eabe254c783fc34b617c8a9a95a0eb0cda535dab5.json b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json similarity index 64% rename from backend/.sqlx/query-07da723ce5c9ee2d7c236e8eabe254c783fc34b617c8a9a95a0eb0cda535dab5.json rename to backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json index b905255754..8890104678 100644 --- a/backend/.sqlx/query-07da723ce5c9ee2d7c236e8eabe254c783fc34b617c8a9a95a0eb0cda535dab5.json +++ b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO capture (workspace_id, path, is_flow, trigger_kind, payload, trigger_extra, created_by)\n VALUES ($1, $2, $3, $4, $5, $6, $7)", + "query": "\n INSERT INTO \n capture (\n workspace_id, path, is_flow, trigger_kind, main_args, preprocessor_args, created_by\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n ", "describe": { "columns": [], "parameters": { @@ -21,7 +21,8 @@ "nats", "postgres", "sqs", - "mqtt" + "mqtt", + "gcp" ] } } @@ -33,5 +34,5 @@ }, "nullable": [] }, - "hash": "07da723ce5c9ee2d7c236e8eabe254c783fc34b617c8a9a95a0eb0cda535dab5" + "hash": "eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423" } diff --git a/backend/.sqlx/query-eb68469026be39048c5f42a80a2c538fbb54ad269ec81aea89e431a511245a1e.json b/backend/.sqlx/query-eb68469026be39048c5f42a80a2c538fbb54ad269ec81aea89e431a511245a1e.json deleted file mode 100644 index 60a98ba32a..0000000000 --- a/backend/.sqlx/query-eb68469026be39048c5f42a80a2c538fbb54ad269ec81aea89e431a511245a1e.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id AS \"id!\" FROM v2_job_queue INNER JOIN v2_job USING (id) WHERE parent_job = $1 AND v2_job.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id!", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "eb68469026be39048c5f42a80a2c538fbb54ad269ec81aea89e431a511245a1e" -} diff --git a/backend/.sqlx/query-ec7836df5f9056ec70015800b7f4feaeb1b671120f5f8c98fca8c89c6587fc35.json b/backend/.sqlx/query-ec7836df5f9056ec70015800b7f4feaeb1b671120f5f8c98fca8c89c6587fc35.json deleted file mode 100644 index 81093c5f12..0000000000 --- a/backend/.sqlx/query-ec7836df5f9056ec70015800b7f4feaeb1b671120f5f8c98fca8c89c6587fc35.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, on_behalf_of_email, created_by from script where hash = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "concurrency_key", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "concurrent_limit", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "concurrency_time_window_s", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "cache_ttl", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "language: ScriptLang", - "type_info": { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb" - ] - } - } - } - }, - { - "ordinal": 6, - "name": "dedicated_worker", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "priority", - "type_info": "Int2" - }, - { - "ordinal": 8, - "name": "delete_after_use", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "timeout", - "type_info": "Int4" - }, - { - "ordinal": 10, - "name": "on_behalf_of_email", - "type_info": "Text" - }, - { - "ordinal": 11, - "name": "created_by", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - }, - "nullable": [ - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - false - ] - }, - "hash": "ec7836df5f9056ec70015800b7f4feaeb1b671120f5f8c98fca8c89c6587fc35" -} diff --git a/backend/.sqlx/query-ec9980f80a8bfa4b09225035e8a1f78d7d61fbf83dda9b39bb22e0f9584d221b.json b/backend/.sqlx/query-ec9980f80a8bfa4b09225035e8a1f78d7d61fbf83dda9b39bb22e0f9584d221b.json new file mode 100644 index 0000000000..d87e680abe --- /dev/null +++ b/backend/.sqlx/query-ec9980f80a8bfa4b09225035e8a1f78d7d61fbf83dda9b39bb22e0f9584d221b.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS (\n SELECT 1 FROM v2_as_completed_job\n WHERE workspace_id = $2\n AND (job_kind = 'appscript' OR job_kind = 'preview')\n AND created_by = 'anonymous'\n AND started_at > now() - interval '3 hours'\n AND script_path LIKE $3 || '/%'\n AND result @> ('{\"s3\":\"' || $1 || '\"}')::jsonb\n )", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "ec9980f80a8bfa4b09225035e8a1f78d7d61fbf83dda9b39bb22e0f9584d221b" +} diff --git a/backend/.sqlx/query-ed99d4d088d0fd0c01f29803b12e99ae0a53d0b1feaa67737da409c51c1b6751.json b/backend/.sqlx/query-ed99d4d088d0fd0c01f29803b12e99ae0a53d0b1feaa67737da409c51c1b6751.json new file mode 100644 index 0000000000..a7ed7469d9 --- /dev/null +++ b/backend/.sqlx/query-ed99d4d088d0fd0c01f29803b12e99ae0a53d0b1feaa67737da409c51c1b6751.json @@ -0,0 +1,59 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO http_trigger (\n workspace_id, \n path, \n route_path, \n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path, \n summary,\n description,\n is_flow, \n is_async, \n authentication_method, \n http_method, \n static_asset_config, \n edited_by, \n email, \n edited_at, \n is_static_website\n ) \n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Bool", + "Bool", + "Varchar", + "Varchar", + "Text", + "Bool", + "Bool", + { + "Custom": { + "name": "authentication_method", + "kind": { + "Enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + } + } + }, + { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + }, + "Jsonb", + "Varchar", + "Varchar", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "ed99d4d088d0fd0c01f29803b12e99ae0a53d0b1feaa67737da409c51c1b6751" +} diff --git a/backend/.sqlx/query-f0fdeb7aea3e71099e7db0f4343bbd7ec86610ddc8589bf5b606fab0947c8b75.json b/backend/.sqlx/query-f0fdeb7aea3e71099e7db0f4343bbd7ec86610ddc8589bf5b606fab0947c8b75.json deleted file mode 100644 index 26111685ba..0000000000 --- a/backend/.sqlx/query-f0fdeb7aea3e71099e7db0f4343bbd7ec86610ddc8589bf5b606fab0947c8b75.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n v2_as_queue.job_kind AS \"job_kind!: JobKind\",\n v2_as_queue.script_hash AS \"script_hash: ScriptHash\",\n v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json>\",\n v2_as_completed_job.parent_job AS \"parent_job: Uuid\",\n v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\",\n v2_as_completed_job.created_by AS \"created_by!\",\n v2_as_queue.script_path,\n v2_as_queue.args AS \"args: sqlx::types::Json>\"\n FROM v2_as_queue\n JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id\n WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2\n LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "job_kind!: JobKind", - "type_info": { - "Custom": { - "name": "job_kind", - "kind": { - "Enum": [ - "script", - "preview", - "flow", - "dependencies", - "flowpreview", - "script_hub", - "identity", - "flowdependencies", - "http", - "graphql", - "postgresql", - "noop", - "appdependencies", - "deploymentcallback", - "singlescriptflow", - "flowscript", - "flownode", - "appscript" - ] - } - } - } - }, - { - "ordinal": 1, - "name": "script_hash: ScriptHash", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "raw_flow: sqlx::types::Json>", - "type_info": "Jsonb" - }, - { - "ordinal": 3, - "name": "parent_job: Uuid", - "type_info": "Uuid" - }, - { - "ordinal": 4, - "name": "created_at!: chrono::NaiveDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "created_by!", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "args: sqlx::types::Json>", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - true, - true, - true, - true, - true, - true, - true, - true - ] - }, - "hash": "f0fdeb7aea3e71099e7db0f4343bbd7ec86610ddc8589bf5b606fab0947c8b75" -} diff --git a/backend/.sqlx/query-f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632.json b/backend/.sqlx/query-f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632.json new file mode 100644 index 0000000000..b58fad894b --- /dev/null +++ b/backend/.sqlx/query-f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2\n AND f.id = j.id AND q.id = j.id\n AND (f.flow_status->'step')::int = 0", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632" +} diff --git a/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json b/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json new file mode 100644 index 0000000000..b400be3485 --- /dev/null +++ b/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json @@ -0,0 +1,247 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n cj.id AS \"id!\",\n cj.workspace_id AS \"workspace_id!\",\n cj.parent_job,\n cj.created_by AS \"created_by!\",\n cj.duration_ms AS \"duration_ms!\",\n cj.success AS \"success!\",\n cj.script_hash AS \"script_hash!: Option\",\n cj.script_path,\n cj.args AS \"args: sqlx::types::Json>>\",\n cj.result AS \"result: sqlx::types::Json>\",\n cj.deleted AS \"deleted!\",\n cj.canceled AS \"canceled!\",\n cj.canceled_by,\n cj.canceled_reason,\n cj.job_kind AS \"job_kind!: JobKind\",\n cj.schedule_path,\n cj.permissioned_as AS \"permissioned_as!\",\n cj.is_flow_step AS \"is_flow_step!\",\n cj.language AS \"language: ScriptLang\",\n cj.is_skipped AS \"is_skipped!\",\n cj.email AS \"email!\",\n cj.visible_to_owner AS \"visible_to_owner!\",\n cj.mem_peak,\n cj.tag AS \"tag!\",\n cj.created_at AS \"created_at!\",\n cj.started_at,\n job_logs.logs,\n job_logs.log_offset AS \"log_offset?\",\n job_logs.log_file_index\n\n FROM v2_as_completed_job AS cj\n LEFT JOIN job_logs ON cj.id = job_logs.job_id\n WHERE cj.created_at < $1\n ORDER BY cj.created_at ASC LIMIT $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id!", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "workspace_id!", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "parent_job", + "type_info": "Uuid" + }, + { + "ordinal": 3, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "duration_ms!", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "success!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "script_hash!: Option", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "args: sqlx::types::Json>>", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "deleted!", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "canceled!", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "canceled_by", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "canceled_reason", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "job_kind!: JobKind", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 15, + "name": "schedule_path", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "permissioned_as!", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "is_flow_step!", + "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb" + ] + } + } + } + }, + { + "ordinal": 19, + "name": "is_skipped!", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "email!", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "visible_to_owner!", + "type_info": "Bool" + }, + { + "ordinal": 22, + "name": "mem_peak", + "type_info": "Int4" + }, + { + "ordinal": 23, + "name": "tag!", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "created_at!", + "type_info": "Timestamptz" + }, + { + "ordinal": 25, + "name": "started_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 26, + "name": "logs", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "log_offset?", + "type_info": "Int4" + }, + { + "ordinal": 28, + "name": "log_file_index", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Timestamptz", + "Int8" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true + ] + }, + "hash": "f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7" +} diff --git a/backend/.sqlx/query-f3c78cb67379f9407f1f32ce3387184d7f4fcb04c07a8e2f07c5effc10f8fd5c.json b/backend/.sqlx/query-f3c78cb67379f9407f1f32ce3387184d7f4fcb04c07a8e2f07c5effc10f8fd5c.json deleted file mode 100644 index 0cf878b15c..0000000000 --- a/backend/.sqlx/query-f3c78cb67379f9407f1f32ce3387184d7f4fcb04c07a8e2f07c5effc10f8fd5c.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2)\n WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "f3c78cb67379f9407f1f32ce3387184d7f4fcb04c07a8e2f07c5effc10f8fd5c" -} diff --git a/backend/.sqlx/query-f44f1a794ab217be05a9feb21e57b3409d9166a028701981cfc280aaab3c4436.json b/backend/.sqlx/query-f44f1a794ab217be05a9feb21e57b3409d9166a028701981cfc280aaab3c4436.json deleted file mode 100644 index 79ef441a8a..0000000000 --- a/backend/.sqlx/query-f44f1a794ab217be05a9feb21e57b3409d9166a028701981cfc280aaab3c4436.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT tag, dedicated_worker, on_behalf_of_email, edited_by from flow WHERE path = $1 and workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "dedicated_worker", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "on_behalf_of_email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "edited_by", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - true, - true, - true, - false - ] - }, - "hash": "f44f1a794ab217be05a9feb21e57b3409d9166a028701981cfc280aaab3c4436" -} diff --git a/backend/.sqlx/query-f632ca2e17a3952fc45bd40a055a9442c35453dff95140d2f252c4fe6a14c6a4.json b/backend/.sqlx/query-f632ca2e17a3952fc45bd40a055a9442c35453dff95140d2f252c4fe6a14c6a4.json deleted file mode 100644 index 5145efa595..0000000000 --- a/backend/.sqlx/query-f632ca2e17a3952fc45bd40a055a9442c35453dff95140d2f252c4fe6a14c6a4.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT EXISTS (SELECT 1 \n FROM workspace_settings \n WHERE workspace_id <> $1 \n AND slack_command_script IS NOT NULL\n AND slack_team_id IS NOT NULL \n AND slack_team_id = (SELECT slack_team_id FROM workspace_settings WHERE workspace_id = $1))\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "f632ca2e17a3952fc45bd40a055a9442c35453dff95140d2f252c4fe6a14c6a4" -} diff --git a/backend/.sqlx/query-f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5.json b/backend/.sqlx/query-f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5.json deleted file mode 100644 index aa29c1b4dd..0000000000 --- a/backend/.sqlx/query-f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE \n sqs_trigger \n SET \n aws_resource_path = $1,\n queue_url = $2,\n message_attributes = $3, \n is_flow = $4, \n edited_by = $5, \n email = $6,\n script_path = $7,\n path = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "TextArray", - "Bool", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5" -} diff --git a/backend/.sqlx/query-f699cc3644aeb35a0588bbb3a6bf2dc0746d9f3a1bea104123188fe2921bc886.json b/backend/.sqlx/query-f699cc3644aeb35a0588bbb3a6bf2dc0746d9f3a1bea104123188fe2921bc886.json new file mode 100644 index 0000000000..d352d3d69d --- /dev/null +++ b/backend/.sqlx/query-f699cc3644aeb35a0588bbb3a6bf2dc0746d9f3a1bea104123188fe2921bc886.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_runnable_dependencies SET app_path = REGEXP_REPLACE(app_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE app_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f699cc3644aeb35a0588bbb3a6bf2dc0746d9f3a1bea104123188fe2921bc886" +} diff --git a/backend/.sqlx/query-f955a01779f5441efc6aa9364b24c79b3cbc6413046c3b6099d19f675d8a395b.json b/backend/.sqlx/query-f955a01779f5441efc6aa9364b24c79b3cbc6413046c3b6099d19f675d8a395b.json new file mode 100644 index 0000000000..925d8c39f1 --- /dev/null +++ b/backend/.sqlx/query-f955a01779f5441efc6aa9364b24c79b3cbc6413046c3b6099d19f675d8a395b.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS (SELECT 1\n FROM workspace_settings\n WHERE workspace_id <> $1\n AND slack_command_script IS NOT NULL\n AND slack_team_id = $2\n AND (SELECT slack_command_script IS NOT NULL FROM workspace_settings WHERE workspace_id = $1))\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "f955a01779f5441efc6aa9364b24c79b3cbc6413046c3b6099d19f675d8a395b" +} diff --git a/backend/.sqlx/query-fa59674af1d1a4ceb696fc883005ef114772f7d2ee0f60cb1358cdb7f0b5cd0c.json b/backend/.sqlx/query-fa59674af1d1a4ceb696fc883005ef114772f7d2ee0f60cb1358cdb7f0b5cd0c.json deleted file mode 100644 index 917a0910cc..0000000000 --- a/backend/.sqlx/query-fa59674af1d1a4ceb696fc883005ef114772f7d2ee0f60cb1358cdb7f0b5cd0c.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT EXISTS (SELECT 1 \n FROM workspace_settings \n WHERE workspace_id <> $1 \n AND slack_command_script IS NOT NULL\n AND slack_team_id = $2\n AND (SELECT slack_command_script IS NOT NULL FROM workspace_settings WHERE workspace_id = $1))\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "fa59674af1d1a4ceb696fc883005ef114772f7d2ee0f60cb1358cdb7f0b5cd0c" -} diff --git a/backend/.sqlx/query-fd0a0ca4a107dc813240ad71f372ab7e0bf26431158fbfb5f0023ed847ca7dc5.json b/backend/.sqlx/query-fd0a0ca4a107dc813240ad71f372ab7e0bf26431158fbfb5f0023ed847ca7dc5.json new file mode 100644 index 0000000000..d47bb3fd68 --- /dev/null +++ b/backend/.sqlx/query-fd0a0ca4a107dc813240ad71f372ab7e0bf26431158fbfb5f0023ed847ca7dc5.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n gcp_trigger \n SET \n enabled = FALSE, \n error = $1, \n server_id = NULL, \n last_server_ping = NULL \n WHERE \n workspace_id = $2 AND \n path = $3\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "fd0a0ca4a107dc813240ad71f372ab7e0bf26431158fbfb5f0023ed847ca7dc5" +} diff --git a/backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json b/backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json deleted file mode 100644 index e797ab8805..0000000000 --- a/backend/.sqlx/query-fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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" -} diff --git a/backend/.sqlx/query-fe7221651a982861dede4116bc71fe2dce615ff76a53f72cb5386dc17e4e07aa.json b/backend/.sqlx/query-fe7221651a982861dede4116bc71fe2dce615ff76a53f72cb5386dc17e4e07aa.json new file mode 100644 index 0000000000..1ca60067d0 --- /dev/null +++ b/backend/.sqlx/query-fe7221651a982861dede4116bc71fe2dce615ff76a53f72cb5386dc17e4e07aa.json @@ -0,0 +1,21 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n SELECT unnest($1::uuid[]), $2, $3, $4, $5, $6, $7, $8", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "UuidArray", + "Varchar", + "Varchar", + "Bool", + "Bool", + "JsonbArray", + "TextArray", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "fe7221651a982861dede4116bc71fe2dce615ff76a53f72cb5386dc17e4e07aa" +} diff --git a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json b/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json index 6a4a3b3d75..4e911403de 100644 --- a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json +++ b/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json @@ -63,7 +63,10 @@ "rust", "ansible", "csharp", - "oracledb" + "oracledb", + "nu", + "java", + "duckdb" ] } } diff --git a/backend/.vscode/settings.json b/backend/.vscode/settings.json index ab8340f5c8..6c9612d5c3 100644 --- a/backend/.vscode/settings.json +++ b/backend/.vscode/settings.json @@ -12,4 +12,6 @@ "conventionalCommits.scopes": [ "restructring triggers, decoding trigger message on work" ], + "files.exclude": { "**/*ee.rs": false }, + "search.exclude": { "**/*ee.rs": false } } diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 0000000000..1fe509a0d6 --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,12 @@ +# Backend Development (Rust) + +## Core Principles + +- Follow @rust-best-practices.mdc for detailed guidelines +- Database schema reference: @summarized_schema.txt +- The API routes prefixes are all listed in windmill-api/src/lib.rs + +## Adding New Features + +1. Update database schema with migration if necessary +2. Update backend/windmill-api/openapi.yaml after modifying API endpoints diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0128144432..27b967ac4e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -29,9 +29,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -109,23 +109,23 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -190,44 +190,53 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arc-swap" @@ -279,9 +288,9 @@ dependencies = [ [[package]] name = "arrow" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05048a8932648b63f21c37d88b552ccc8a65afb6dfe9fc9f30ce79174c2e7a85" +checksum = "b1bb018b6960c87fd9d025009820406f74e83281185a8bdcb44880d2aa5c9a87" dependencies = [ "arrow-arith", "arrow-array", @@ -300,41 +309,40 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8a57966e43bfe9a3277984a14c24ec617ad874e4c0e1d2a1b083a39cfbf22c" +checksum = "44de76b51473aa888ecd6ad93ceb262fb8d40d1f1154a4df2f069b3590aa7575" dependencies = [ "arrow-array", "arrow-buffer", "arrow-data", "arrow-schema", "chrono", - "half", "num", ] [[package]] name = "arrow-array" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f4a9468c882dc66862cef4e1fd8423d47e67972377d85d80e022786427768c" +checksum = "29ed77e22744475a9a53d00026cf8e166fe73cf42d89c4c4ae63607ee1cfcc3f" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow-buffer", "arrow-data", "arrow-schema", "chrono", - "chrono-tz 0.9.0", + "chrono-tz", "half", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "num", ] [[package]] name = "arrow-buffer" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c975484888fc95ec4a632cdc98be39c085b1bb518531b0c80c5d462063e5daa1" +checksum = "b0391c96eb58bf7389171d1e103112d3fc3e5625ca6b372d606f2688f1ea4cce" dependencies = [ "bytes", "half", @@ -343,9 +351,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da26719e76b81d8bc3faad1d4dbdc1bcc10d14704e63dc17fc9f3e7e1e567c8e" +checksum = "f39e1d774ece9292697fcbe06b5584401b26bd34be1bec25c33edae65c2420ff" dependencies = [ "arrow-array", "arrow-buffer", @@ -364,28 +372,25 @@ dependencies = [ [[package]] name = "arrow-csv" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13c36dc5ddf8c128df19bab27898eea64bf9da2b555ec1cd17a8ff57fba9ec2" +checksum = "9055c972a07bf12c2a827debfd34f88d3b93da1941d36e1d9fee85eebe38a12a" dependencies = [ "arrow-array", - "arrow-buffer", "arrow-cast", - "arrow-data", "arrow-schema", "chrono", "csv", "csv-core", "lazy_static", - "lexical-core", "regex", ] [[package]] name = "arrow-data" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9d6f18c65ef7a2573ab498c374d8ae364b4a4edf67105357491c031f716ca5" +checksum = "cf75ac27a08c7f48b88e5c923f267e980f27070147ab74615ad85b5c5f90473d" dependencies = [ "arrow-buffer", "arrow-schema", @@ -395,13 +400,12 @@ dependencies = [ [[package]] name = "arrow-ipc" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e786e1cdd952205d9a8afc69397b317cfbb6e0095e445c69cda7e8da5c1eeb0f" +checksum = "a222f0d93772bd058d1268f4c28ea421a603d66f7979479048c429292fac7b2e" dependencies = [ "arrow-array", "arrow-buffer", - "arrow-cast", "arrow-data", "arrow-schema", "flatbuffers", @@ -410,9 +414,9 @@ dependencies = [ [[package]] name = "arrow-json" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb22284c5a2a01d73cebfd88a33511a3234ab45d66086b2ca2d1228c3498e445" +checksum = "9085342bbca0f75e8cb70513c0807cc7351f1fbf5cb98192a67d5e3044acb033" dependencies = [ "arrow-array", "arrow-buffer", @@ -421,35 +425,34 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.7.1", + "indexmap 2.9.0", "lexical-core", + "memchr", "num", "serde", "serde_json", + "simdutf8", ] [[package]] name = "arrow-ord" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42745f86b1ab99ef96d1c0bcf49180848a64fe2c7a7a0d945bc64fa2b21ba9bc" +checksum = "ab2f1065a5cad7b9efa9e22ce5747ce826aa3855766755d4904535123ef431e7" dependencies = [ "arrow-array", "arrow-buffer", "arrow-data", "arrow-schema", "arrow-select", - "half", - "num", ] [[package]] name = "arrow-row" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd09a518c602a55bd406bcc291a967b284cfa7a63edfbf8b897ea4748aad23c" +checksum = "3703a0e3e92d23c3f756df73d2dc9476873f873a76ae63ef9d3de17fda83b2d8" dependencies = [ - "ahash 0.8.11", "arrow-array", "arrow-buffer", "arrow-data", @@ -459,17 +462,20 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e972cd1ff4a4ccd22f86d3e53e835c2ed92e0eea6a3e8eadb72b4f1ac802cf8" +checksum = "73a47aa0c771b5381de2b7f16998d351a6f4eb839f1e13d48353e17e873d969b" +dependencies = [ + "bitflags 2.9.1", +] [[package]] name = "arrow-select" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600bae05d43483d216fb3494f8c32fdbefd8aa4e1de237e790dbb3d9f44690a3" +checksum = "24b7b85575702b23b85272b01bc1c25a01c9b9852305e5d0078c79ba25d995d4" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow-array", "arrow-buffer", "arrow-data", @@ -479,9 +485,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc1985b67cb45f6606a248ac2b4a288849f196bab8c657ea5589f47cdd55e6" +checksum = "9260fddf1cdf2799ace2b4c2fc0356a9789fa7551e0953e35435536fecefebbd" dependencies = [ "arrow-array", "arrow-buffer", @@ -551,7 +557,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -572,7 +578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ "brotli 7.0.0", - "bzip2 0.5.2", + "bzip2", "flate2", "futures-core", "futures-io", @@ -601,7 +607,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regex", - "ring 0.17.12", + "ring 0.17.14", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-webpki 0.102.8", @@ -628,13 +634,13 @@ checksum = "021cf450e9574793e45e1044a5d3d94bba7dbaa0802e6122e9c10eb8c4dd12dc" dependencies = [ "base64 0.22.1", "bytes", - "http 1.2.0", + "http 1.3.1", "rand 0.8.5", - "reqwest 0.12.12", + "reqwest 0.12.20", "serde", "serde-aux", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 1.0.69", "url", ] @@ -653,7 +659,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -675,43 +681,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", -] - -[[package]] -name = "async-stripe" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58d670cf4d47a1b8ffef54286a5625382e360a34ee76902fd93ad8c7032a0c30" -dependencies = [ - "chrono", - "futures-util", - "hex", - "hmac", - "http-types", - "hyper 0.14.32", - "hyper-tls 0.5.0", - "serde", - "serde_json", - "serde_path_to_error", - "serde_qs 0.10.1", - "sha2 0.10.8", - "smart-default", - "smol_str", - "thiserror 1.0.69", - "tokio", - "uuid 0.8.2", + "syn 2.0.104", ] [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -723,7 +704,7 @@ dependencies = [ "async-compression", "chrono", "crc32fast", - "futures-lite 2.6.0", + "futures-lite", "pin-project", "thiserror 1.0.69", "tokio", @@ -760,15 +741,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.5.18" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90aff65e86db5fe300752551c1b015ef72b708ac54bded8ef43d0d53cb7cb0b1" +checksum = "455e9fb7743c6f6267eb2830ccc08686fbb3d13c9a689369562fd4d4ef9ea462" dependencies = [ "aws-credential-types", "aws-runtime", @@ -776,17 +757,17 @@ dependencies = [ "aws-sdk-ssooidc", "aws-sdk-sts", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.3.0", + "fastrand", "hex", - "http 0.2.12", - "ring 0.17.12", + "http 1.3.1", + "ring 0.17.14", "time", "tokio", "tracing", @@ -796,9 +777,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -807,106 +788,128 @@ dependencies = [ ] [[package]] -name = "aws-runtime" -version = "1.5.5" +name = "aws-lc-rs" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dd04d39cc12844c0994f2c9c5a6f5184c22e9188ec1ff723de41910a21dcad" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +dependencies = [ + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" dependencies = [ "aws-credential-types", "aws-sigv4", "aws-smithy-async", - "aws-smithy-http 0.60.12", + "aws-smithy-http", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.3.0", + "fastrand", "http 0.2.12", "http-body 0.4.6", - "once_cell", "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.15.1", + "uuid", ] [[package]] name = "aws-sdk-sqs" -version = "1.61.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50ff7694f857436b7c9f8e8e930190bdf8381251151886f4a0ac32eb0cb8fd1" +checksum = "8f3d3cf0d52a50e0ac3fada7e0db27a2172b986f833d061def1df78c7dbe80ae" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sso" -version = "1.61.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65ff295979977039a25f5a0bf067a64bc5e6aa38f3cef4037cf42516265553c" +checksum = "b2ac1674cba7872061a29baaf02209fefe499ff034dfd91bd4cc59e4d7741489" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.62.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91430a60f754f235688387b75ee798ef00cfd09709a582be2b7525ebb5306d4f" +checksum = "3a6a22f077f5fd3e3c0270d4e1a110346cddf6769e9433eb9e6daceb4ca3b149" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.62.0" +version = "1.75.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9276e139d39fff5a0b0c984fc2d30f970f9a202da67234f948fda02e5bea1dbe" +checksum = "e3258fa707f2f585ee3049d9550954b959002abd59176975150a01d5cf38ae3f" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-query", "aws-smithy-runtime", @@ -914,20 +917,20 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", + "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.2.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe75fad52793ce6dec0dc3d4b1f388f038b5eb866c8d4d7f3a8e21b5ea5051" +checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" dependencies = [ "aws-credential-types", - "aws-smithy-http 0.60.12", + "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", @@ -935,19 +938,18 @@ dependencies = [ "hex", "hmac", "http 0.2.12", - "http 1.2.0", - "once_cell", + "http 1.3.1", "percent-encoding", - "sha2 0.10.8", + "sha2 0.10.9", "time", "tracing", ] [[package]] name = "aws-smithy-async" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" dependencies = [ "futures-util", "pin-project-lite", @@ -956,9 +958,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.12" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7809c27ad8da6a6a68c454e651d4962479e81472aa19ae99e59f9aba1f9713cc" +checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -966,8 +968,8 @@ dependencies = [ "bytes-utils", "futures-core", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", - "once_cell", "percent-encoding", "pin-project-lite", "pin-utils", @@ -975,34 +977,52 @@ dependencies = [ ] [[package]] -name = "aws-smithy-http" -version = "0.61.1" +name = "aws-smithy-http-client" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f276f21c7921fe902826618d1423ae5bf74cf8c1b8472aee8434f3dfd31824" +checksum = "7f491388e741b7ca73b24130ff464c1478acc34d5b331b7dd0a2ee4643595a15" dependencies = [ + "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", + "h2 0.3.26", + "h2 0.4.10", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", - "once_cell", - "percent-encoding", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", "pin-project-lite", - "pin-utils", + "rustls 0.21.12", + "rustls 0.23.28", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower 0.5.2", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.2" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -1015,42 +1035,39 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.8" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" +checksum = "14302f06d1d5b7d333fd819943075b13d27c7700b414f574c3c35859bfb55d5e" dependencies = [ "aws-smithy-async", - "aws-smithy-http 0.60.12", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand 2.3.0", - "h2 0.3.26", + "fastrand", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", "http-body 1.0.1", - "httparse", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.12", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "bd8531b6d8882fd8f48f82a9754e682e29dd44cff27154af51fa3eb730f59efb" dependencies = [ "aws-smithy-async", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "pin-project-lite", "tokio", "tracing", @@ -1059,16 +1076,16 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.13" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd 0.8.0", "bytes", "bytes-utils", "futures-core", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1084,19 +1101,29 @@ dependencies = [ ] [[package]] -name = "aws-smithy-xml" +name = "aws-smithy-types-convert" version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "df786cc1aea35d24b609f7a32d05570916edfe7b3e09e81f2faf365f9062f647" +dependencies = [ + "aws-smithy-types", + "chrono", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.5" +version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbd0a668309ec1f66c0f6bda4840dd6d4796ae26d699ebc266d7cc95c6d040f" +checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -1116,7 +1143,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -1150,7 +1177,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -1170,25 +1197,25 @@ checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" [[package]] name = "backon" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fef586913a57ff189f25c9b3d034356a5bf6b3fa9a7f067588fe1698ba1f5d" +checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" dependencies = [ - "fastrand 2.3.0", + "fastrand", "gloo-timers", "tokio", ] [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.5", + "miniz_oxide 0.8.9", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -1245,9 +1272,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "better_scoped_tls" @@ -1260,9 +1287,9 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", @@ -1286,20 +1313,20 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.12.1", "lazy_static", "lazycell", "log", - "prettyplease 0.2.30", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.99", + "syn 2.0.104", "which 4.4.2", ] @@ -1309,18 +1336,18 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", "log", - "prettyplease 0.2.30", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1329,7 +1356,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -1338,6 +1374,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -1346,9 +1388,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -1385,9 +1427,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", @@ -1458,7 +1500,7 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper 1.6.0", "hyper-named-pipe", @@ -1492,9 +1534,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases 0.2.1", @@ -1502,15 +1544,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1520,7 +1562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17d4f95e880cfd28c4ca5a006cf7f6af52b4bcb7b5866f573b2faa126fb7affb" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1531,7 +1573,7 @@ checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 4.0.3", ] [[package]] @@ -1542,14 +1584,35 @@ checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 4.0.3", +] + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", ] [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1557,9 +1620,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", @@ -1575,22 +1638,23 @@ dependencies = [ ] [[package]] -name = "built" -version = "0.6.1" +name = "bumpalo" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99c4cdc7b2c2364182331055623bdf45254fcb679fea565c40c3c11c101889a" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" dependencies = [ - "cargo-lock", - "git2", + "allocator-api2", ] [[package]] -name = "bumpalo" -version = "3.17.0" +name = "byte-unit" +version = "5.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" dependencies = [ - "allocator-api2", + "rust_decimal", + "serde", + "utf8-width", ] [[package]] @@ -1617,22 +1681,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1661,14 +1725,10 @@ dependencies = [ ] [[package]] -name = "bzip2" -version = "0.4.4" +name = "bytesize" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "bzip2" @@ -1697,30 +1757,31 @@ checksum = "1bf2a5fb3207c12b5d208ebc145f967fea5cac41a021c37417ccc31ba40f39ee" [[package]] name = "candle-core" -version = "0.3.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db8659ea87ee8197d2fc627348916cce0561330ee7ae3874e771691d3cecb2f" +checksum = "a9f51e2ecf6efe9737af8f993433c839f956d2b6ed4fd2dd4a7c6d8b0fa667ff" dependencies = [ "byteorder", - "gemm", + "gemm 0.17.1", "half", "memmap2 0.9.5", "num-traits", "num_cpus", - "rand 0.8.5", - "rand_distr", + "rand 0.9.0", + "rand_distr 0.5.1", "rayon", "safetensors", "thiserror 1.0.69", - "yoke", + "ug", + "yoke 0.7.5", "zip", ] [[package]] name = "candle-nn" -version = "0.3.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddce8312032760a6791d6adc9c56dc54fd7c1be38d85dcc4862f1c75228bbc7" +checksum = "c1980d53280c8f9e2c6cbe1785855d7ff8010208b46e21252b978badf13ad69d" dependencies = [ "candle-core", "half", @@ -1733,21 +1794,21 @@ dependencies = [ [[package]] name = "candle-transformers" -version = "0.3.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68834a0cacb7e002d1f4abfe26a7cd1237e2ba342fddcf2e30913c4edb96409d" +checksum = "186cb80045dbe47e0b387ea6d3e906f02fb3056297080d9922984c90e90a72b0" dependencies = [ "byteorder", "candle-core", "candle-nn", + "fancy-regex 0.13.0", "num-traits", - "rand 0.8.5", + "rand 0.9.0", "rayon", "serde", "serde_json", "serde_plain", "tracing", - "wav", ] [[package]] @@ -1778,20 +1839,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] -name = "cargo-lock" -version = "9.0.0" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11c675378efb449ed3ce8de78d75d0d80542fc98487c26aba28eb3b82feac72" -dependencies = [ - "semver 1.0.26", - "serde", - "toml 0.7.8", - "url", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cbc" @@ -1804,9 +1859,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -1830,9 +1885,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -1848,57 +1903,45 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", + "pure-rust-locales", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "chrono-humanize" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b" +dependencies = [ + "chrono", ] [[package]] name = "chrono-tz" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" dependencies = [ "chrono", - "chrono-tz-build 0.3.0", - "phf", -] - -[[package]] -name = "chrono-tz" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6ac4f2c0bf0f44e9161aec9675e1050aa4a530663c4a9e37e108fa948bca9f" -dependencies = [ - "chrono", - "chrono-tz-build 0.4.0", + "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" +checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" dependencies = [ "parse-zoneinfo", "phf_codegen", @@ -1931,14 +1974,14 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.6", + "libloading 0.8.8", ] [[package]] name = "clap" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -1946,9 +1989,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -1958,21 +2001,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clipboard-win" @@ -2020,7 +2063,7 @@ dependencies = [ "nom 7.1.3", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2031,9 +2074,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "comfy-table" @@ -2042,7 +2085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -2069,7 +2112,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "windows-sys 0.59.0", ] @@ -2094,7 +2137,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -2180,9 +2223,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -2216,9 +2259,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -2260,31 +2303,18 @@ dependencies = [ [[package]] name = "croner" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" +checksum = "c344b0690c1ad1c7176fe18eb173e0c927008fdaaa256e40dfd43ddd149c0843" dependencies = [ "chrono", ] -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -2324,10 +2354,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "crunchy" -version = "0.2.3" +name = "crossterm_winapi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -2406,7 +2445,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2415,8 +2454,8 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813" dependencies = [ - "bitflags 2.9.0", - "libloading 0.8.6", + "bitflags 2.9.1", + "libloading 0.8.8", "winapi", ] @@ -2442,12 +2481,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -2480,16 +2519,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2516,13 +2555,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.10", + "darling_core 0.20.11", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2535,14 +2574,28 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.11", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.11", ] [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" @@ -2552,105 +2605,280 @@ checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "datafusion" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f92d2d7a9cba4580900b32b009848d9eb35f1028ac84cdd6ddcf97612cd0068" +checksum = "ffe060b978f74ab446be722adb8a274e052e005bf6dfd171caadc3abaad10080" dependencies = [ - "ahash 0.8.11", "arrow", - "arrow-array", "arrow-ipc", "arrow-schema", - "async-compression", "async-trait", "bytes", - "bzip2 0.4.4", + "bzip2", "chrono", - "dashmap", + "datafusion-catalog", + "datafusion-catalog-listing", "datafusion-common", "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-datasource-csv", + "datafusion-datasource-json", + "datafusion-datasource-parquet", "datafusion-execution", "datafusion-expr", + "datafusion-expr-common", "datafusion-functions", "datafusion-functions-aggregate", - "datafusion-functions-array", + "datafusion-functions-nested", + "datafusion-functions-table", + "datafusion-functions-window", + "datafusion-macros", "datafusion-optimizer", "datafusion-physical-expr", "datafusion-physical-expr-common", + "datafusion-physical-optimizer", "datafusion-physical-plan", + "datafusion-session", "datafusion-sql", "flate2", "futures", - "glob", - "half", - "hashbrown 0.14.5", - "indexmap 2.7.1", - "itertools 0.12.1", + "itertools 0.14.0", "log", - "num_cpus", "object_store", - "parking_lot", + "parking_lot 0.12.4", "parquet", - "paste", - "pin-project-lite", "rand 0.8.5", + "regex", "sqlparser", "tempfile", "tokio", - "tokio-util", "url", - "uuid 1.15.1", + "uuid", "xz2", "zstd", ] [[package]] -name = "datafusion-common" -version = "39.0.0" +name = "datafusion-catalog" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effed030d2c1667eb1e11df5372d4981eaf5d11a521be32220b3985ae5ba6971" +checksum = "61fe34f401bd03724a1f96d12108144f8cd495a3cdda2bf5e091822fb80b7e66" dependencies = [ - "ahash 0.8.11", "arrow", - "arrow-array", - "arrow-buffer", - "arrow-schema", - "chrono", - "half", - "hashbrown 0.14.5", - "instant", - "libc", - "num_cpus", + "async-trait", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-session", + "datafusion-sql", + "futures", + "itertools 0.14.0", + "log", "object_store", - "parquet", - "sqlparser", -] - -[[package]] -name = "datafusion-common-runtime" -version = "39.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0091318129dad1359f08e4c6c71f855163c35bba05d1dbf983196f727857894" -dependencies = [ + "parking_lot 0.12.4", "tokio", ] [[package]] -name = "datafusion-execution" -version = "39.0.0" +name = "datafusion-catalog-listing" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8385aba84fc4a06d3ebccfbcbf9b4f985e80c762fac634b49079f7cc14933fb1" +checksum = "a4411b8e3bce5e0fc7521e44f201def2e2d5d1b5f176fb56e8cdc9942c890f00" dependencies = [ "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "log", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-common" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0734015d81c8375eb5d4869b7f7ecccc2ee8d6cb81948ef737cd0e7b743bd69c" +dependencies = [ + "ahash 0.8.12", + "arrow", + "arrow-ipc", + "base64 0.22.1", + "half", + "hashbrown 0.14.5", + "indexmap 2.9.0", + "libc", + "log", + "object_store", + "parquet", + "paste", + "recursive", + "sqlparser", + "tokio", + "web-time", +] + +[[package]] +name = "datafusion-common-runtime" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5167bb1d2ccbb87c6bc36c295274d7a0519b14afcfdaf401d53cbcaa4ef4968b" +dependencies = [ + "futures", + "log", + "tokio", +] + +[[package]] +name = "datafusion-datasource" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e602dcdf2f50c2abf297cc2203c73531e6f48b29516af7695d338cf2a778b1" +dependencies = [ + "arrow", + "async-compression", + "async-trait", + "bytes", + "bzip2", "chrono", - "dashmap", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "flate2", + "futures", + "glob", + "itertools 0.14.0", + "log", + "object_store", + "parquet", + "rand 0.8.5", + "tempfile", + "tokio", + "tokio-util", + "url", + "xz2", + "zstd", +] + +[[package]] +name = "datafusion-datasource-csv" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bb2253952dc32296ed5b84077cb2e0257fea4be6373e1c376426e17ead4ef6" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "regex", + "tokio", +] + +[[package]] +name = "datafusion-datasource-json" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8c7f47a5d2fe03bfa521ec9bafdb8a5c82de8377f60967c3663f00c8790352" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "serde_json", + "tokio", +] + +[[package]] +name = "datafusion-datasource-parquet" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d15868ea39ed2dc266728b554f6304acd473de2142281ecfa1294bb7415923" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.4", + "parquet", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "datafusion-doc" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91f8c2c5788ef32f48ff56c68e5b545527b744822a284373ac79bba1ba47292" + +[[package]] +name = "datafusion-execution" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06f004d100f49a3658c9da6fb0c3a9b760062d96cd4ad82ccc3b7b69a9fb2f84" +dependencies = [ + "arrow", + "dashmap 6.1.0", "datafusion-common", "datafusion-expr", "futures", - "hashbrown 0.14.5", "log", "object_store", - "parking_lot", + "parking_lot 0.12.4", "rand 0.8.5", "tempfile", "url", @@ -2658,160 +2886,258 @@ dependencies = [ [[package]] name = "datafusion-expr" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb192f0055d2ce64e38ac100abc18e4e6ae9734d3c28eee522bbbd6a32108a3" +checksum = "7a4e4ce3802609be38eeb607ee72f6fe86c3091460de9dbfae9e18db423b3964" dependencies = [ - "ahash 0.8.11", "arrow", - "arrow-array", - "arrow-buffer", "chrono", "datafusion-common", + "datafusion-doc", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr-common", + "indexmap 2.9.0", "paste", + "recursive", "serde_json", "sqlparser", - "strum 0.26.3", - "strum_macros 0.26.4", +] + +[[package]] +name = "datafusion-expr-common" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ac9cf3b22bbbae8cdf8ceb33039107fde1b5492693168f13bd566b1bcc839" +dependencies = [ + "arrow", + "datafusion-common", + "indexmap 2.9.0", + "itertools 0.14.0", + "paste", ] [[package]] name = "datafusion-functions" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c081ae5b7edd712b92767fb8ed5c0e32755682f8075707666cd70835807c0b" +checksum = "2ddf0a0a2db5d2918349c978d42d80926c6aa2459cd8a3c533a84ec4bb63479e" dependencies = [ "arrow", + "arrow-buffer", "base64 0.22.1", "blake2", "blake3", "chrono", "datafusion-common", + "datafusion-doc", "datafusion-execution", "datafusion-expr", - "datafusion-physical-expr", - "hashbrown 0.14.5", + "datafusion-expr-common", + "datafusion-macros", "hex", - "itertools 0.12.1", + "itertools 0.14.0", "log", "md-5 0.10.6", "rand 0.8.5", "regex", - "sha2 0.10.8", + "sha2 0.10.9", "unicode-segmentation", - "uuid 1.15.1", + "uuid", ] [[package]] name = "datafusion-functions-aggregate" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb28a4ea52c28a26990646986a27c4052829a2a2572386258679e19263f8b78" +checksum = "408a05dafdc70d05a38a29005b8b15e21b0238734dab1e98483fcb58038c5aba" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow", - "arrow-schema", "datafusion-common", + "datafusion-doc", "datafusion-execution", "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr", "datafusion-physical-expr-common", + "half", "log", "paste", - "sqlparser", ] [[package]] -name = "datafusion-functions-array" -version = "39.0.0" +name = "datafusion-functions-aggregate-common" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b17c02a74cdc87380a56758ec27e7d417356bf806f33062700908929aedb8a" +checksum = "756d21da2dd6c9bef97af1504970ff56cbf35d03fbd4ffd62827f02f4d2279d4" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-functions-nested" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8d50f6334b378930d992d801a10ac5b3e93b846b39e4a05085742572844537" dependencies = [ "arrow", - "arrow-array", - "arrow-buffer", "arrow-ord", - "arrow-schema", "datafusion-common", + "datafusion-doc", "datafusion-execution", "datafusion-expr", "datafusion-functions", - "itertools 0.12.1", + "datafusion-functions-aggregate", + "datafusion-macros", + "datafusion-physical-expr-common", + "itertools 0.14.0", "log", "paste", ] +[[package]] +name = "datafusion-functions-table" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9a97220736c8fff1446e936be90d57216c06f28969f9ffd3b72ac93c958c8a" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot 0.12.4", + "paste", +] + +[[package]] +name = "datafusion-functions-window" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefc2d77646e1aadd1d6a9c40088937aedec04e68c5f0465939912e1291f8193" +dependencies = [ + "datafusion-common", + "datafusion-doc", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-window-common" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4aff082c42fa6da99ce0698c85addd5252928c908eb087ca3cfa64ff16b313" +dependencies = [ + "datafusion-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-macros" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6f88d7ee27daf8b108ba910f9015176b36fbc72902b1ca5c2a5f1d1717e1a1" +dependencies = [ + "datafusion-expr", + "quote", + "syn 2.0.104", +] + [[package]] name = "datafusion-optimizer" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12172f2a6c9eb4992a51e62d709eeba5dedaa3b5369cce37ff6c2260e100ba76" +checksum = "084d9f979c4b155346d3c34b18f4256e6904ded508e9554d90fed416415c3515" dependencies = [ "arrow", - "async-trait", "chrono", "datafusion-common", "datafusion-expr", "datafusion-physical-expr", - "hashbrown 0.14.5", - "indexmap 2.7.1", - "itertools 0.12.1", + "indexmap 2.9.0", + "itertools 0.14.0", "log", + "recursive", + "regex", "regex-syntax 0.8.5", ] [[package]] name = "datafusion-physical-expr" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3fce531b623e94180f6cd33d620ef01530405751b6ddd2fd96250cdbd78e2e" +checksum = "64c536062b0076f4e30084065d805f389f9fe38af0ca75bcbac86bc5e9fbab65" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow", - "arrow-array", - "arrow-buffer", - "arrow-ord", - "arrow-schema", - "arrow-string", - "base64 0.22.1", - "chrono", "datafusion-common", - "datafusion-execution", "datafusion-expr", - "datafusion-functions-aggregate", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", "datafusion-physical-expr-common", "half", "hashbrown 0.14.5", - "hex", - "indexmap 2.7.1", - "itertools 0.12.1", + "indexmap 2.9.0", + "itertools 0.14.0", "log", "paste", - "petgraph 0.6.5", - "regex", + "petgraph", ] [[package]] name = "datafusion-physical-expr-common" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046400b6a2cc3ed57a7c576f5ae6aecc77804ac8e0186926b278b189305b2a77" +checksum = "f8a92b53b3193fac1916a1c5b8e3f4347c526f6822e56b71faa5fb372327a863" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "hashbrown 0.14.5", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa0a5ac94c7cf3da97bedabd69d6bbca12aef84b9b37e6e9e8c25286511b5e2" dependencies = [ "arrow", "datafusion-common", + "datafusion-execution", "datafusion-expr", - "rand 0.8.5", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "itertools 0.14.0", + "log", + "recursive", ] [[package]] name = "datafusion-physical-plan" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aed47f5a2ad8766260befb375b201592e86a08b260256e168ae4311426a2bff" +checksum = "690c615db468c2e5fe5085b232d8b1c088299a6c63d87fd960a354a71f7acb55" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow", - "arrow-array", - "arrow-buffer", "arrow-ord", "arrow-schema", "async-trait", @@ -2820,37 +3146,59 @@ dependencies = [ "datafusion-common-runtime", "datafusion-execution", "datafusion-expr", - "datafusion-functions-aggregate", + "datafusion-functions-window-common", "datafusion-physical-expr", "datafusion-physical-expr-common", "futures", "half", "hashbrown 0.14.5", - "indexmap 2.7.1", - "itertools 0.12.1", + "indexmap 2.9.0", + "itertools 0.14.0", "log", - "once_cell", - "parking_lot", + "parking_lot 0.12.4", "pin-project-lite", - "rand 0.8.5", + "tokio", +] + +[[package]] +name = "datafusion-session" +version = "47.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad229a134c7406c057ece00c8743c0c34b97f4e72f78b475fe17b66c5e14fa4f" +dependencies = [ + "arrow", + "async-trait", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-sql", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.4", "tokio", ] [[package]] name = "datafusion-sql" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa92bb1fd15e46ce5fb6f1c85f3ac054592560f294429a28e392b5f9cd4255e" +checksum = "64f6ab28b72b664c21a27b22a2ff815fd390ed224c26e89a93b5a8154a4e8607" dependencies = [ "arrow", - "arrow-array", - "arrow-schema", + "bigdecimal", "datafusion-common", "datafusion-expr", + "indexmap 2.9.0", "log", + "recursive", "regex", "sqlparser", - "strum 0.26.3", ] [[package]] @@ -2866,7 +3214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -2883,7 +3231,7 @@ dependencies = [ "once_cell", "percent-encoding", "serde", - "sourcemap 9.1.2", + "sourcemap 9.2.2", "swc_atoms", "swc_common", "swc_config", @@ -2922,7 +3270,7 @@ dependencies = [ "deno_error", "thiserror 2.0.12", "tokio", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -2936,7 +3284,7 @@ dependencies = [ "deno_error", "rusqlite", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 2.0.12", "tokio", ] @@ -2957,14 +3305,14 @@ dependencies = [ "deno_error", "deno_media_type", "deno_path_util", - "http 1.2.0", - "indexmap 2.7.1", + "http 1.3.1", + "indexmap 2.9.0", "log", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sys_traits", "thiserror 1.0.69", "url", @@ -2999,7 +3347,7 @@ dependencies = [ "glob", "ignore", "import_map", - "indexmap 2.7.1", + "indexmap 2.9.0", "jsonc-parser", "log", "percent-encoding", @@ -3029,8 +3377,8 @@ dependencies = [ "anyhow", "az", "bincode", - "bit-set", - "bit-vec", + "bit-set 0.5.3", + "bit-vec 0.6.3", "bytes", "capacity_builder 0.1.3", "cooked-waker", @@ -3040,10 +3388,10 @@ dependencies = [ "deno_path_util", "deno_unsync", "futures", - "indexmap 2.7.1", + "indexmap 2.9.0", "libc", "memoffset", - "parking_lot", + "parking_lot 0.12.4", "percent-encoding", "pin-project", "serde", @@ -3106,18 +3454,18 @@ dependencies = [ "p384", "p521", "rand 0.8.5", - "ring 0.17.12", + "ring 0.17.14", "rsa", "sec1", "serde", "serde_bytes", "sha1", - "sha2 0.10.8", + "sha2 0.10.9", "signature", "spki", "thiserror 2.0.12", "tokio", - "uuid 1.15.1", + "uuid", "x25519-dalek", ] @@ -3143,7 +3491,7 @@ checksum = "babccedee31ce7e57c3e6dff2cb3ab8d68c49d0df8222fe0d11d628e65192790" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3163,12 +3511,12 @@ dependencies = [ "deno_tls", "dyn-clone", "error_reporter", - "h2 0.4.8", + "h2 0.4.10", "hickory-resolver", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls 0.27.7", "hyper-util", "ipnet", "percent-encoding", @@ -3225,7 +3573,7 @@ dependencies = [ "filetime", "junction", "libc", - "nix", + "nix 0.27.1", "rand 0.8.5", "rayon", "serde", @@ -3252,7 +3600,7 @@ dependencies = [ "deno_websocket", "flate2", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "httparse", "hyper 0.14.32", "hyper 1.6.0", @@ -3264,7 +3612,7 @@ dependencies = [ "percent-encoding", "phf", "pin-project", - "ring 0.17.12", + "ring 0.17.14", "scopeguard", "serde", "smallvec", @@ -3288,11 +3636,11 @@ dependencies = [ "log", "once_cell", "os_pipe", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "rand 0.8.5", "tokio", - "uuid 1.15.1", + "uuid", "winapi", "windows-sys 0.59.0", ] @@ -3319,7 +3667,7 @@ dependencies = [ "denokv_remote", "denokv_sqlite", "faster-hex", - "http 1.2.0", + "http 1.3.1", "http-body-util", "log", "num-bigint", @@ -3444,14 +3792,14 @@ dependencies = [ "elliptic-curve", "errno", "faster-hex", - "h2 0.4.8", + "h2 0.4.10", "hkdf", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper 1.6.0", "hyper-util", "idna", - "indexmap 2.7.1", + "indexmap 2.9.0", "ipnetwork", "k256", "lazy-regex", @@ -3474,14 +3822,14 @@ dependencies = [ "pkcs8", "rand 0.8.5", "regex", - "ring 0.17.12", + "ring 0.17.14", "ripemd", "rsa", "scrypt", "sec1", "serde", "sha1", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "signature", "simd-json", @@ -3493,12 +3841,12 @@ dependencies = [ "tokio", "tokio-eld", "url", - "webpki-root-certs", + "webpki-root-certs 0.26.11", "winapi", "windows-sys 0.59.0", "x25519-dalek", "x509-parser", - "yoke", + "yoke 0.7.5", ] [[package]] @@ -3527,14 +3875,14 @@ version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d328067139909aa81522a5d90f119368b541fbddd73ab630e4d9f777865f0d" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "proc-macro-rules", "proc-macro2", "quote", "stringcase", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.99", + "syn 2.0.104", "thiserror 2.0.12", ] @@ -3571,7 +3919,7 @@ dependencies = [ "deno_error", "deno_path_util", "deno_semver", - "indexmap 2.7.1", + "indexmap 2.9.0", "serde", "serde_json", "sys_traits", @@ -3630,7 +3978,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.27.1", "pin-project-lite", "rand 0.8.5", "serde", @@ -3654,7 +4002,7 @@ dependencies = [ "async-trait", "base32", "boxed_error", - "dashmap", + "dashmap 5.5.3", "deno_cache_dir", "deno_config", "deno_error", @@ -3668,7 +4016,7 @@ dependencies = [ "log", "node_resolver", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "sys_traits", "thiserror 2.0.12", "url", @@ -3716,14 +4064,14 @@ dependencies = [ "dlopen2 0.6.1", "encoding_rs", "fastwebsockets", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper 0.14.32", "hyper 1.6.0", "hyper-util", "libc", "log", - "nix", + "nix 0.27.1", "node_resolver", "notify", "ntapi", @@ -3739,7 +4087,7 @@ dependencies = [ "tokio", "tokio-metrics", "twox-hash 1.6.3", - "uuid 1.15.1", + "uuid", "which 6.0.3", "winapi", "windows-sys 0.59.0", @@ -3774,7 +4122,7 @@ dependencies = [ "deno_tls", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls 0.27.7", "hyper-util", "log", "once_cell", @@ -3791,9 +4139,9 @@ dependencies = [ [[package]] name = "deno_terminal" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daef12499e89ee99e51ad6000a91f600d3937fb028ad4918af76810c5bc9e0d5" +checksum = "23f71c27009e0141dedd315f1dfa3ebb0a6ca4acce7c080fac576ea415a465f6" dependencies = [ "once_cell", "termcolor", @@ -3808,24 +4156,24 @@ dependencies = [ "deno_core", "deno_error", "deno_native_certs", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-pemfile 2.2.0", "rustls-tokio-stream", "rustls-webpki 0.102.8", "serde", "thiserror 2.0.12", "tokio", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "deno_unsync" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d774fd83f26b24f0805a6ab8b26834a0d06ceac0db517b769b1e4633c96a2057" +checksum = "6742a724e8becb372a74c650a1aefb8924a5b8107f7d75b3848763ea24b27a87" dependencies = [ - "futures", - "parking_lot", + "futures-util", + "parking_lot 0.12.4", "tokio", ] @@ -3859,7 +4207,7 @@ dependencies = [ "serde", "thiserror 2.0.12", "tokio", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -3900,8 +4248,8 @@ dependencies = [ "deno_permissions", "deno_tls", "fastwebsockets", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.10", + "http 1.3.1", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -3948,7 +4296,7 @@ dependencies = [ "num-bigint", "prost", "serde", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -3964,7 +4312,7 @@ dependencies = [ "deno_error", "denokv_proto", "futures", - "http 1.2.0", + "http 1.3.1", "log", "prost", "rand 0.8.5", @@ -3974,7 +4322,7 @@ dependencies = [ "tokio", "tokio-util", "url", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -3998,15 +4346,15 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tokio-stream", - "uuid 1.15.1", + "uuid", "v8_valueserializer", ] [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "der_derive", @@ -4036,19 +4384,30 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "derive_builder" version = "0.12.0" @@ -4082,15 +4441,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4143,6 +4502,15 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -4160,7 +4528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -4172,10 +4540,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.60.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -4183,7 +4563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -4195,7 +4575,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4224,21 +4604,15 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "document-features" version = "0.2.11" @@ -4292,11 +4666,35 @@ dependencies = [ "num-traits", "pkcs8", "rfc6979", - "sha2 0.10.8", + "sha2 0.10.9", "signature", "zeroize", ] +[[package]] +name = "duckdb" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb1ff45dea0ba559e9d25b768631f41a6061b0b76760deef09094659b691c8" +dependencies = [ + "arrow", + "cast", + "fallible-iterator 0.3.0", + "fallible-streaming-iterator", + "hashlink 0.9.1", + "libduckdb-sys", + "num-integer", + "rust_decimal", + "smallvec", + "strum 0.25.0", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.19" @@ -4319,6 +4717,15 @@ dependencies = [ "reborrow", ] +[[package]] +name = "dyn-stack" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd" +dependencies = [ + "bytemuck", +] + [[package]] name = "dynasm" version = "1.2.3" @@ -4370,9 +4777,9 @@ dependencies = [ [[package]] name = "ecow" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42fc0a93992b20c58b99e59d61eaf1635a25bfbe49e4275c34ba0aee98119ba" +checksum = "b92b481eb5d59fd8e80e92ff11d057d1ca8d144b2cd8c66cc8d5bd177a3c0dc5" dependencies = [ "serde", ] @@ -4397,7 +4804,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "signature", "subtle", "zeroize", @@ -4478,27 +4885,27 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "enumflags2" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4508,20 +4915,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.10" +name = "erased-serde" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "error_reporter" @@ -4584,6 +5001,28 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set 0.5.3", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set 0.8.0", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "fastdivide" version = "0.4.2" @@ -4599,15 +5038,6 @@ dependencies = [ "serde", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -4636,13 +5066,13 @@ dependencies = [ [[package]] name = "fd-lock" -version = "4.0.2" +version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix", - "windows-sys 0.52.0", + "rustix 1.0.7", + "windows-sys 0.59.0", ] [[package]] @@ -4656,9 +5086,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", "subtle", @@ -4688,12 +5118,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fixedbitset" version = "0.5.7" @@ -4702,23 +5126,24 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flatbuffers" -version = "24.12.23" +version = "25.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "rustc_version 0.4.1", ] [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", + "libz-rs-sys", "libz-sys", - "miniz_oxide 0.8.5", + "miniz_oxide 0.8.9", ] [[package]] @@ -4738,6 +5163,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", + "nanorand", "spin 0.9.8", ] @@ -4749,9 +5175,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -4780,7 +5206,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4818,7 +5244,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4838,10 +5264,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" dependencies = [ - "rustix", + "rustix 0.38.44", "windows-sys 0.52.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -4917,7 +5349,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.12.4", ] [[package]] @@ -4926,28 +5358,13 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ - "fastrand 2.3.0", + "fastrand", "futures-core", "futures-io", "parking", @@ -4962,7 +5379,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5026,17 +5443,37 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" dependencies = [ - "dyn-stack", - "gemm-c32", - "gemm-c64", - "gemm-common", - "gemm-f16", - "gemm-f32", - "gemm-f64", + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-c32 0.18.2", + "gemm-c64 0.18.2", + "gemm-common 0.18.2", + "gemm-f16 0.18.2", + "gemm-f32 0.18.2", + "gemm-f64 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -5046,12 +5483,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -5061,12 +5513,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -5077,17 +5544,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" dependencies = [ "bytemuck", - "dyn-stack", + "dyn-stack 0.10.0", "half", "num-complex", "num-traits", "once_cell", "paste", - "pulp", - "raw-cpuid", + "pulp 0.18.22", + "raw-cpuid 10.7.0", "rayon", "seq-macro", - "sysctl", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.0", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.21.5", + "raw-cpuid 11.5.0", + "rayon", + "seq-macro", + "sysctl 0.6.0", ] [[package]] @@ -5096,14 +5584,32 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" dependencies = [ - "dyn-stack", - "gemm-common", - "gemm-f32", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", "half", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "gemm-f32 0.18.2", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "rayon", "seq-macro", ] @@ -5114,12 +5620,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -5129,26 +5650,42 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows", + "windows 0.61.3", ] [[package]] @@ -5174,47 +5711,38 @@ dependencies = [ [[package]] name = "getopts" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" dependencies = [ - "unicode-width 0.1.14", + "unicode-width 0.2.1", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -5250,20 +5778,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", -] - -[[package]] -name = "git2" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", + "syn 2.0.104", ] [[package]] @@ -5329,6 +5844,94 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "google-cloud-auth" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57a13fbacc5e9c41ded3ad8d0373175a6b7a6ad430d99e89d314ac121b7ab06" +dependencies = [ + "async-trait", + "base64 0.21.7", + "google-cloud-metadata", + "google-cloud-token", + "home", + "jsonwebtoken 9.3.1", + "reqwest 0.12.20", + "serde", + "serde_json", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", + "urlencoding", +] + +[[package]] +name = "google-cloud-gax" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de13e62d7e0ffc3eb40a0113ddf753cf6ec741be739164442b08893db4f9bfca" +dependencies = [ + "google-cloud-token", + "http 1.3.1", + "thiserror 1.0.69", + "tokio", + "tokio-retry2", + "tonic", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "google-cloud-googleapis" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886aa8ec755382a1fdf4651f6e6ec01f2f3bf49f2cb0f068b9a74cafd574a715" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + +[[package]] +name = "google-cloud-metadata" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d901aeb453fd80e51d64df4ee005014f6cf39f2d736dd64f7239c132d9d39a6a" +dependencies = [ + "reqwest 0.12.20", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "google-cloud-pubsub" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebc6e7327e49a66ffb40508c673b7643191bd6b509530193bda97f09272cdcf" +dependencies = [ + "async-channel", + "async-stream", + "google-cloud-auth", + "google-cloud-gax", + "google-cloud-googleapis", + "google-cloud-token", + "prost-types", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "google-cloud-token" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c12ba8b21d128a2ce8585955246977fbce4415f680ebf9199b6f9d6d725f" +dependencies = [ + "async-trait", +] + [[package]] name = "gosyn" version = "0.2.9" @@ -5347,7 +5950,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-alloc-types", ] @@ -5357,18 +5960,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -5377,7 +5980,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -5412,7 +6015,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -5421,17 +6024,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", - "indexmap 2.7.1", + "http 1.3.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -5440,16 +6043,16 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", "crunchy", "num-traits", - "rand 0.8.5", - "rand_distr", + "rand 0.9.0", + "rand_distr 0.5.1", ] [[package]] @@ -5471,30 +6074,21 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.11", -] - [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -5509,7 +6103,7 @@ checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5527,7 +6121,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -5558,9 +6152,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -5576,19 +6170,26 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hf-hub" -version = "0.3.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b780635574b3d92f036890d8373433d6f9fc7abb320ee42a5c25897fc8ed732" +checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" dependencies = [ - "dirs 5.0.1", + "dirs 6.0.0", + "futures", + "http 1.3.1", "indicatif", + "libc", "log", "native-tls", - "rand 0.8.5", + "num_cpus", + "rand 0.9.0", + "reqwest 0.12.20", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", + "tokio", "ureq", + "windows-sys 0.60.2", ] [[package]] @@ -5629,7 +6230,7 @@ dependencies = [ "ipconfig", "moka", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "rand 0.9.0", "resolv-conf", "serde", @@ -5677,17 +6278,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "hstr" version = "0.2.17" @@ -5721,9 +6311,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -5748,43 +6338,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] -[[package]] -name = "http-types" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" -dependencies = [ - "anyhow", - "async-channel", - "base64 0.13.1", - "futures-lite 1.13.0", - "http 0.2.12", - "infer", - "pin-project-lite", - "rand 0.7.3", - "serde", - "serde_json", - "serde_qs 0.8.5", - "serde_urlencoded", - "url", -] - [[package]] name = "httparse" version = "1.10.1" @@ -5799,9 +6368,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "hyper" @@ -5836,8 +6405,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.10", + "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", @@ -5881,21 +6450,20 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", - "http 1.2.0", + "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", "tower-service", - "webpki-roots", + "webpki-roots 1.0.1", ] [[package]] @@ -5942,21 +6510,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -5976,16 +6551,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -5999,21 +6575,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", - "yoke", + "potential_utf", + "yoke 0.8.0", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -6022,31 +6599,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -6054,67 +6611,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", - "yoke", + "yoke 0.8.0", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -6134,9 +6678,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -6185,7 +6729,7 @@ checksum = "1215d4d92511fbbdaea50e750e91f2429598ef817f02b579158e92803b52c00a" dependencies = [ "boxed_error", "deno_error", - "indexmap 2.7.1", + "indexmap 2.9.0", "log", "percent-encoding", "serde", @@ -6207,12 +6751,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -6225,16 +6769,10 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "web-time", ] -[[package]] -name = "infer" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" - [[package]] name = "inotify" version = "0.9.6" @@ -6283,6 +6821,15 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -6310,6 +6857,16 @@ dependencies = [ "serde", ] +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-macro" version = "0.3.7" @@ -6319,9 +6876,15 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -6387,10 +6950,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -6427,6 +6991,21 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem 3.0.5", + "ring 0.17.14", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "junction" version = "1.2.0" @@ -6447,7 +7026,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.8", + "sha2 0.10.9", "signature", ] @@ -6466,7 +7045,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", ] [[package]] @@ -6476,7 +7055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.6", + "libloading 0.8.8", "pkg-config", ] @@ -6503,9 +7082,9 @@ checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -6547,7 +7126,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6573,9 +7152,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] name = "lexical-core" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958" dependencies = [ "lexical-parse-float", "lexical-parse-integer", @@ -6586,9 +7165,9 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -6597,9 +7176,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "0.8.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" dependencies = [ "lexical-util", "static_assertions", @@ -6607,18 +7186,18 @@ dependencies = [ [[package]] name = "lexical-util" -version = "0.8.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" dependencies = [ "static_assertions", ] [[package]] name = "lexical-write-float" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd" dependencies = [ "lexical-util", "lexical-write-integer", @@ -6627,9 +7206,9 @@ dependencies = [ [[package]] name = "lexical-write-integer" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978" dependencies = [ "lexical-util", "static_assertions", @@ -6637,9 +7216,25 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libduckdb-sys" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce15e3fe359068a9a3a687e074e7e32d2954f0a3dd138a0f529c779176b7e661" +dependencies = [ + "autocfg", + "cc", + "flate2", + "pkg-config", + "serde", + "serde_json", + "tar", + "vcpkg", +] [[package]] name = "libffi" @@ -6660,18 +7255,6 @@ dependencies = [ "cc", ] -[[package]] -name = "libgit2-sys" -version = "0.15.2+1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -6684,29 +7267,40 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libproc" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" +dependencies = [ + "bindgen 0.70.1", + "errno", + "libc", +] [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", ] [[package]] @@ -6732,10 +7326,29 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.21" +name = "libyml" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -6756,10 +7369,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "litemap" -version = "0.7.5" +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" @@ -6769,9 +7388,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -6779,9 +7398,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loki-api" @@ -6812,16 +7431,40 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", +] + +[[package]] +name = "lru" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" +dependencies = [ + "hashbrown 0.15.4", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lscolors" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53304fff6ab1e597661eee37e42ea8c47a146fca280af902bb76bff8a896e523" +dependencies = [ + "nu-ansi-term 0.50.1", ] [[package]] name = "lz4_flex" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" dependencies = [ - "twox-hash 1.6.3", + "twox-hash 2.1.1", ] [[package]] @@ -6836,10 +7479,19 @@ dependencies = [ ] [[package]] -name = "macro_rules_attribute" -version = "0.2.0" +name = "mach2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ "macro_rules_attribute-proc_macro", "paste", @@ -6847,9 +7499,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute-proc_macro" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" [[package]] name = "magic-crypt" @@ -6897,12 +7549,12 @@ dependencies = [ "base64 0.22.1", "gethostname", "mail-builder", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-pki-types", "smtp-proto", "tokio", "tokio-rustls 0.26.2", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] @@ -6978,12 +7630,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "204651f31b0a6a7b2128d2b92c372cd94607b210c3a6b6e542c57a8cfd4db996" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -7047,9 +7693,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -7091,7 +7737,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "core-graphics-types", "foreign-types 0.5.0", @@ -7100,6 +7746,34 @@ dependencies = [ "paste", ] +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "mime" version = "0.3.17" @@ -7143,9 +7817,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -7159,19 +7833,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -7184,13 +7858,13 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "loom", - "parking_lot", + "parking_lot 0.12.4", "portable-atomic", "rustc_version 0.4.1", "smallvec", "tagptr", "thiserror 1.0.69", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -7217,7 +7891,7 @@ checksum = "c402a4092d5e204f32c9e155431046831fa712637043c58cb73bc6bc6c9663b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7229,7 +7903,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.2.0", + "http 1.3.1", "httparse", "memchr", "mime", @@ -7239,9 +7913,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "murmurhash32" @@ -7251,42 +7925,41 @@ checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] name = "mysql-common-derive" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb6d9ff4094f6d58d3f892fc558e60048476213dd17dcf904b62202e9029da6" +checksum = "66f62cad7623a9cb6f8f64037f0c4f69c8db8e82914334a83c9788201c2c1bfa" dependencies = [ - "darling 0.20.10", + "darling 0.20.11", "heck 0.5.0", "num-bigint", "proc-macro-crate", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "termcolor", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "mysql_async" -version = "0.35.1" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14cf024116ba8fef4a7fec5abf0bd5de89b9fb29a7e55818a119ac5ec745077" +checksum = "277ce2f2459b2af4cc6d0a0b7892381f80800832f57c533f03e2845f4ea331ea" dependencies = [ "bytes", - "crossbeam", + "crossbeam-queue", "flate2", "futures-core", "futures-sink", "futures-util", "keyed_priority_queue", - "lru", + "lru 0.14.0", "mysql_common", "native-tls", "pem 3.0.5", "percent-encoding", - "pin-project", - "rand 0.8.5", + "rand 0.9.0", "serde", "serde_json", "socket2", @@ -7294,41 +7967,36 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-util", - "twox-hash 2.1.0", + "twox-hash 2.1.1", "url", ] [[package]] name = "mysql_common" -version = "0.34.1" +version = "0.35.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a9141e735d5bb02414a7ac03add09522466d4db65bdd827069f76ae0850e58" +checksum = "6e0ec195e788c95f36b7cf88127d538465fc2f7773e6e47af01834738eab0aee" dependencies = [ "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "btoi", "byteorder", "bytes", - "cc", - "cmake", "crc32fast", "flate2", - "lazy_static", + "getrandom 0.3.3", "mysql-common-derive", "num-bigint", "num-traits", - "rand 0.8.5", "regex", "rust_decimal", "saturating", "serde", "serde_json", "sha1", - "sha2 0.10.8", - "subprocess", - "thiserror 1.0.69", - "uuid 1.15.1", - "zstd", + "sha2 0.10.9", + "thiserror 2.0.12", + "uuid", ] [[package]] @@ -7338,11 +8006,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" dependencies = [ "arrayvec", - "bit-set", - "bitflags 2.9.0", + "bit-set 0.5.3", + "bitflags 2.9.1", "codespan-reporting", "hexf-parse", - "indexmap 2.7.1", + "indexmap 2.9.0", "log", "num-traits", "rustc-hash 1.1.0", @@ -7353,6 +8021,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "napi_sym" version = "0.120.0" @@ -7362,7 +8039,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7422,11 +8099,23 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "libc", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nkeys" version = "0.4.4" @@ -7436,7 +8125,7 @@ dependencies = [ "data-encoding", "ed25519", "ed25519-dalek", - "getrandom 0.2.15", + "getrandom 0.2.16", "log", "rand 0.8.5", "signatory", @@ -7451,7 +8140,7 @@ dependencies = [ "anyhow", "async-trait", "boxed_error", - "dashmap", + "dashmap 5.5.3", "deno_error", "deno_media_type", "deno_package_json", @@ -7494,7 +8183,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -7526,6 +8215,145 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "nu-derive-value" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71f7c8ed6ba88a567ec6f7c4cad4a7a8465ab93b8cdaf89d3dc72347a83c2d1f" +dependencies = [ + "heck 0.5.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "nu-engine" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6619583ed281060a9ea0a3f4532eea918370c94e703b903065f35e5aa49b14" +dependencies = [ + "log", + "nu-glob", + "nu-path", + "nu-protocol", + "nu-utils", + "terminal_size", +] + +[[package]] +name = "nu-glob" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd0a9fe69412acdc8501f5ef19031f9cac119d93823cb957b14ddfe1cb97660" + +[[package]] +name = "nu-parser" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adc2876bd7bc83be15786cedf2cb08a81a9d70fa4b8df569b3f1cbec1e0b58d" +dependencies = [ + "bytesize", + "chrono", + "itertools 0.13.0", + "log", + "nu-engine", + "nu-path", + "nu-protocol", + "nu-utils", + "serde_json", +] + +[[package]] +name = "nu-path" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ccd1bbaf370d79118bd1a807abb07d8d1386751d0ae9266baafa91bd0b5523f" +dependencies = [ + "dirs 5.0.1", + "omnipath", + "pwd", +] + +[[package]] +name = "nu-protocol" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f49a395b632530d7f46fd24183c7f42423677f70afb3cb4726e3abfe92273b" +dependencies = [ + "byte-unit", + "bytes", + "chrono", + "chrono-humanize", + "dirs 5.0.1", + "dirs-sys 0.4.1", + "fancy-regex 0.14.0", + "heck 0.5.0", + "indexmap 2.9.0", + "log", + "lru 0.12.5", + "miette", + "nix 0.29.0", + "nu-derive-value", + "nu-path", + "nu-system", + "nu-utils", + "num-format", + "serde", + "serde_json", + "thiserror 2.0.12", + "typetag", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-system" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81182f7e64bd5dd16ab844d8e40f78e389d06d95f5a0c419f4701fb8fc163077" +dependencies = [ + "chrono", + "itertools 0.13.0", + "libc", + "libproc", + "log", + "mach2", + "nix 0.29.0", + "ntapi", + "procfs", + "sysinfo", + "windows 0.56.0", +] + +[[package]] +name = "nu-utils" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d1468fa8e6e12d9d53c90b44f3d11a37d87502d7a30d145f122341c5b33745" +dependencies = [ + "crossterm_winapi", + "fancy-regex 0.14.0", + "log", + "lscolors", + "nix 0.29.0", + "num-format", + "serde", + "serde_json", + "strip-ansi-escapes", + "sys-locale", + "unicase", +] + [[package]] name = "nuid" version = "0.5.0" @@ -7595,6 +8423,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -7638,9 +8476,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -7648,23 +8486,24 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7681,14 +8520,14 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", - "getrandom 0.2.15", - "http 1.2.0", + "getrandom 0.2.16", + "http 1.3.1", "rand 0.8.5", - "reqwest 0.12.12", + "reqwest 0.12.20", "serde", "serde_json", "serde_path_to_error", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 1.0.69", "url", ] @@ -7713,32 +8552,38 @@ dependencies = [ [[package]] name = "object_store" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6da452820c715ce78221e8202ccc599b4a52f3e1eb3eedb487b680c81a8e3f3" +version = "0.12.0" +source = "git+https://github.com/apache/arrow-rs-object-store?rev=36752c975d4f29e20b57c91f81a10872dcd48ae7#36752c975d4f29e20b57c91f81a10872dcd48ae7" dependencies = [ "async-trait", "base64 0.22.1", "bytes", "chrono", + "form_urlencoded", "futures", + "http 1.3.1", + "http-body-util", + "httparse", "humantime", "hyper 1.6.0", - "itertools 0.13.0", + "itertools 0.14.0", "md-5 0.10.6", - "parking_lot", + "parking_lot 0.12.4", "percent-encoding", - "quick-xml 0.36.2", - "rand 0.8.5", - "reqwest 0.12.12", - "ring 0.17.12", + "quick-xml 0.37.5", + "rand 0.9.0", + "reqwest 0.12.20", + "ring 0.17.14", "serde", "serde_json", - "snafu", + "serde_urlencoded", + "thiserror 2.0.12", "tokio", "tracing", "url", "walkdir", + "wasm-bindgen-futures", + "web-time", ] [[package]] @@ -7760,10 +8605,22 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.20.3" +name = "omnipath" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "oneshot" @@ -7773,11 +8630,11 @@ checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -7785,9 +8642,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -7821,7 +8678,7 @@ dependencies = [ "dyn-clone", "ed25519-dalek", "hmac", - "http 1.2.0", + "http 1.3.1", "itertools 0.10.5", "log", "oauth2", @@ -7835,7 +8692,7 @@ dependencies = [ "serde_path_to_error", "serde_plain", "serde_with", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "thiserror 1.0.69", "url", @@ -7843,11 +8700,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types 0.3.2", "libc", @@ -7864,7 +8721,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7875,18 +8732,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.2+3.4.1" +version = "300.5.0+3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" +checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -7929,7 +8786,7 @@ checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80" dependencies = [ "async-trait", "bytes", - "http 1.2.0", + "http 1.3.1", "opentelemetry", ] @@ -7941,7 +8798,7 @@ checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" dependencies = [ "async-trait", "futures-core", - "http 1.2.0", + "http 1.3.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -8074,6 +8931,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + [[package]] name = "p224" version = "0.13.2" @@ -8083,7 +8946,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -8095,7 +8958,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -8107,7 +8970,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -8121,7 +8984,7 @@ dependencies = [ "elliptic-curve", "primeorder", "rand_core 0.6.4", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -8132,34 +8995,59 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.11", ] [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "parquet" -version = "52.2.0" +version = "55.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e977b9066b4d3b03555c22bdc442f3fadebd96a39111249113087d0edb2691cd" +checksum = "be7b2d778f6b841d37083ebdf32e33a524acde1266b5884a8ca29bf00dfa1231" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "arrow-array", "arrow-buffer", "arrow-cast", @@ -8168,25 +9056,25 @@ dependencies = [ "arrow-schema", "arrow-select", "base64 0.22.1", - "brotli 6.0.0", + "brotli 8.0.1", "bytes", "chrono", "flate2", "futures", "half", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "lz4_flex", "num", "num-bigint", "object_store", "paste", "seq-macro", + "simdutf8", "snap", "thrift", "tokio", - "twox-hash 1.6.3", + "twox-hash 2.1.1", "zstd", - "zstd-sys", ] [[package]] @@ -8265,30 +9153,32 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pep440_rs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c" +dependencies = [ + "once_cell", + "serde", + "unicode-width 0.2.1", + "unscanny", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset 0.4.2", - "indexmap 2.7.1", -] - [[package]] name = "petgraph" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "fixedbitset 0.5.7", - "indexmap 2.7.1", + "fixedbitset", + "indexmap 2.9.0", ] [[package]] @@ -8340,7 +9230,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8359,7 +9249,7 @@ source = "git+https://github.com/php-rust-tools/parser?rev=ec4cb411dec09450946ef dependencies = [ "ariadne", "clap", - "schemars", + "schemars 0.8.22", "serde", "serde_json", ] @@ -8381,7 +9271,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8418,7 +9308,7 @@ dependencies = [ "der", "pbkdf2", "scrypt", - "sha2 0.10.8", + "sha2 0.10.9", "spki", ] @@ -8450,7 +9340,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.5", + "miniz_oxide 0.8.9", ] [[package]] @@ -8467,9 +9357,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "postgres-native-tls" @@ -8507,7 +9397,7 @@ dependencies = [ "md-5 0.10.6", "memchr", "rand 0.8.5", - "sha2 0.10.8", + "sha2 0.10.9", "stringprep", ] @@ -8525,7 +9415,7 @@ dependencies = [ "md-5 0.10.6", "memchr", "rand 0.9.0", - "sha2 0.10.8", + "sha2 0.10.9", "stringprep", ] @@ -8546,14 +9436,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" dependencies = [ "array-init", - "bit-vec", + "bit-vec 0.6.3", "bytes", "chrono", "fallible-iterator 0.2.0", "postgres-protocol 0.6.8", "serde", "serde_json", - "uuid 1.15.1", + "uuid", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", ] [[package]] @@ -8564,11 +9463,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -8579,22 +9478,12 @@ checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "prettyplease" -version = "0.2.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" -dependencies = [ - "proc-macro2", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8612,7 +9501,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] @@ -8658,7 +9547,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8669,7 +9558,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8681,43 +9570,49 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] -name = "profiling" -version = "1.0.16" +name = "procfs" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" +dependencies = [ + "bitflags 2.9.1", + "chrono", + "flate2", + "hex", + "procfs-core", + "rustix 0.38.44", +] [[package]] -name = "progenitor" -version = "0.3.0" -source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" +name = "procfs-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "anyhow", - "built", - "clap", - "openapiv3", - "progenitor-client", - "progenitor-impl", - "progenitor-macro", - "project-root", - "rustfmt-wrapper", - "serde", - "serde_json", - "serde_yaml", + "bitflags 2.9.1", + "chrono", + "hex", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + [[package]] name = "progenitor-client" version = "0.3.0" @@ -8732,63 +9627,18 @@ dependencies = [ "serde_urlencoded", ] -[[package]] -name = "progenitor-impl" -version = "0.3.0" -source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" -dependencies = [ - "getopts", - "heck 0.4.1", - "http 0.2.12", - "indexmap 1.9.3", - "openapiv3", - "proc-macro2", - "quote", - "regex", - "schemars", - "serde", - "serde_json", - "syn 2.0.99", - "thiserror 1.0.69", - "typify", - "unicode-ident", -] - -[[package]] -name = "progenitor-macro" -version = "0.3.0" -source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" -dependencies = [ - "openapiv3", - "proc-macro2", - "progenitor-impl", - "quote", - "schemars", - "serde", - "serde_json", - "serde_tokenstream", - "serde_yaml", - "syn 2.0.99", -] - -[[package]] -name = "project-root" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" - [[package]] name = "prometheus" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" dependencies = [ "cfg-if", "fnv", "lazy_static", "memchr", - "parking_lot", - "thiserror 1.0.69", + "parking_lot 0.12.4", + "thiserror 2.0.12", ] [[package]] @@ -8812,12 +9662,12 @@ dependencies = [ "log", "multimap", "once_cell", - "petgraph 0.7.1", - "prettyplease 0.2.30", + "petgraph", + "prettyplease", "prost", "prost-types", "regex", - "syn 2.0.99", + "syn 2.0.104", "tempfile", ] @@ -8831,7 +9681,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8845,9 +9695,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" dependencies = [ "cc", ] @@ -8878,7 +9728,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "getopts", "memchr", "unicase", @@ -8897,10 +9747,34 @@ dependencies = [ ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "pulp" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] + +[[package]] +name = "pure-rust-locales" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" + +[[package]] +name = "pwd" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2" +dependencies = [ + "libc", + "thiserror 1.0.69", +] [[package]] name = "quick-xml" @@ -8914,9 +9788,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", "serde", @@ -8924,46 +9798,49 @@ dependencies = [ [[package]] name = "quick_cache" -version = "0.6.11" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af25b4e960ffdf0dead61cf0cec0c2e44c76927bf933ab4f02e2858fb449397" +checksum = "6b450dad8382b1b95061d5ca1eb792081fb082adf48c678791fe917509596d5f" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "equivalent", - "hashbrown 0.15.2", - "parking_lot", + "hashbrown 0.15.4", + "parking_lot 0.12.4", ] [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases 0.2.1", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.28", "socket2", "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring 0.17.12", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.0", + "ring 0.17.14", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -8974,9 +9851,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -8988,13 +9865,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -9011,19 +9894,6 @@ dependencies = [ "nibble_vec", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -9043,17 +9913,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.22", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "zerocopy", ] [[package]] @@ -9076,22 +9936,13 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -9100,7 +9951,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -9114,12 +9965,13 @@ dependencies = [ ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_distr" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ - "rand_core 0.5.1", + "num-traits", + "rand 0.9.0", ] [[package]] @@ -9137,6 +9989,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -9212,6 +10073,35 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -9223,11 +10113,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -9236,11 +10126,22 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -9258,7 +10159,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -9317,16 +10218,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "regress" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9ecfa0cb04d0b04dddb99b8ccf4f66bc8dfd23df694b398570bd8ae3a50fb" -dependencies = [ - "hashbrown 0.13.2", - "memchr", -] - [[package]] name = "rend" version = "0.4.2" @@ -9380,9 +10271,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "async-compression", "base64 0.22.1", @@ -9390,55 +10281,93 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.10", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls 0.27.7", "hyper-tls 0.6.0", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-native-certs 0.8.1", - "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", - "system-configuration 0.6.1", "tokio", "tokio-native-tls", "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", - "windows-registry", + "webpki-roots 1.0.1", +] + +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http 1.3.1", + "reqwest 0.12.20", + "serde", + "thiserror 1.0.69", + "tower-service", +] + +[[package]] +name = "reqwest-retry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom 0.2.16", + "http 1.3.1", + "hyper 1.6.0", + "parking_lot 0.11.2", + "reqwest 0.12.20", + "reqwest-middleware", + "retry-policies", + "thiserror 1.0.69", + "tokio", + "tracing", + "wasm-timer", ] [[package]] name = "resolv-conf" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" dependencies = [ - "hostname", - "quick-error", + "rand 0.8.5", ] [[package]] @@ -9451,12 +10380,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "riff" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" - [[package]] name = "ring" version = "0.16.20" @@ -9474,13 +10397,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.12" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9b823fa29b721a59671b41d6b06e66b29e0628e207e8b1c3ceeda701ec928d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted 0.9.0", "windows-sys 0.52.0", @@ -9510,7 +10433,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -9524,6 +10447,47 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rmcp" +version = "0.1.5" +source = "git+https://github.com/modelcontextprotocol/rust-sdk#b9d7d61ebd6e8385cbc4aa105d4e25774fc1a59c" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "futures", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "paste", + "pin-project-lite", + "rand 0.9.0", + "rmcp-macros", + "schemars 0.8.22", + "serde", + "serde_json", + "sse-stream", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "rmcp-macros" +version = "0.1.5" +source = "git+https://github.com/modelcontextprotocol/rust-sdk#b9d7d61ebd6e8385cbc4aa105d4e25774fc1a59c" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.104", +] + [[package]] name = "ron" version = "0.8.1" @@ -9531,16 +10495,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "serde_derive", ] [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest 0.10.7", @@ -9582,7 +10546,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink 0.9.1", @@ -9611,7 +10575,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.99", + "syn 2.0.104", "walkdir", ] @@ -9621,7 +10585,7 @@ version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" dependencies = [ - "sha2 0.10.8", + "sha2 0.10.9", "walkdir", ] @@ -9637,9 +10601,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.36.0" +version = "1.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" dependencies = [ "arrayvec", "borsh", @@ -9654,9 +10618,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -9688,19 +10652,6 @@ dependencies = [ "semver 1.0.26", ] -[[package]] -name = "rustfmt-wrapper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" -dependencies = [ - "serde", - "tempfile", - "thiserror 1.0.69", - "toml 0.8.20", - "toolchain_find", -] - [[package]] name = "rusticata-macros" version = "4.1.0" @@ -9716,10 +10667,23 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -9730,7 +10694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.12", + "ring 0.17.14", "rustls-webpki 0.101.7", "sct", ] @@ -9742,7 +10706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.12", + "ring 0.17.14", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -9751,15 +10715,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ + "aws-lc-rs", "log", "once_cell", - "ring 0.17.12", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.3", "subtle", "zeroize", ] @@ -9821,11 +10786,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] @@ -9835,7 +10801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33" dependencies = [ "futures", - "rustls 0.23.23", + "rustls 0.23.28", "socket2", "tokio", ] @@ -9846,7 +10812,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.12", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -9856,7 +10822,19 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.12", + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +dependencies = [ + "aws-lc-rs", + "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", ] @@ -9920,9 +10898,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rustyline" @@ -9930,7 +10908,7 @@ version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "clipboard-win", "fd-lock", @@ -9938,7 +10916,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.27.1", "radix_trie", "unicode-segmentation", "unicode-width 0.1.14", @@ -10011,7 +10989,7 @@ dependencies = [ "serde", "thiserror 1.0.69", "url", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -10049,7 +11027,18 @@ dependencies = [ "schemars_derive", "serde", "serde_json", - "uuid 1.15.1", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] @@ -10061,7 +11050,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -10085,7 +11074,7 @@ dependencies = [ "password-hash", "pbkdf2", "salsa20", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -10094,7 +11083,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.12", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -10125,7 +11114,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -10138,8 +11127,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -10169,9 +11158,6 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" -dependencies = [ - "serde", -] [[package]] name = "semver-parser" @@ -10187,18 +11173,18 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde-aux" -version = "4.6.0" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5290c39c5f6992b9dddbda28541d965dba46468294e6018a408fa297e6c602de" +checksum = "207f67b28fe90fb596503a9bf0bf1ea5e831e21307658e177c5dfcdfc3ab8a0a" dependencies = [ "chrono", "serde", @@ -10229,22 +11215,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.16" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -10255,7 +11241,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -10264,7 +11250,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -10299,28 +11285,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_qs" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "serde_qs" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 1.0.69", -] - [[package]] name = "serde_repr" version = "0.1.20" @@ -10329,30 +11293,18 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] -[[package]] -name = "serde_tokenstream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.99", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -10381,15 +11333,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.9.0", + "schemars 0.9.0", "serde", "serde_derive", "serde_json", @@ -10399,27 +11352,29 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ - "darling 0.20.10", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serde_yml" +version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "itoa", + "libyml", + "memchr", "ryu", "serde", - "unsafe-libyaml", + "version_check", ] [[package]] @@ -10458,9 +11413,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -10503,9 +11458,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -10513,9 +11468,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -10563,7 +11518,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2bcf6c6e164e81bc7a5d49fc6988b3d515d9e8c07457d7b74ffb9324b9cd40" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "halfbrown", "ref-cast", "serde", @@ -10602,6 +11557,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "size" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" + [[package]] name = "sketches-ddsketch" version = "0.2.2" @@ -10613,12 +11574,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slotmap" @@ -10640,24 +11598,13 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] -[[package]] -name = "smart-default" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "smartstring" version = "1.0.1" @@ -10669,43 +11616,12 @@ dependencies = [ "version_check", ] -[[package]] -name = "smol_str" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" -dependencies = [ - "serde", -] - [[package]] name = "smtp-proto" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7d3950ab75b03c52f2f13fd52aab91c9d62698b231b67240e85c3ef5301e63e" -[[package]] -name = "snafu" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" -dependencies = [ - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "snap" version = "1.1.1" @@ -10714,14 +11630,25 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "sourcemap" version = "8.0.1" @@ -10743,17 +11670,16 @@ dependencies = [ [[package]] name = "sourcemap" -version = "9.1.2" +version = "9.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c4ea7042fd1a155ad95335b5d505ab00d5124ea0332a06c8390d200bb1a76a" +checksum = "e22afbcb92ce02d23815b9795523c005cb9d3c214f8b7a66318541c240ea7935" dependencies = [ - "base64-simd 0.7.0", + "base64-simd 0.8.0", "bitvec", "data-encoding", "debugid", "if_chain", - "rustc-hash 1.1.0", - "rustc_version 0.2.3", + "rustc-hash 2.1.1", "serde", "serde_json", "unicode-id-start", @@ -10781,7 +11707,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -10824,30 +11750,31 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.47.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25" +checksum = "c4521174166bac1ff04fe16ef4524c70144cd29682a45978978ca3d7f4e0be11" dependencies = [ "log", + "recursive", "sqlparser_derive", ] [[package]] name = "sqlparser_derive" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "sqlx" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", @@ -10858,10 +11785,11 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ + "base64 0.22.1", "bigdecimal", "bytes", "chrono", @@ -10873,46 +11801,45 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "hashlink 0.10.0", - "indexmap 2.7.1", + "indexmap 2.9.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.23", - "rustls-pemfile 2.2.0", + "rustls 0.23.28", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "thiserror 2.0.12", "tokio", "tokio-stream", "tracing", "url", - "uuid 1.15.1", - "webpki-roots", + "uuid", + "webpki-roots 0.26.11", ] [[package]] name = "sqlx-macros" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "sqlx-macros-core" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", @@ -10923,27 +11850,26 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.99", - "tempfile", + "syn 2.0.104", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", @@ -10969,26 +11895,26 @@ dependencies = [ "rsa", "serde", "sha1", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.12", "tracing", - "uuid 1.15.1", + "uuid", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", @@ -11010,21 +11936,21 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.12", "tracing", - "uuid 1.15.1", + "uuid", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", @@ -11040,9 +11966,23 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", + "thiserror 2.0.12", "tracing", "url", - "uuid 1.15.1", + "uuid", +] + +[[package]] +name = "sse-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" +dependencies = [ + "bytes", + "futures-util", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", ] [[package]] @@ -11053,9 +11993,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9156ebd5870ef293bfb43f91c7a74528d363ec0d424afe24160ed5a4343d08a" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" dependencies = [ "cc", "cfg-if", @@ -11079,7 +12019,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11099,6 +12039,15 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.10.0" @@ -11122,11 +12071,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.26.3" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ - "strum_macros 0.26.4", + "strum_macros 0.27.1", ] [[package]] @@ -11139,30 +12088,20 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.99", -] - -[[package]] -name = "subprocess" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" -dependencies = [ - "libc", - "winapi", + "syn 2.0.104", ] [[package]] @@ -11171,6 +12110,27 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "swc_allocator" version = "0.1.10" @@ -11202,9 +12162,9 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83406221c501860fce9c27444f44125eafe9e598b8b81be7563d7036784cd05c" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "anyhow", - "dashmap", + "dashmap 5.5.3", "once_cell", "regex", "serde", @@ -11227,7 +12187,7 @@ dependencies = [ "rustc-hash 1.1.0", "serde", "siphasher 0.3.11", - "sourcemap 9.1.2", + "sourcemap 9.2.2", "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", @@ -11244,7 +12204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4740e53eaf68b101203c1df0937d5161a29f3c13bceed0836ddfe245b72dd000" dependencies = [ "anyhow", - "indexmap 2.7.1", + "indexmap 2.9.0", "serde", "serde_json", "swc_cached", @@ -11260,7 +12220,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11269,7 +12229,7 @@ version = "0.118.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6f866d12e4d519052b92a0a86d1ac7ff17570da1272ca0c89b3d6f802cd79df" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "is-macro", "num-bigint", "phf", @@ -11291,7 +12251,7 @@ dependencies = [ "num-bigint", "once_cell", "serde", - "sourcemap 9.1.2", + "sourcemap 9.2.2", "swc_allocator", "swc_atoms", "swc_common", @@ -11309,7 +12269,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11355,8 +12315,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65f21494e75d0bd8ef42010b47cabab9caaed8f2207570e809f6f4eb51a710d1" dependencies = [ "better_scoped_tls", - "bitflags 2.9.0", - "indexmap 2.7.1", + "bitflags 2.9.1", + "indexmap 2.9.0", "once_cell", "phf", "rustc-hash 1.1.0", @@ -11394,7 +12354,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11424,8 +12384,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76c76d8b9792ce51401d38da0fa62158d61f6d80d16d68fe5b03ce4bf5fba383" dependencies = [ "base64 0.21.7", - "dashmap", - "indexmap 2.7.1", + "dashmap 5.5.3", + "indexmap 2.9.0", "once_cell", "serde", "sha1", @@ -11465,7 +12425,7 @@ version = "0.134.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029eec7dd485923a75b5a45befd04510288870250270292fc2c1b3a9e7547408" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "num_cpus", "once_cell", "rustc-hash 1.1.0", @@ -11501,7 +12461,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11512,7 +12472,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11535,7 +12495,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11551,9 +12511,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -11589,13 +12549,22 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", ] [[package]] @@ -11614,7 +12583,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "enum-as-inner", "libc", @@ -11622,6 +12591,34 @@ dependencies = [ "walkdir", ] +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.9.1", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysinfo" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -11639,7 +12636,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys 0.6.0", ] @@ -11664,6 +12661,20 @@ dependencies = [ "libc", ] +[[package]] +name = "systemstat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668a4db78b439df482c238f559e4ea869017f9e62ef0a059c8bfcd841a4df544" +dependencies = [ + "bytesize", + "lazy_static", + "libc", + "nom 7.1.3", + "time", + "winapi", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -11692,7 +12703,7 @@ dependencies = [ "itertools 0.12.1", "levenshtein_automata", "log", - "lru", + "lru 0.12.5", "lz4_flex", "measure_time", "memmap2 0.9.5", @@ -11717,7 +12728,7 @@ dependencies = [ "tempfile", "thiserror 1.0.69", "time", - "uuid 1.15.1", + "uuid", "winapi", ] @@ -11798,7 +12809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" dependencies = [ "murmurhash32", - "rand_distr", + "rand_distr 0.4.3", "tantivy-common", ] @@ -11830,15 +12841,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", - "fastrand 2.3.0", - "getrandom 0.3.1", + "fastrand", + "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -11851,6 +12861,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix 1.0.7", + "windows-sys 0.59.0", +] + [[package]] name = "text_lines" version = "0.6.0" @@ -11860,6 +12880,16 @@ dependencies = [ "serde", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.1", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -11886,7 +12916,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -11897,17 +12927,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -11947,7 +12976,7 @@ dependencies = [ "tokio-rustls 0.24.1", "tokio-util", "tracing", - "uuid 1.15.1", + "uuid", ] [[package]] @@ -11994,9 +13023,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -12009,15 +13038,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -12034,9 +13063,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -12066,7 +13095,7 @@ dependencies = [ "bincode", "lazy_static", "rayon", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", "tracing", @@ -12082,7 +13111,7 @@ dependencies = [ "clap", "derive_builder", "esaxx-rs", - "getrandom 0.2.15", + "getrandom 0.2.16", "indicatif", "itertools 0.11.0", "lazy_static", @@ -12107,15 +13136,15 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.3", - "parking_lot", + "mio 1.0.4", + "parking_lot 0.12.4", "pin-project-lite", "signal-hook-registry", "socket2", @@ -12142,7 +13171,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -12179,7 +13208,7 @@ dependencies = [ "futures-channel", "futures-util", "log", - "parking_lot", + "parking_lot 0.12.4", "percent-encoding", "phf", "pin-project-lite", @@ -12205,7 +13234,7 @@ dependencies = [ "futures-channel", "futures-util", "log", - "parking_lot", + "parking_lot 0.12.4", "percent-encoding", "phf", "pin-project-lite", @@ -12218,6 +13247,16 @@ dependencies = [ "whoami", ] +[[package]] +name = "tokio-retry2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1264d076dd34560544a2799e40e457bd07c43d30f4a845686b031bcd8455c84f" +dependencies = [ + "pin-project", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -12245,7 +13284,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.23", + "rustls 0.23.28", "tokio", ] @@ -12303,16 +13342,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "futures-util", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -12328,10 +13367,10 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "http 1.2.0", + "http 1.3.1", "httparse", "rand 0.8.5", - "ring 0.17.12", + "ring 0.17.14", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -12351,23 +13390,11 @@ dependencies = [ "toml_edit 0.19.15", ] -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.24", -] - [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -12378,7 +13405,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -12387,15 +13414,13 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", + "indexmap 2.9.0", "toml_datetime", - "winnow 0.7.3", + "winnow 0.7.11", ] [[package]] @@ -12409,8 +13434,9 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.8", - "http 1.2.0", + "flate2", + "h2 0.4.10", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -12429,19 +13455,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", -] - -[[package]] -name = "toolchain_find" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" -dependencies = [ - "home", - "once_cell", - "regex", - "semver 1.0.26", - "walkdir", + "webpki-roots 0.26.11", ] [[package]] @@ -12490,8 +13504,8 @@ dependencies = [ "axum-core", "cookie 0.18.1", "futures-util", - "http 1.2.0", - "parking_lot", + "http 1.3.1", + "parking_lot 0.12.4", "pin-project-lite", "tower-layer", "tower-service", @@ -12499,20 +13513,23 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-core", - "http 1.2.0", + "futures-util", + "http 1.3.1", "http-body 1.0.1", "http-body-util", + "iri-string", "pin-project-lite", "tokio", "tokio-util", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -12556,20 +13573,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -12603,7 +13620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3beec919fbdf99d719de8eda6adae3281f8a5b71ae40431f44dc7423053d34" dependencies = [ "loki-api", - "reqwest 0.12.12", + "reqwest 0.12.20", "serde", "serde_json", "snap", @@ -12652,7 +13669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "regex", "serde", @@ -12688,6 +13705,16 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa6cbcdc8c679b214e616fd3300da67da0e492e066df01bcf5a5921a71e90d6" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "tree-sitter-language" version = "0.1.5" @@ -12712,11 +13739,10 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tryhard" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9f0a709784e86923586cff0d872dba54cd2d2e116b3bc57587d15737cfce9d" +checksum = "9fe58ebd5edd976e0fe0f8a14d2a04b7c81ef153ea9a54eebc42e67c2c23b4e5" dependencies = [ - "futures", "pin-project-lite", "tokio", ] @@ -12730,7 +13756,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", "native-tls", @@ -12753,9 +13779,9 @@ dependencies = [ [[package]] name = "twox-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" +checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" [[package]] name = "typed-arena" @@ -12763,6 +13789,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" @@ -12770,57 +13802,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] -name = "typify" -version = "0.0.12" +name = "typetag" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6658d09e71bfe59e7987dc95ee7f71809fdb5793ab0cdc1503cc0073990484d" +checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" dependencies = [ - "typify-impl", - "typify-macro", -] - -[[package]] -name = "typify-impl" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d3bb47587b13edf526d6ed02bf360ecefe083ab47a4ef29fc43112828b2bef" -dependencies = [ - "heck 0.4.1", - "log", - "proc-macro2", - "quote", - "regress", - "schemars", - "serde_json", - "syn 2.0.99", - "thiserror 1.0.69", - "unicode-ident", -] - -[[package]] -name = "typify-macro" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f7e627c18be12d53bc1f261830b9c2763437b6a86ac57293b9085af2d32ffe" -dependencies = [ - "proc-macro2", - "quote", - "schemars", + "erased-serde", + "inventory", + "once_cell", "serde", - "serde_json", - "serde_tokenstream", - "syn 2.0.99", - "typify-impl", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "ug" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b70b37e9074642bc5f60bb23247fd072a84314ca9e71cdf8527593406a0dd3" +dependencies = [ + "gemm 0.18.2", + "half", + "libloading 0.8.8", + "memmap2 0.9.5", + "num", + "num-traits", + "num_cpus", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", + "tracing", + "yoke 0.7.5", ] [[package]] name = "ulid" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab82fc73182c29b02e2926a6df32f2241dbadb5cfc111fd595515b3598f46bb3" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ "rand 0.9.0", - "uuid 1.15.1", + "uuid", "web-time", ] @@ -12918,6 +13951,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.24" @@ -12956,9 +13995,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -13005,10 +14044,10 @@ dependencies = [ ] [[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "unscanny" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" [[package]] name = "untrusted" @@ -13033,12 +14072,13 @@ dependencies = [ "log", "native-tls", "once_cell", - "rustls 0.23.23", + "rustls 0.23.28", "rustls-pki-types", "serde", "serde_json", + "socks", "url", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] @@ -13077,18 +14117,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8-ranges" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -13103,21 +14143,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "0.8.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "uuid" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" -dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -13127,7 +14160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a511192602f7b435b0a241c1947aa743eb7717f20a9195f4b5e8ed1952e01db1" dependencies = [ "bindgen 0.70.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "fslock", "gzip-header", "home", @@ -13143,9 +14176,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "encoding_rs", - "indexmap 2.7.1", + "indexmap 2.9.0", "num-bigint", "serde", "thiserror 1.0.69", @@ -13189,10 +14222,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] -name = "waker-fn" -version = "1.2.0" +name = "vte" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] [[package]] name = "walkdir" @@ -13215,21 +14251,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -13262,7 +14292,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -13297,7 +14327,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -13332,7 +14362,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -13348,6 +14378,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm_dep_analyzer" version = "0.2.0" @@ -13358,15 +14403,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "wav" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d97402f69875b579ec37f2aa52d1f455a1d6224251edba32e8c18a5da2698d" -dependencies = [ - "riff", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -13389,18 +14425,36 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.1", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.1", +] + +[[package]] +name = "webpki-roots" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -13412,16 +14466,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" dependencies = [ "arrayvec", - "bit-vec", - "bitflags 2.9.0", + "bit-vec 0.6.3", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "codespan-reporting", "document-features", - "indexmap 2.7.1", + "indexmap 2.9.0", "log", "naga", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "profiling", "raw-window-handle", "ron", @@ -13443,8 +14497,8 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", - "bitflags 2.9.0", + "bit-set 0.5.3", + "bitflags 2.9.1", "block", "cfg_aliases 0.1.1", "core-graphics-types", @@ -13456,14 +14510,14 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.6", + "libloading 0.8.8", "log", "metal", "naga", "ndk-sys", "objc", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "profiling", "range-alloc", "raw-window-handle", @@ -13482,7 +14536,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "js-sys", "serde", "web-sys", @@ -13497,7 +14551,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -13508,26 +14562,26 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix", + "rustix 0.38.44", "winsafe", ] [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "wasite", "web-sys", ] [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -13562,12 +14616,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "axum", "base64 0.22.1", "chrono", + "constant_time_eq", "deno_core", "dotenv", "futures", @@ -13577,21 +14632,29 @@ dependencies = [ "memchr", "object_store", "once_cell", + "pep440_rs", "prometheus", "quote", "rand 0.9.0", - "reqwest 0.12.12", + "reqwest 0.12.20", + "rustls 0.23.28", "serde", "serde_json", - "sha2 0.10.8", + "serde_yml", + "sha1", + "sha2 0.10.9", + "size", "sqlx", + "strum 0.27.1", + "systemstat", "tikv-jemalloc-ctl", "tikv-jemalloc-sys", "tikv-jemallocator", "tokio", + "tokio-stream", "tracing", "url", - "uuid 1.15.1", + "uuid", "v8", "windmill-api", "windmill-api-client", @@ -13605,7 +14668,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "argon2", @@ -13613,10 +14676,10 @@ dependencies = [ "async-oauth2", "async-recursion", "async-stream", - "async-stripe", "async_zip", "aws-config", "aws-sdk-sqs", + "aws-sdk-sts", "axum", "base32", "base64 0.22.1", @@ -13626,21 +14689,28 @@ dependencies = [ "candle-nn", "candle-transformers", "chrono", - "chrono-tz 0.10.1", + "chrono-tz", "const_format", + "constant_time_eq", "cookie 0.17.0", "cron", "datafusion", + "deno_core", + "deno_error", "futures", "git-version", + "google-cloud-googleapis", + "google-cloud-pubsub", "hex", "hf-hub", "hmac", - "http 1.2.0", + "http 1.3.1", "hyper 1.6.0", + "indexmap 2.9.0", "itertools 0.14.0", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "lazy_static", + "libxml", "magic-crypt", "mail-parser", "matchit", @@ -13658,7 +14728,8 @@ dependencies = [ "rand 0.9.0", "rdkafka", "regex", - "reqwest 0.12.12", + "reqwest 0.12.20", + "rmcp", "rsa", "rumqttc", "rust-embed", @@ -13667,7 +14738,9 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sha2 0.10.8", + "serde_yml", + "sha1", + "sha2 0.10.9", "sql-builder", "sqlx", "tempfile", @@ -13678,9 +14751,11 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-postgres 0.7.11", + "tokio-stream", "tokio-tar", "tokio-tungstenite", "tokio-util", + "tonic", "tower 0.5.2", "tower-cookies", "tower-http", @@ -13689,38 +14764,37 @@ dependencies = [ "ulid", "url", "urlencoding", - "uuid 1.15.1", + "uuid", "windmill-audit", "windmill-common", "windmill-git-sync", "windmill-indexer", "windmill-parser", "windmill-parser-py", + "windmill-parser-py-imports", "windmill-parser-ts", "windmill-queue", + "windmill-worker", ] [[package]] name = "windmill-api-client" -version = "1.475.0" +version = "1.501.4" dependencies = [ "base64 0.22.1", "chrono", "openapiv3", - "prettyplease 0.1.25", - "progenitor", "progenitor-client", "rand 0.9.0", "reqwest 0.11.27", "serde", "serde_json", - "syn 1.0.109", - "uuid 1.15.1", + "uuid", ] [[package]] name = "windmill-audit" -version = "1.475.0" +version = "1.501.4" dependencies = [ "chrono", "serde", @@ -13733,35 +14807,37 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "serde", "serde_json", "sqlx", "tracing", - "uuid 1.15.1", + "uuid", "windmill-common", "windmill-queue", ] [[package]] name = "windmill-common" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "async-stream", "aws-config", "aws-sdk-sts", + "aws-smithy-types-convert", "axum", "backon", "bytes", "chrono", - "chrono-tz 0.10.1", + "chrono-tz", "const_format", "crc", "cron", "croner", + "datafusion", "futures", "futures-core", "gethostname", @@ -13769,13 +14845,14 @@ dependencies = [ "hex", "hmac", "hyper 1.6.0", - "indexmap 2.7.1", + "indexmap 2.9.0", "itertools 0.14.0", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "lazy_static", "magic-crypt", "mail-send", "object_store", + "openidconnect", "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-otlp", @@ -13786,43 +14863,56 @@ dependencies = [ "quick_cache", "rand 0.9.0", "regex", - "reqwest 0.12.12", + "reqwest 0.12.20", + "reqwest-middleware", + "reqwest-retry", "semver 1.0.26", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", + "size", "sqlx", + "strum 0.27.1", + "strum_macros 0.27.1", + "systemstat", + "tar", "tempfile", "thiserror 2.0.12", "tikv-jemalloc-ctl", "tokio", + "tokio-stream", + "tokio-util", "tonic", "tracing", "tracing-appender", "tracing-loki", "tracing-opentelemetry", "tracing-subscriber", - "uuid 1.15.1", + "url", + "uuid", "windmill-macros", + "windmill-parser-py", + "windmill-parser-sql", + "windmill-parser-ts", ] [[package]] name = "windmill-git-sync" -version = "1.475.0" +version = "1.501.4" dependencies = [ "regex", "serde", "serde_json", "sqlx", "tracing", - "uuid 1.15.1", + "uuid", "windmill-common", "windmill-queue", ] [[package]] name = "windmill-indexer" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "bytes", @@ -13839,25 +14929,25 @@ dependencies = [ "tokio", "tokio-tar", "tracing", - "uuid 1.15.1", + "uuid", "windmill-common", ] [[package]] name = "windmill-macros" -version = "1.475.0" +version = "1.501.4" dependencies = [ "itertools 0.14.0", "lazy_static", "proc-macro2", "quote", "regex", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "windmill-parser" -version = "1.475.0" +version = "1.501.4" dependencies = [ "convert_case 0.6.0", "serde", @@ -13866,7 +14956,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "lazy_static", @@ -13878,7 +14968,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "serde_json", @@ -13890,7 +14980,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "gosyn", @@ -13902,7 +14992,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "lazy_static", @@ -13912,9 +15002,32 @@ dependencies = [ "windmill-parser", ] +[[package]] +name = "windmill-parser-java" +version = "1.501.4" +dependencies = [ + "anyhow", + "serde_json", + "tree-sitter", + "tree-sitter-java", + "wasm-bindgen", + "windmill-parser", +] + +[[package]] +name = "windmill-parser-nu" +version = "1.501.4" +dependencies = [ + "anyhow", + "nu-parser", + "serde_json", + "wasm-bindgen", + "windmill-parser", +] + [[package]] name = "windmill-parser-php" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "itertools 0.14.0", @@ -13925,7 +15038,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "itertools 0.14.0", @@ -13936,7 +15049,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "async-recursion", @@ -13944,19 +15057,22 @@ dependencies = [ "lazy_static", "malachite", "malachite-bigint", + "pep440_rs", "phf", "regex", "regex-lite", "rustpython-parser", + "serde", "serde_json", "sqlx", + "toml", "windmill-common", "windmill-parser", ] [[package]] name = "windmill-parser-rust" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -13966,14 +15082,14 @@ dependencies = [ "quote", "regex", "serde_json", - "syn 2.0.99", - "toml 0.7.8", + "syn 2.0.104", + "toml", "windmill-parser", ] [[package]] name = "windmill-parser-sql" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "lazy_static", @@ -13985,7 +15101,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "lazy_static", @@ -14003,10 +15119,10 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", - "getrandom 0.2.15", + "getrandom 0.2.16", "serde_json", "wasm-bindgen", "wasm-bindgen-test", @@ -14015,6 +15131,8 @@ dependencies = [ "windmill-parser-csharp", "windmill-parser-go", "windmill-parser-graphql", + "windmill-parser-java", + "windmill-parser-nu", "windmill-parser-php", "windmill-parser-py", "windmill-parser-rust", @@ -14025,7 +15143,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "serde_json", @@ -14035,14 +15153,14 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "async-recursion", "axum", "backon", "chrono", - "chrono-tz 0.10.1", + "chrono-tz", "cron", "futures", "futures-core", @@ -14052,7 +15170,7 @@ dependencies = [ "lazy_static", "prometheus", "regex", - "reqwest 0.12.12", + "reqwest 0.12.20", "serde", "serde_json", "serde_urlencoded", @@ -14061,14 +15179,14 @@ dependencies = [ "tokio", "tracing", "ulid", - "uuid 1.15.1", + "uuid", "windmill-audit", "windmill-common", ] [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.475.0" +version = "1.501.4" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -14078,13 +15196,14 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.475.0" +version = "1.501.4" dependencies = [ "anyhow", "async-recursion", + "async-stream", "backon", "base64 0.22.1", - "bit-vec", + "bit-vec 0.6.3", "bollard", "bytes", "chrono", @@ -14105,49 +15224,59 @@ dependencies = [ "deno_web", "deno_webidl", "dotenv", + "duckdb", "dyn-iter", + "flume", "futures", "gcp_auth", "git-version", "hex", "itertools 0.14.0", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "lazy_static", "mappable-rc", "mysql_async", "native-tls", - "nix", + "nix 0.27.1", "object_store", "once_cell", "opentelemetry", "oracle", "pem 3.0.5", + "pep440_rs", "postgres-native-tls 0.5.1", "prometheus", "rand 0.9.0", "regex", - "reqwest 0.12.12", + "reqwest 0.12.20", + "reqwest-middleware", "rust_decimal", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sqlx", "tar", "tiberius", "tokio", "tokio-postgres 0.7.13", + "tokio-stream", "tokio-util", "tracing", + "url", "urlencoding", - "uuid 1.15.1", + "uuid", + "winapi", "windmill-audit", "windmill-common", "windmill-git-sync", + "windmill-macros", "windmill-parser", "windmill-parser-bash", "windmill-parser-csharp", "windmill-parser-go", "windmill-parser-graphql", + "windmill-parser-java", + "windmill-parser-nu", "windmill-parser-php", "windmill-parser-py", "windmill-parser-py-imports", @@ -14161,86 +15290,212 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" dependencies = [ - "windows-core 0.58.0", + "windows-core 0.56.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement 0.56.0", + "windows-interface 0.56.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.52.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", "windows-strings", - "windows-targets 0.52.6", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", ] [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-result", + "windows-link", + "windows-result 0.3.4", "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -14270,6 +15525,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -14294,13 +15558,38 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -14313,6 +15602,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -14325,6 +15620,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -14337,12 +15638,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -14355,6 +15668,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -14367,6 +15686,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -14379,6 +15704,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -14391,6 +15722,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.5.40" @@ -14411,9 +15748,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -14436,24 +15773,18 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wtf8" @@ -14501,20 +15832,19 @@ dependencies = [ [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "rustix 1.0.7", ] [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmlparser" @@ -14554,7 +15884,19 @@ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.7.5", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.8.0", "zerofrom", ] @@ -14566,49 +15908,40 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", - "synstructure 0.13.1", + "syn 2.0.104", + "synstructure 0.13.2", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure 0.13.2", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09612fda0b63f7cb9e0af7e5916fe5a1f8cdcb066829f10f36883207628a4872" -dependencies = [ - "zerocopy-derive 0.8.22", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79f81d38d7a2ed52d8f034e62c568e111df9bf8aba2f7cf19ddc5bf7bd89d520" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -14628,8 +15961,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", - "synstructure 0.13.1", + "syn 2.0.104", + "synstructure 0.13.2", ] [[package]] @@ -14649,42 +15982,63 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke 0.8.0", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ - "yoke", + "yoke 0.8.0", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "zip" -version = "0.6.6" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" dependencies = [ - "byteorder", + "arbitrary", "crc32fast", "crossbeam-utils", + "displaydoc", + "indexmap 2.9.0", + "num_enum", + "thiserror 1.0.69", ] +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + [[package]] name = "zstd" version = "0.13.3" @@ -14696,18 +16050,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f5d1be0897..3aebd08d16 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.475.0" +version = "1.501.4" authors.workspace = true edition.workspace = true @@ -22,15 +22,17 @@ members = [ "./parsers/windmill-parser-go", "./parsers/windmill-parser-rust", "./parsers/windmill-parser-csharp", + "./parsers/windmill-parser-nu", + "./parsers/windmill-parser-java", "./parsers/windmill-parser-bash", "./parsers/windmill-parser-py", "./parsers/windmill-parser-py-imports", "./parsers/windmill-sql-datatype-parser-wasm", - "./parsers/windmill-parser-yaml", "windmill-macros", + "./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu", ] [workspace.package] -version = "1.475.0" +version = "1.501.4" authors = ["Ruben Fiszel "] edition = "2021" @@ -47,49 +49,64 @@ 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"] -stripe = ["windmill-api/stripe", "enterprise"] +stripe = ["windmill-api/stripe"] benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark", "windmill-common/benchmark"] loki = ["windmill-common/loki"] 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"] -cloud = ["windmill-queue/cloud", "windmill-worker/cloud"] +openidconnect = ["windmill-api/openidconnect", "windmill-common/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"] sqlx = ["windmill-worker/sqlx"] -deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"] +deno_core = ["windmill-worker/deno_core", "windmill-api/deno_core", "dep:deno_core", "dep:v8"] kafka = ["windmill-api/kafka"] nats = ["windmill-api/nats"] otel = ["windmill-common/otel", "windmill-worker/otel"] dind = ["windmill-worker/dind"] -php = ["windmill-worker/php"] -rust = ["windmill-worker/rust"] -mysql = ["windmill-worker/mysql"] -oracledb = ["windmill-worker/oracledb"] -mssql = ["windmill-worker/mssql"] -bigquery = ["windmill-worker/bigquery"] websocket = ["windmill-api/websocket"] +http_trigger = ["windmill-api/http_trigger"] postgres_trigger = ["windmill-api/postgres_trigger"] +mcp = ["windmill-api/mcp"] mqtt_trigger = ["windmill-api/mqtt_trigger"] -sqs_trigger = ["windmill-api/sqs_trigger"] -python = ["windmill-worker/python"] +sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"] +gcp_trigger = ["windmill-api/gcp_trigger"] smtp = ["windmill-api/smtp", "windmill-common/smtp"] -csharp = ["windmill-worker/csharp"] license = ["windmill-api/license"] oauth2 = ["windmill-api/oauth2"] -http_trigger = ["windmill-api/http_trigger"] zip = ["windmill-api/zip"] static_frontend = ["windmill-api/static_frontend"] scoped_cache = ["windmill-common/scoped_cache"] +# Languages +python = ["windmill-worker/python", "windmill-api/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"] +# For windows we have another set of languages enabled +# NOTE: DuckDB is ignored because of compilation problems +all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"] +[patch.crates-io] +object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" } [dependencies] anyhow.workspace = true tokio.workspace = true +tokio-stream.workspace = true dotenv.workspace = true windmill-queue.workspace = true windmill-common = { workspace = true, default-features = false } @@ -113,12 +130,20 @@ prometheus = { workspace = true, optional = true } uuid.workspace = true gethostname.workspace = true serde_json.workspace = true +serde_yml.workspace = true serde.workspace = true deno_core = { workspace = true, optional = true } object_store = { workspace = true, optional = true } +sha1 = { workspace = true, optional = true } +constant_time_eq = { workspace = true, optional = true } 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] @@ -136,7 +161,6 @@ windmill-api-client.workspace = true deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] } - [workspace.dependencies] windmill-api = { path = "./windmill-api", default-features = false } windmill-queue = { path = "./windmill-queue" } @@ -155,26 +179,34 @@ windmill-parser-go = { path = "./parsers/windmill-parser-go" } windmill-parser-rust = { path = "./parsers/windmill-parser-rust" } windmill-parser-yaml = { path = "./parsers/windmill-parser-yaml" } windmill-parser-csharp = { path = "./parsers/windmill-parser-csharp" } +windmill-parser-java = { path = "./parsers/windmill-parser-java" } +windmill-parser-nu = { path = "./parsers/windmill-parser-nu" } windmill-parser-bash = { path = "./parsers/windmill-parser-bash" } windmill-parser-sql = { path = "./parsers/windmill-parser-sql" } windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" } windmill-parser-php = { path = "./parsers/windmill-parser-php" } windmill-api-client = { path = "./windmill-api-client" } +reqwest-retry = "^0" +reqwest-middleware = { version = "^0", features = ["json"] } + +rustls = "0.23.0" memchr = "2.7.4" axum = { version = "^0.7", features = ["multipart"] } headers = "^0" hyper = { version = "^1", features = ["full"] } tokio = { version = "^1.42.0", features = ["full", "tracing", "time"] } +tokio-stream = { version = "0.1.17" } tower = "^0" tower-http = { version = "^0.6", features = ["trace", "cors"] } tower-cookies = "^0.10" serde = "^1" serde_json = { version = "^1", features = ["preserve_order", "raw_value"] } +serde_yml = "0.0.12" uuid = { version = "^1", features = ["serde", "v4"] } thiserror = "^2" anyhow = "^1" -chrono = { version = "=0.4.39", features = ["serde"] } +chrono = { version = "^0.4", features = ["serde"] } chrono-tz = "^0.10.1" tracing = "^0" tracing-subscriber = { version = "^0", features = ["env-filter", "json"] } @@ -195,11 +227,12 @@ 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 } urlencoding = "^2" -url = "^2" +url = { version = "^2" , features = ["serde"]} async-oauth2 = "^0" reqwest = { version = "^0.12", features = ["json", "stream", "gzip"] } time = "^0" @@ -211,8 +244,9 @@ json-pointer = "^0" itertools = "^0" regex = "^1" semver = "^1" +duckdb = { version = "1.2.2", features = ["bundled"] } -v8 = "=130.0.7" # Exact version +v8 = "=130.0.7" # Exact version NOTE: Do not forget to update version and hash in flake.nix deno_fetch = "0.214.0" deno_tls = "0.177.0" deno_console = "0.190.0" @@ -228,6 +262,12 @@ deno_runtime = { version = "0.198.0", features = ["transpile"] } deno_telemetry = "0.12.0" deno_error = "=0.5.5" +google-cloud-pubsub = "0.30.0" +google-cloud-googleapis = {version = "0.16.1", features = ["pubsub"]} +# TODO: remove once deno fixes the issue on their end +# https://github.com/denoland/deno/issues/28557 +winapi = { version = "0.3.9", features = ["sysinfoapi"] } + swc_common = "=0.37.5" swc_ecma_parser = "=0.149.1" swc_ecma_ast = "=0.118.2" @@ -235,11 +275,11 @@ swc_ecma_visit = "=0.104.8" async-recursion = "^1" - base64 = "^0" base32 = "^0" hmac = "0.12.1" sha2 = "0.10.6" +sha1 = "0.10.6" sqlx = { version = "0.8.0", features = [ "macros", "migrate", @@ -258,13 +298,9 @@ futures-core = "^0" lazy_static = "1.4.0" serde_derive = "1.0.147" const_format = { version = "0.2", features = ["rust_1_64", "rust_1_51"] } +constant_time_eq = "0.3.1" dyn-iter = "0.2.0" rsa = "^0" -async-stripe = { version = "0.39.1", features = [ - "runtime-tokio-hyper", - "checkout", - "billing", -] } async_zip = { version = "0.0.17", features = ["tokio", "tokio-fs", "deflate", "chrono"] } once_cell = "1.17.1" gosyn = "0.2.6" @@ -285,6 +321,7 @@ postgres-native-tls = "^0" native-tls = "^0" # samael will break compilation on MacOS. Use this fork instead to make it work # samael = { git="https://github.com/njaremko/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = ["xmlsec"] } +libxml = { version = "=0.3.3" } samael = { version="0.0.14", features = ["xmlsec"] } gcp_auth = "0.9.0" rust_decimal = { version = "^1", features = ["db-postgres", "serde-float"]} @@ -292,11 +329,11 @@ jsonwebtoken = "8.3.0" pem = "3.0.1" nix = { version = "0.27.1", features = ["process", "signal"] } tinyvector = { git = "https://github.com/windmill-labs/tinyvector", rev = "20823b94c20f2b9093f318badd24026cf54dcc85" } -hf-hub = "0.3.2" +hf-hub = "0.4.3" tokenizers = "0.14.1" -candle-core = "0.3.0" -candle-transformers = "0.3.0" -candle-nn = "0.3.0" +candle-core = "0.9.1" +candle-transformers = "0.9.1" +candle-nn = "0.9.1" tiberius = { version = "0.12.3", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]} pin-project = "1" indexmap = { version = "2.2.5", features = ["serde"]} @@ -308,14 +345,15 @@ rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored"] } pg_escape = "0.1.1" async-nats = "0.38.0" nkeys = "0.4.4" +nu-parser = { version = "0.101.0", default-features = false } -datafusion = "39.0.0" -object_store = { version = "0.10.0", features = ["aws", "azure"] } +datafusion = "47.0.0" +object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7", features = ["aws", "azure"] } 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" @@ -330,7 +368,7 @@ opentelemetry-semantic-conventions = { version = "0.27.0", features = ["semconv_ bollard = "0.18.1" -tonic = { version = "^0", features = ["tls-native-roots"] } +tonic = { version = "=0.12.3", features = ["tls-native-roots"] } byteorder = "1.5.0" tikv-jemallocator = { version = "0.5" } @@ -343,6 +381,10 @@ pin-project-lite = "^0" tantivy = "0.22.0" backon = "1.3.0" +systemstat = "0.2.4" +size = "0.5.0" + +flume = { version = "0.11.1", features = ["async"] } # Macro-related proc-macro2 = "1.0" @@ -353,7 +395,10 @@ quote = "1.0.36" regex-lite = "0.1.6" yaml-rust = "0.4.5" tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } -tree-sitter = {version = "0.23.0", features = []} +tree-sitter = { version = "0.23.0", features = [] } 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"]} \ No newline at end of file +rumqttc = { version = "0.24.0", features = ["use-native-tls"]} +strum = { version = "0.27", features = ["derive"] } +strum_macros = "^0" diff --git a/backend/all_features_oss.sh b/backend/all_features_oss.sh new file mode 100755 index 0000000000..5871bc656f --- /dev/null +++ b/backend/all_features_oss.sh @@ -0,0 +1,20 @@ +# 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 \ No newline at end of file diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 4c2e4d7211..3afeb766d1 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -d3e20c73b06a7c6820868769a49b5fa621591653 \ No newline at end of file +651b945d4567081968005278d7e87ea41cabbe1c diff --git a/backend/migrations/20240917172503_job_routing.down.sql b/backend/migrations/20240917172503_job_routing.down.sql index 569385aa5a..15c3e17762 100644 --- a/backend/migrations/20240917172503_job_routing.down.sql +++ b/backend/migrations/20240917172503_job_routing.down.sql @@ -4,4 +4,4 @@ DROP TYPE http_method; ALTER TABLE script DROP COLUMN has_preprocessor; -DROP FUNCTION prevent_route_path_change(); \ No newline at end of file +DROP FUNCTION prevent_route_path_change(); \ No newline at end of file diff --git a/backend/migrations/20241223155748_raw_apps_v2.down.sql b/backend/migrations/20241223155748_raw_apps_v2.down.sql new file mode 100644 index 0000000000..7e7dbecf84 --- /dev/null +++ b/backend/migrations/20241223155748_raw_apps_v2.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER TABLE app_version DROP COLUMN IF EXISTS raw_app; \ No newline at end of file diff --git a/backend/migrations/20241223155748_raw_apps_v2.up.sql b/backend/migrations/20241223155748_raw_apps_v2.up.sql new file mode 100644 index 0000000000..02b1124957 --- /dev/null +++ b/backend/migrations/20241223155748_raw_apps_v2.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE app_version ADD COLUMN IF NOT EXISTS raw_app BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/backend/migrations/20250122094704_add_nu_lang.down.sql b/backend/migrations/20250122094704_add_nu_lang.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250122094704_add_nu_lang.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250122094704_add_nu_lang.up.sql b/backend/migrations/20250122094704_add_nu_lang.up.sql new file mode 100644 index 0000000000..5317efa87f --- /dev/null +++ b/backend/migrations/20250122094704_add_nu_lang.up.sql @@ -0,0 +1,2 @@ +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'nu'; +UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["nu"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp"]}'::jsonb AND NOT config->'worker_tags' @> '"nu"'::jsonb; diff --git a/backend/migrations/20250205141517_backend_schema_validation.down.sql b/backend/migrations/20250205141517_backend_schema_validation.down.sql new file mode 100644 index 0000000000..0c2b95e5c6 --- /dev/null +++ b/backend/migrations/20250205141517_backend_schema_validation.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE script +DROP COLUMN schema_validation; diff --git a/backend/migrations/20250205141517_backend_schema_validation.up.sql b/backend/migrations/20250205141517_backend_schema_validation.up.sql new file mode 100644 index 0000000000..61546be0e8 --- /dev/null +++ b/backend/migrations/20250205141517_backend_schema_validation.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TABLE script +ADD COLUMN schema_validation BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/backend/migrations/20250304181111_add_java.down.sql b/backend/migrations/20250304181111_add_java.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250304181111_add_java.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250304181111_add_java.up.sql b/backend/migrations/20250304181111_add_java.up.sql new file mode 100644 index 0000000000..57256afe18 --- /dev/null +++ b/backend/migrations/20250304181111_add_java.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'java'; +UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["java"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp", "nu"]}'::jsonb AND NOT config->'worker_tags' @> '"java"'::jsonb; diff --git a/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.down.sql b/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.down.sql new file mode 100644 index 0000000000..937853bec6 --- /dev/null +++ b/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE http_trigger +DROP COLUMN workspaced_route; \ No newline at end of file diff --git a/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.up.sql b/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.up.sql new file mode 100644 index 0000000000..8bb8c8e2ff --- /dev/null +++ b/backend/migrations/20250309165536_add_workspaced_route_column_for_http_trigger.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TABLE http_trigger +ADD COLUMN workspaced_route BOOLEAN NOT NULL DEFAULT false; \ No newline at end of file diff --git a/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.down.sql b/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.down.sql new file mode 100644 index 0000000000..8e428e5290 --- /dev/null +++ b/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here +ALTER TABLE http_trigger +DROP COLUMN wrap_body, +DROP COLUMN raw_string; \ No newline at end of file diff --git a/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.up.sql b/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.up.sql new file mode 100644 index 0000000000..13b1f4bd58 --- /dev/null +++ b/backend/migrations/20250311162140_add_columns_to_handle_format_response_of_http_routes.up.sql @@ -0,0 +1,4 @@ +-- Add up migration script here +ALTER TABLE http_trigger +ADD COLUMN wrap_body BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN raw_string BOOLEAN NOT NULL DEFAULT false; \ No newline at end of file diff --git a/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.down.sql b/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.down.sql new file mode 100644 index 0000000000..27f65545ae --- /dev/null +++ b/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.down.sql @@ -0,0 +1,24 @@ +DROP VIEW flow_workspace_runnables; + +DELETE FROM workspace_runnable_dependencies WHERE flow_path IS NULL; + +ALTER TABLE workspace_runnable_dependencies +DROP CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey; + +ALTER TABLE workspace_runnable_dependencies +ADD CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey +FOREIGN KEY (flow_path, workspace_id) REFERENCES flow (path, workspace_id) +ON DELETE CASCADE; + +ALTER TABLE workspace_runnable_dependencies +DROP CONSTRAINT workspace_runnable_dependencies_path_exclusive; + +ALTER TABLE workspace_runnable_dependencies +DROP CONSTRAINT fk_workspace_runnable_dependencies_app_path; + +ALTER TABLE workspace_runnable_dependencies DROP COLUMN app_path; + +ALTER TABLE workspace_runnable_dependencies ALTER flow_path SET NOT NULL; + +ALTER TABLE workspace_runnable_dependencies +RENAME TO flow_workspace_runnables; diff --git a/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.up.sql b/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.up.sql new file mode 100644 index 0000000000..4b7b209bb5 --- /dev/null +++ b/backend/migrations/20250317145509_more_generic_workspace_runnable_usage.up.sql @@ -0,0 +1,42 @@ +-- flow_workspace_runnables only stored runnable usages by +-- flows although apps can also use runnables + +ALTER TABLE flow_workspace_runnables +RENAME TO workspace_runnable_dependencies; + +ALTER TABLE workspace_runnable_dependencies ALTER flow_path DROP NOT NULL; + +ALTER TABLE workspace_runnable_dependencies +ADD COLUMN app_path VARCHAR(255); + +ALTER TABLE workspace_runnable_dependencies +ADD CONSTRAINT workspace_runnable_dependencies_path_exclusive CHECK ( + (flow_path IS NOT NULL AND app_path IS NULL) OR + (flow_path IS NULL AND app_path IS NOT NULL) +); + +ALTER TABLE workspace_runnable_dependencies +ADD CONSTRAINT fk_workspace_runnable_dependencies_app_path +FOREIGN KEY (app_path, workspace_id) REFERENCES app (path, workspace_id) +ON DELETE CASCADE +ON UPDATE CASCADE; + + +ALTER TABLE workspace_runnable_dependencies +DROP CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey; + +ALTER TABLE workspace_runnable_dependencies +ADD CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey +FOREIGN KEY (flow_path, workspace_id) REFERENCES flow (path, workspace_id) +ON DELETE CASCADE +ON UPDATE CASCADE; + + +CREATE UNIQUE INDEX app_workspace_without_hash_unique_idx ON workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) WHERE script_hash IS NULL; +CREATE UNIQUE INDEX app_workspace_with_hash_unique_idx ON workspace_runnable_dependencies (app_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE script_hash IS NOT NULL; + + +-- This is to maintain compatibility with old workers +CREATE VIEW flow_workspace_runnables AS +SELECT flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id +FROM workspace_runnable_dependencies; \ No newline at end of file diff --git a/backend/migrations/20250318151955_workspace_git_app_settings.down.sql b/backend/migrations/20250318151955_workspace_git_app_settings.down.sql new file mode 100644 index 0000000000..97cc1fafb8 --- /dev/null +++ b/backend/migrations/20250318151955_workspace_git_app_settings.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_settings +DROP COLUMN IF EXISTS git_app_installations; diff --git a/backend/migrations/20250318151955_workspace_git_app_settings.up.sql b/backend/migrations/20250318151955_workspace_git_app_settings.up.sql new file mode 100644 index 0000000000..0e0fc97fb6 --- /dev/null +++ b/backend/migrations/20250318151955_workspace_git_app_settings.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_settings +ADD COLUMN IF NOT EXISTS git_app_installations JSONB NOT NULL DEFAULT '[]'; \ No newline at end of file diff --git a/backend/migrations/20250319121050_multiple_ai_providers.down.sql b/backend/migrations/20250319121050_multiple_ai_providers.down.sql new file mode 100644 index 0000000000..b6c6ff23bc --- /dev/null +++ b/backend/migrations/20250319121050_multiple_ai_providers.down.sql @@ -0,0 +1,24 @@ +ALTER TABLE workspace_settings RENAME COLUMN ai_config TO ai_resource; + +ALTER TABLE workspace_settings +ADD COLUMN ai_models VARCHAR(255)[] NOT NULL DEFAULT '{}', +ADD COLUMN code_completion_model VARCHAR(255); + +UPDATE workspace_settings +SET ai_resource = CASE + WHEN ai_resource IS NULL THEN NULL + ELSE jsonb_build_object( + 'provider', + COALESCE((SELECT jsonb_object_keys(COALESCE(ai_resource->>'providers', '{}')::jsonb) LIMIT 1), 'openai'), -- Get the first provider key + 'path', + ai_resource->'providers'->(SELECT jsonb_object_keys(COALESCE(ai_resource->>'providers', '{}')::jsonb) LIMIT 1)->>'resource_path' + ) +END, +ai_models = COALESCE(( + SELECT array_agg(model) + FROM jsonb_array_elements_text( + COALESCE(ai_resource->'providers'->(SELECT jsonb_object_keys(COALESCE(ai_resource->>'providers', '{}')::jsonb) LIMIT 1)->>'models', '[]')::jsonb + ) model + WHERE model IS NOT NULL +), '{}'), +code_completion_model = ai_resource->'code_completion_model'->>'model'; diff --git a/backend/migrations/20250319121050_multiple_ai_providers.up.sql b/backend/migrations/20250319121050_multiple_ai_providers.up.sql new file mode 100644 index 0000000000..40353f7501 --- /dev/null +++ b/backend/migrations/20250319121050_multiple_ai_providers.up.sql @@ -0,0 +1,38 @@ +UPDATE workspace_settings +SET ai_resource = CASE + WHEN ai_resource IS NULL OR ai_resource->>'path' IS NULL OR ai_resource->>'provider' IS NULL THEN NULL + ELSE jsonb_build_object( + 'providers', jsonb_build_object( + ai_resource->>'provider', + jsonb_build_object( + 'resource_path', ai_resource->>'path', + 'models', to_jsonb(ai_models) + ) + ), + 'default_model', + CASE + WHEN array_length(ai_models, 1) > 0 THEN jsonb_build_object( + 'model', ai_models[1], + 'provider', ai_resource->>'provider' + ) + ELSE NULL + END, + 'code_completion_model', + CASE + WHEN code_completion_model IS NULL THEN NULL + ELSE jsonb_build_object( + 'model', code_completion_model, + 'provider', ai_resource->>'provider' + ) + END + ) +END; + +ALTER TABLE workspace_settings +DROP COLUMN code_completion_model, +DROP COLUMN ai_models; + +ALTER TABLE workspace_settings RENAME COLUMN ai_resource TO ai_config; + + +-- { providers: { [provider]: { resource_path: resource_path, models: ai_models}, default_model: ai_models[0], code_completion_model: code_completion_model} diff --git a/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.down.sql b/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.down.sql new file mode 100644 index 0000000000..f34e8407b8 --- /dev/null +++ b/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.down.sql @@ -0,0 +1,15 @@ +ALTER TABLE http_trigger + DROP COLUMN authentication_resource_path, + ALTER COLUMN authentication_method DROP DEFAULT, + ALTER COLUMN authentication_method TYPE boolean + USING CASE + WHEN authentication_method = 'windmill'::AUTHENTICATION_METHOD THEN true + ELSE false + END, + ALTER COLUMN authentication_method SET NOT NULL, + ALTER COLUMN authentication_method SET DEFAULT false; + +ALTER TABLE http_trigger + RENAME COLUMN authentication_method TO requires_auth; + +DROP TYPE AUTHENTICATION_METHOD; \ No newline at end of file diff --git a/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.up.sql b/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.up.sql new file mode 100644 index 0000000000..713e1b1fe3 --- /dev/null +++ b/backend/migrations/20250319234432_rename_require_auth_column_of_http_trigger.up.sql @@ -0,0 +1,22 @@ +CREATE TYPE AUTHENTICATION_METHOD AS ENUM ( + 'none', + 'windmill', + 'api_key', + 'basic_http', + 'custom_script', + 'signature' +); + +ALTER TABLE http_trigger + RENAME COLUMN requires_auth TO authentication_method; + +ALTER TABLE http_trigger + ADD COLUMN authentication_resource_path VARCHAR(255) DEFAULT NULL, + ALTER COLUMN authentication_method DROP DEFAULT, + ALTER COLUMN authentication_method TYPE AUTHENTICATION_METHOD + USING CASE + WHEN authentication_method = true THEN 'windmill'::AUTHENTICATION_METHOD + ELSE 'none'::AUTHENTICATION_METHOD + END, + ALTER COLUMN authentication_method SET NOT NULL, + ALTER COLUMN authentication_method SET DEFAULT 'none'::AUTHENTICATION_METHOD; diff --git a/backend/migrations/20250320081915_longer_variable_description.down.sql b/backend/migrations/20250320081915_longer_variable_description.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250320081915_longer_variable_description.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250320081915_longer_variable_description.up.sql b/backend/migrations/20250320081915_longer_variable_description.up.sql new file mode 100644 index 0000000000..d7c5ba06f5 --- /dev/null +++ b/backend/migrations/20250320081915_longer_variable_description.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TABLE variable +ALTER COLUMN description TYPE VARCHAR(10000); \ No newline at end of file diff --git a/backend/migrations/20250322171903_workspace_envs_cache.down.sql b/backend/migrations/20250322171903_workspace_envs_cache.down.sql new file mode 100644 index 0000000000..68a678236d --- /dev/null +++ b/backend/migrations/20250322171903_workspace_envs_cache.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +DROP TRIGGER workspace_envs_change_trigger ON workspace_env; +DROP FUNCTION notify_workspace_envs_change(); diff --git a/backend/migrations/20250322171903_workspace_envs_cache.up.sql b/backend/migrations/20250322171903_workspace_envs_cache.up.sql new file mode 100644 index 0000000000..439a4dd106 --- /dev/null +++ b/backend/migrations/20250322171903_workspace_envs_cache.up.sql @@ -0,0 +1,15 @@ +-- Add up migration script here +-- Add up migration script here + +CREATE OR REPLACE FUNCTION notify_workspace_envs_change() +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('notify_workspace_envs_change', NEW.workspace_id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER workspace_envs_change_trigger +AFTER INSERT OR UPDATE OF name, value OR DELETE ON workspace_env +FOR EACH ROW +EXECUTE FUNCTION notify_workspace_envs_change(); diff --git a/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.down.sql b/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.down.sql new file mode 100644 index 0000000000..8384b48725 --- /dev/null +++ b/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.down.sql @@ -0,0 +1,6 @@ +-- Add down migration script here +ALTER TABLE schedule + DROP COLUMN description, + ALTER COLUMN on_failure_extra_args SET DATA TYPE json USING on_failure_extra_args::json, + ALTER COLUMN on_success_extra_args SET DATA TYPE json USING on_success_extra_args::json, + ALTER COLUMN on_recovery_extra_args SET DATA TYPE json USING on_recovery_extra_args::json; diff --git a/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.up.sql b/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.up.sql new file mode 100644 index 0000000000..cd50e7a2db --- /dev/null +++ b/backend/migrations/20250323131258_add-description-and-cast-extra-args-to-jsonb.up.sql @@ -0,0 +1,6 @@ +-- Add up migration script here +ALTER TABLE schedule + ADD COLUMN description TEXT NULL, + ALTER COLUMN on_failure_extra_args SET DATA TYPE jsonb USING on_failure_extra_args::jsonb, + ALTER COLUMN on_success_extra_args SET DATA TYPE jsonb USING on_success_extra_args::jsonb, + ALTER COLUMN on_recovery_extra_args SET DATA TYPE jsonb USING on_recovery_extra_args::jsonb; diff --git a/backend/migrations/20250323135044_add-gcp-trigger-table.down.sql b/backend/migrations/20250323135044_add-gcp-trigger-table.down.sql new file mode 100644 index 0000000000..94e32f5962 --- /dev/null +++ b/backend/migrations/20250323135044_add-gcp-trigger-table.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +DROP TABLE gcp_trigger; +DROP TYPE DELIVERY_MODE; \ No newline at end of file diff --git a/backend/migrations/20250323135044_add-gcp-trigger-table.up.sql b/backend/migrations/20250323135044_add-gcp-trigger-table.up.sql new file mode 100644 index 0000000000..2098d27e69 --- /dev/null +++ b/backend/migrations/20250323135044_add-gcp-trigger-table.up.sql @@ -0,0 +1,80 @@ +-- Add up migration script here + +ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'gcp'; +CREATE TYPE DELIVERY_MODE AS ENUM ('push', 'pull'); + +CREATE TABLE gcp_trigger ( + gcp_resource_path VARCHAR(255) NOT NULL, + topic_id VARCHAR(255) NOT NULL CHECK ( + CHAR_LENGTH(topic_id) BETWEEN 3 AND 255 + ), + subscription_id VARCHAR(255) NOT NULL CHECK ( + CHAR_LENGTH(subscription_id) BETWEEN 3 AND 255 + ), + delivery_type DELIVERY_MODE NOT NULL, + delivery_config JSONB NULL CHECK (delivery_type != 'push'::DELIVERY_MODE OR (delivery_config IS NOT NULL)), + path VARCHAR(255) NOT NULL, + script_path VARCHAR(255) NOT NULL, + is_flow BOOLEAN NOT NULL, + workspace_id VARCHAR(50) NOT NULL, + edited_by VARCHAR(50) NOT NULL, + email VARCHAR(255) NOT NULL, + edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + extra_perms JSONB NOT NULL DEFAULT '{}', + server_id VARCHAR(50), + last_server_ping TIMESTAMPTZ, + error TEXT, + enabled BOOLEAN NOT NULL, + PRIMARY KEY (path, workspace_id) +); + +CREATE UNIQUE INDEX unique_subscription_per_gcp_resource +ON gcp_trigger (subscription_id, gcp_resource_path, workspace_id); + +GRANT ALL ON gcp_trigger TO windmill_user; +GRANT ALL ON gcp_trigger TO windmill_admin; + +ALTER TABLE gcp_trigger ENABLE ROW LEVEL SECURITY; + +CREATE POLICY admin_policy ON gcp_trigger FOR ALL TO windmill_admin USING (true); + +CREATE POLICY see_folder_extra_perms_user_select ON gcp_trigger FOR SELECT TO windmill_user +USING (SPLIT_PART(gcp_trigger.path, '/', 1) = 'f' AND SPLIT_PART(gcp_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_insert ON gcp_trigger FOR INSERT TO windmill_user +WITH CHECK (SPLIT_PART(gcp_trigger.path, '/', 1) = 'f' AND SPLIT_PART(gcp_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_update ON gcp_trigger FOR UPDATE TO windmill_user +USING (SPLIT_PART(gcp_trigger.path, '/', 1) = 'f' AND SPLIT_PART(gcp_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_delete ON gcp_trigger FOR DELETE TO windmill_user +USING (SPLIT_PART(gcp_trigger.path, '/', 1) = 'f' AND SPLIT_PART(gcp_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); + +CREATE POLICY see_own ON gcp_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(gcp_trigger.path, '/', 1) = 'u' AND SPLIT_PART(gcp_trigger.path, '/', 2) = current_setting('session.user')); +CREATE POLICY see_member ON gcp_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(gcp_trigger.path, '/', 1) = 'g' AND SPLIT_PART(gcp_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[])); + +CREATE POLICY see_extra_perms_user_select ON gcp_trigger FOR SELECT TO windmill_user +USING (extra_perms ? CONCAT('u/', current_setting('session.user'))); +CREATE POLICY see_extra_perms_user_insert ON gcp_trigger FOR INSERT TO windmill_user +WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_update ON gcp_trigger FOR UPDATE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_delete ON gcp_trigger FOR DELETE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); + +CREATE POLICY see_extra_perms_groups_select ON gcp_trigger FOR SELECT TO windmill_user +USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]); +CREATE POLICY see_extra_perms_groups_insert ON gcp_trigger FOR INSERT TO windmill_user +WITH CHECK (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_update ON gcp_trigger FOR UPDATE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_delete ON gcp_trigger FOR DELETE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); \ No newline at end of file diff --git a/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.down.sql b/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.up.sql b/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.up.sql new file mode 100644 index 0000000000..cdf8530b35 --- /dev/null +++ b/backend/migrations/20250323162033_add-missing-trigger-kind-to-job-trigger-kind-type.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TYPE job_trigger_kind ADD VALUE IF NOT EXISTS 'sqs'; +ALTER TYPE job_trigger_kind ADD VALUE IF NOT EXISTS 'gcp'; diff --git a/backend/migrations/20250325003851_workspace_premium_listener.down.sql b/backend/migrations/20250325003851_workspace_premium_listener.down.sql new file mode 100644 index 0000000000..5515b4226d --- /dev/null +++ b/backend/migrations/20250325003851_workspace_premium_listener.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +DROP TRIGGER workspace_premium_change_trigger ON workspace; +DROP FUNCTION notify_workspace_premium_change(); diff --git a/backend/migrations/20250325003851_workspace_premium_listener.up.sql b/backend/migrations/20250325003851_workspace_premium_listener.up.sql new file mode 100644 index 0000000000..e841dfbe16 --- /dev/null +++ b/backend/migrations/20250325003851_workspace_premium_listener.up.sql @@ -0,0 +1,13 @@ +-- Add up migration script here +CREATE OR REPLACE FUNCTION notify_workspace_premium_change() +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('notify_workspace_premium_change', NEW.id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER workspace_premium_change_trigger +AFTER UPDATE OF premium ON workspace +FOR EACH ROW +EXECUTE FUNCTION notify_workspace_premium_change(); diff --git a/backend/migrations/20250326105126_remove_automatic_billing_col.down.sql b/backend/migrations/20250326105126_remove_automatic_billing_col.down.sql new file mode 100644 index 0000000000..b49aa205d9 --- /dev/null +++ b/backend/migrations/20250326105126_remove_automatic_billing_col.down.sql @@ -0,0 +1 @@ +ALTER TABLE workspace_settings ADD COLUMN automatic_billing BOOLEAN NOT NULL DEFAULT TRUE; \ No newline at end of file diff --git a/backend/migrations/20250326105126_remove_automatic_billing_col.up.sql b/backend/migrations/20250326105126_remove_automatic_billing_col.up.sql new file mode 100644 index 0000000000..776f1da82f --- /dev/null +++ b/backend/migrations/20250326105126_remove_automatic_billing_col.up.sql @@ -0,0 +1 @@ +ALTER TABLE workspace_settings DROP COLUMN automatic_billing; \ No newline at end of file diff --git a/backend/migrations/20250407124204_update_hub_sync_script.down.sql b/backend/migrations/20250407124204_update_hub_sync_script.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250407124204_update_hub_sync_script.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250407124204_update_hub_sync_script.up.sql b/backend/migrations/20250407124204_update_hub_sync_script.up.sql new file mode 100644 index 0000000000..84e50b36c0 --- /dev/null +++ b/backend/migrations/20250407124204_update_hub_sync_script.up.sql @@ -0,0 +1,284 @@ +-- Add up migration script here +-- Add up migration script here +UPDATE script SET content = 'import * as wmill from "windmill-cli@1.481.0" + +export async function main() { + await wmill.hubPull({ workspace: "admins", token: process.env["WM_TOKEN"], baseUrl: globalThis.process.env["BASE_URL"] }); +} +', language = 'bun', +lock = '{ + "dependencies": { + "windmill-cli": "1.481.0" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "windmill-cli": "1.481.0", + }, + }, + }, + "packages": { + "@ayonli/jsext": ["@ayonli/jsext@1.6.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "sudo-prompt": "^9.2.1", "ws": "^8.17.0", "zod": "^3.23.8" } }, "sha512-dMQuZJIVadEgQ6xp1Q5hRv2JfwANDwnElH6kZWuSjcnAjhvtCoQQ02CdOipfCd4cyrFpaI+8yBboiT9OkRaVyg=="], + + "@deno/shim-deno": ["@deno/shim-deno@0.18.2", "", { "dependencies": { "@deno/shim-deno-test": "^0.5.0", "which": "^4.0.0" } }, "sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA=="], + + "@deno/shim-deno-test": ["@deno/shim-deno-test@0.5.0", "", {}, "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="], + + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + + "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="], + + "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-main": ["es-main@1.3.0", "", {}, "sha512-AzORKdz1Zt97TzbYQnIrI3ZiibWpRXUfpo/w0xOJ20GpNYd2bd3MU9m31zS/aJ1TJl6JfLTok83Y8HjNunYT0A=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], + + "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-port": ["get-port@7.1.0", "", {}, "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "jszip": ["jszip@3.7.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "set-immediate-shim": "~1.0.1" } }, "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg=="], + + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + + "minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "open": ["open@10.1.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw=="], + + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], + + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "run-applescript": ["run-applescript@7.0.0", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], + + "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], + + "set-immediate-shim": ["set-immediate-shim@1.0.1", "", {}, "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "sudo-prompt": ["sudo-prompt@9.2.1", "", {}, "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "windmill-cli": ["windmill-cli@1.481.0", "", { "dependencies": { "@ayonli/jsext": "*", "@deno/shim-deno": "~0.18.0", "diff": "*", "es-main": "*", "esbuild": "*", "express": "*", "get-port": "7.1.0", "jszip": "3.7.1", "minimatch": "*", "open": "*", "ws": "*" }, "bin": { "wmill": "esm/main.js" } }, "sha512-nIIrt+/+TqeyHlgcDnPMTBH3CZX4TMVwxy2UFD+5lOI5OY9JOtCpWk5UPn6BOs6fw1DG0MIBJG7AhCuPelfSiQ=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], + + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + + "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + } +}' +WHERE hash = -28028598712388162 AND workspace_id = 'admins'; \ No newline at end of file diff --git a/backend/migrations/20250409093642_add_grant.down.sql b/backend/migrations/20250409093642_add_grant.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250409093642_add_grant.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250409093642_add_grant.up.sql b/backend/migrations/20250409093642_add_grant.up.sql new file mode 100644 index 0000000000..95bae6b21c --- /dev/null +++ b/backend/migrations/20250409093642_add_grant.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +GRANT ALL on workspace_runnable_dependencies TO windmill_user; +GRANT ALL on workspace_runnable_dependencies TO windmill_admin; \ No newline at end of file diff --git a/backend/migrations/20250412144540_improve_perf_api_role.down.sql b/backend/migrations/20250412144540_improve_perf_api_role.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250412144540_improve_perf_api_role.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250412144540_improve_perf_api_role.up.sql b/backend/migrations/20250412144540_improve_perf_api_role.up.sql new file mode 100644 index 0000000000..8a521849fb --- /dev/null +++ b/backend/migrations/20250412144540_improve_perf_api_role.up.sql @@ -0,0 +1,22 @@ +-- Add up migration script here + CREATE OR REPLACE FUNCTION set_session_context( + admin BOOLEAN, + username TEXT, + groups TEXT, + pgroups TEXT, + folders_read TEXT, + folders_write TEXT +) RETURNS void AS $$ +BEGIN + IF admin THEN + SET LOCAL ROLE windmill_admin; + ELSE + SET LOCAL ROLE windmill_user; + END IF; + PERFORM set_config('session.user', username, true); + PERFORM set_config('session.groups', groups, true); + PERFORM set_config('session.pgroups', pgroups, true); + PERFORM set_config('session.folders_read', folders_read, true); + PERFORM set_config('session.folders_write', folders_write, true); +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/migrations/20250414082127_update_capture_format.down.sql b/backend/migrations/20250414082127_update_capture_format.down.sql new file mode 100644 index 0000000000..3739642e41 --- /dev/null +++ b/backend/migrations/20250414082127_update_capture_format.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE capture RENAME COLUMN preprocessor_args to trigger_extra; +ALTER TABLE capture RENAME COLUMN main_args to payload; diff --git a/backend/migrations/20250414082127_update_capture_format.up.sql b/backend/migrations/20250414082127_update_capture_format.up.sql new file mode 100644 index 0000000000..dc1653315e --- /dev/null +++ b/backend/migrations/20250414082127_update_capture_format.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE capture RENAME COLUMN trigger_extra to preprocessor_args; +ALTER TABLE capture RENAME COLUMN payload to main_args; \ No newline at end of file diff --git a/backend/migrations/20250417132646_add-resource-type-column-to-sqs.down.sql b/backend/migrations/20250417132646_add-resource-type-column-to-sqs.down.sql new file mode 100644 index 0000000000..8a5cb640cc --- /dev/null +++ b/backend/migrations/20250417132646_add-resource-type-column-to-sqs.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE sqs_trigger DROP COLUMN aws_auth_resource_type; +DROP TYPE IF EXISTS AWS_AUTH_RESOURCE_TYPE; \ No newline at end of file diff --git a/backend/migrations/20250417132646_add-resource-type-column-to-sqs.up.sql b/backend/migrations/20250417132646_add-resource-type-column-to-sqs.up.sql new file mode 100644 index 0000000000..50b3a367d8 --- /dev/null +++ b/backend/migrations/20250417132646_add-resource-type-column-to-sqs.up.sql @@ -0,0 +1,4 @@ +-- Add up migration script here +CREATE TYPE AWS_AUTH_RESOURCE_TYPE AS ENUM ('oidc', 'credentials'); +ALTER TABLE sqs_trigger + ADD COLUMN aws_auth_resource_type AWS_AUTH_RESOURCE_TYPE DEFAULT 'credentials'::AWS_AUTH_RESOURCE_TYPE NOT NULL; \ No newline at end of file diff --git a/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.down.sql b/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.down.sql new file mode 100644 index 0000000000..076f1891b6 --- /dev/null +++ b/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE gcp_trigger DROP COLUMN subscription_mode; +DROP TYPE GCP_SUBSCRIPTION_MODE; \ No newline at end of file diff --git a/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.up.sql b/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.up.sql new file mode 100644 index 0000000000..529d9580db --- /dev/null +++ b/backend/migrations/20250420144035_add-new-column-to-gcp-trigger.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +CREATE TYPE GCP_SUBSCRIPTION_MODE AS ENUM ('create_update', 'existing'); +ALTER TABLE gcp_trigger ADD COLUMN subscription_mode GCP_SUBSCRIPTION_MODE NOT NULL DEFAULT 'create_update'::GCP_SUBSCRIPTION_MODE; \ No newline at end of file diff --git a/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.down.sql b/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.down.sql new file mode 100644 index 0000000000..18b1b65140 --- /dev/null +++ b/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE websocket_trigger +ALTER COLUMN url TYPE VARCHAR(255); \ No newline at end of file diff --git a/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.up.sql b/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.up.sql new file mode 100644 index 0000000000..51da384e02 --- /dev/null +++ b/backend/migrations/20250421120705_alter_url_column_length_on_websocket_triggers.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TABLE websocket_trigger +ALTER COLUMN url TYPE VARCHAR(1000); \ No newline at end of file diff --git a/backend/migrations/20250424144434_critical_alerts_on_db_oversize.down.sql b/backend/migrations/20250424144434_critical_alerts_on_db_oversize.down.sql new file mode 100644 index 0000000000..5eceb2c095 --- /dev/null +++ b/backend/migrations/20250424144434_critical_alerts_on_db_oversize.down.sql @@ -0,0 +1 @@ +DELETE FROM global_settings WHERE name = 'critical_alerts_on_db_oversize'; diff --git a/backend/migrations/20250424144434_critical_alerts_on_db_oversize.up.sql b/backend/migrations/20250424144434_critical_alerts_on_db_oversize.up.sql new file mode 100644 index 0000000000..cb1868dbba --- /dev/null +++ b/backend/migrations/20250424144434_critical_alerts_on_db_oversize.up.sql @@ -0,0 +1,3 @@ +INSERT INTO global_settings (name, value) +VALUES ('critical_alerts_on_db_oversize', '{}') +ON CONFLICT (name) DO NOTHING; diff --git a/backend/migrations/20250428170426_mcp_mode_log.down.sql b/backend/migrations/20250428170426_mcp_mode_log.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250428170426_mcp_mode_log.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250428170426_mcp_mode_log.up.sql b/backend/migrations/20250428170426_mcp_mode_log.up.sql new file mode 100644 index 0000000000..038bb8234a --- /dev/null +++ b/backend/migrations/20250428170426_mcp_mode_log.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TYPE log_mode ADD VALUE 'mcp'; \ No newline at end of file diff --git a/backend/migrations/20250429211554_create_indices_on_queue.down.sql b/backend/migrations/20250429211554_create_indices_on_queue.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250429211554_create_indices_on_queue.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250429211554_create_indices_on_queue.up.sql b/backend/migrations/20250429211554_create_indices_on_queue.up.sql new file mode 100644 index 0000000000..0aafb20d34 --- /dev/null +++ b/backend/migrations/20250429211554_create_indices_on_queue.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +CREATE INDEX IF NOT EXISTS idx_metrics_id_created_at ON public.metrics (id, created_at DESC) WHERE id LIKE 'queue_%'; \ No newline at end of file diff --git a/backend/migrations/20250429214657_create_indices_on_job_stats.down.sql b/backend/migrations/20250429214657_create_indices_on_job_stats.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250429214657_create_indices_on_job_stats.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250429214657_create_indices_on_job_stats.up.sql b/backend/migrations/20250429214657_create_indices_on_job_stats.up.sql new file mode 100644 index 0000000000..b201db5dbe --- /dev/null +++ b/backend/migrations/20250429214657_create_indices_on_job_stats.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +CREATE INDEX IF NOT EXISTS job_stats_id ON job_stats (job_id); \ No newline at end of file diff --git a/backend/migrations/20250506092215_runnable_version_notify.down.sql b/backend/migrations/20250506092215_runnable_version_notify.down.sql new file mode 100644 index 0000000000..b94db9dfdc --- /dev/null +++ b/backend/migrations/20250506092215_runnable_version_notify.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here +DROP TRIGGER script_update_trigger ON script; +DROP TRIGGER flow_update_trigger ON flow_version; +DROP FUNCTION notify_runnable_version_change(); diff --git a/backend/migrations/20250506092215_runnable_version_notify.up.sql b/backend/migrations/20250506092215_runnable_version_notify.up.sql new file mode 100644 index 0000000000..a8d11142f4 --- /dev/null +++ b/backend/migrations/20250506092215_runnable_version_notify.up.sql @@ -0,0 +1,22 @@ +-- Add up migration script here +CREATE OR REPLACE FUNCTION notify_runnable_version_change() +RETURNS TRIGGER AS $$ +DECLARE + source_type TEXT; +BEGIN + source_type := TG_ARGV[0]; + + PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER script_update_trigger +AFTER UPDATE OF lock ON script +FOR EACH ROW +EXECUTE FUNCTION notify_runnable_version_change('script'); + +CREATE TRIGGER flow_update_trigger +AFTER INSERT ON flow_version +FOR EACH ROW +EXECUTE FUNCTION notify_runnable_version_change('flow'); diff --git a/backend/migrations/20250506151818_orderby_refactor.down.sql b/backend/migrations/20250506151818_orderby_refactor.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250506151818_orderby_refactor.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250506151818_orderby_refactor.up.sql b/backend/migrations/20250506151818_orderby_refactor.up.sql new file mode 100644 index 0000000000..a983639760 --- /dev/null +++ b/backend/migrations/20250506151818_orderby_refactor.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +DROP INDEX IF EXISTS index_script_on_path_created_at; +CREATE INDEX IF NOT EXISTS index_script_on_path_created_at ON script (workspace_id, path, created_at DESC); diff --git a/backend/migrations/20250514120017_http_trigger_update_notify.down.sql b/backend/migrations/20250514120017_http_trigger_update_notify.down.sql new file mode 100644 index 0000000000..5f2bfa3b28 --- /dev/null +++ b/backend/migrations/20250514120017_http_trigger_update_notify.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here +DROP TRIGGER http_trigger_change_trigger ON http_trigger; +DROP FUNCTION notify_http_trigger_change(); +DROP SEQUENCE http_trigger_version_seq; \ No newline at end of file diff --git a/backend/migrations/20250514120017_http_trigger_update_notify.up.sql b/backend/migrations/20250514120017_http_trigger_update_notify.up.sql new file mode 100644 index 0000000000..bfcbf91225 --- /dev/null +++ b/backend/migrations/20250514120017_http_trigger_update_notify.up.sql @@ -0,0 +1,15 @@ +-- Add up migration script here +CREATE OR REPLACE FUNCTION notify_http_trigger_change() +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('notify_http_trigger_change', NEW.workspace_id || ':' || NEW.path); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER http_trigger_change_trigger +AFTER INSERT OR UPDATE OR DELETE ON http_trigger +FOR EACH ROW +EXECUTE FUNCTION notify_http_trigger_change(); + +CREATE SEQUENCE http_trigger_version_seq; + diff --git a/backend/migrations/20250515084520_duckdb_support.down.sql b/backend/migrations/20250515084520_duckdb_support.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250515084520_duckdb_support.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250515084520_duckdb_support.up.sql b/backend/migrations/20250515084520_duckdb_support.up.sql new file mode 100644 index 0000000000..869fcc07dc --- /dev/null +++ b/backend/migrations/20250515084520_duckdb_support.up.sql @@ -0,0 +1,3 @@ +-- 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; diff --git a/backend/migrations/20250515155403_add_grant_on_dependency_map.down.sql b/backend/migrations/20250515155403_add_grant_on_dependency_map.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250515155403_add_grant_on_dependency_map.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250515155403_add_grant_on_dependency_map.up.sql b/backend/migrations/20250515155403_add_grant_on_dependency_map.up.sql new file mode 100644 index 0000000000..bc0f350ebb --- /dev/null +++ b/backend/migrations/20250515155403_add_grant_on_dependency_map.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +GRANT ALL ON dependency_map TO windmill_user; +GRANT ALL ON dependency_map TO windmill_admin; \ No newline at end of file diff --git a/backend/migrations/20250515181903_fix_http_routers_cache.down.sql b/backend/migrations/20250515181903_fix_http_routers_cache.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250515181903_fix_http_routers_cache.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250515181903_fix_http_routers_cache.up.sql b/backend/migrations/20250515181903_fix_http_routers_cache.up.sql new file mode 100644 index 0000000000..24bf5d1a2c --- /dev/null +++ b/backend/migrations/20250515181903_fix_http_routers_cache.up.sql @@ -0,0 +1,9 @@ +-- Add up migration script here +-- this makes sure that the first time nextval is called, 2 is returned +-- otherwise, `SELECT last_value from http_trigger_version_seq;` would return 1 before and after the first nextval call +-- which would not refresh the routers cache after the first create/update/delete +SELECT setval( + 'http_trigger_version_seq', + (SELECT last_value FROM http_trigger_version_seq), + true +); \ No newline at end of file diff --git a/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql b/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql new file mode 100644 index 0000000000..2cdc8d698b --- /dev/null +++ b/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql @@ -0,0 +1,12 @@ +-- Add down migration script here +CREATE OR REPLACE FUNCTION notify_runnable_version_change() +RETURNS TRIGGER AS $$ +DECLARE + source_type TEXT; +BEGIN + source_type := TG_ARGV[0]; + + PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql b/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql new file mode 100644 index 0000000000..bb1ebe0a40 --- /dev/null +++ b/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql @@ -0,0 +1,19 @@ +-- Add up migration script here +CREATE OR REPLACE FUNCTION notify_runnable_version_change() +RETURNS TRIGGER AS $$ +DECLARE + source_type TEXT; + kind TEXT; +BEGIN + source_type := TG_ARGV[0]; + + IF source_type = 'script' THEN + kind := NEW.kind; + ELSE + kind := 'flow'; + END IF; + + PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path || ':' || kind); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/migrations/20250526173025_http_trigger_seq_grants.down.sql b/backend/migrations/20250526173025_http_trigger_seq_grants.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250526173025_http_trigger_seq_grants.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250526173025_http_trigger_seq_grants.up.sql b/backend/migrations/20250526173025_http_trigger_seq_grants.up.sql new file mode 100644 index 0000000000..bf09f0a555 --- /dev/null +++ b/backend/migrations/20250526173025_http_trigger_seq_grants.up.sql @@ -0,0 +1,3 @@ +-- 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; \ No newline at end of file diff --git a/backend/migrations/20250604181226_auth_cache_invalidation.down.sql b/backend/migrations/20250604181226_auth_cache_invalidation.down.sql new file mode 100644 index 0000000000..b7f0f12204 --- /dev/null +++ b/backend/migrations/20250604181226_auth_cache_invalidation.down.sql @@ -0,0 +1,4 @@ +-- Remove token invalidation notification trigger + +DROP TRIGGER IF EXISTS token_invalidation_trigger ON token; +DROP FUNCTION IF EXISTS notify_token_invalidation(); diff --git a/backend/migrations/20250604181226_auth_cache_invalidation.up.sql b/backend/migrations/20250604181226_auth_cache_invalidation.up.sql new file mode 100644 index 0000000000..11be3961c0 --- /dev/null +++ b/backend/migrations/20250604181226_auth_cache_invalidation.up.sql @@ -0,0 +1,17 @@ +-- Add token invalidation notification trigger + +CREATE OR REPLACE FUNCTION notify_token_invalidation() +RETURNS TRIGGER AS $$ +BEGIN + -- Only notify for session token deletions when the invalidation settings are enabled + IF OLD.label = 'session' AND OLD.email IS NOT NULL THEN + PERFORM pg_notify('notify_token_invalidation', OLD.token); + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER token_invalidation_trigger +AFTER DELETE ON token +FOR EACH ROW +EXECUTE FUNCTION notify_token_invalidation(); diff --git a/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.down.sql b/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.down.sql new file mode 100644 index 0000000000..597a95161d --- /dev/null +++ b/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here +ALTER TABLE http_trigger +DROP COLUMN summary, +DROP COLUMN description; \ No newline at end of file diff --git a/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.up.sql b/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.up.sql new file mode 100644 index 0000000000..4d51f6d07b --- /dev/null +++ b/backend/migrations/20250607102516_add_description_and_summary_to_http_trigger.up.sql @@ -0,0 +1,5 @@ +-- Add up migration script here +ALTER TABLE http_trigger +ADD COLUMN summary VARCHAR(512) NULL, +ADD COLUMN description TEXT NULL; + diff --git a/backend/migrations/20250618164000_add_agent_token_blacklist.down.sql b/backend/migrations/20250618164000_add_agent_token_blacklist.down.sql new file mode 100644 index 0000000000..aad208817c --- /dev/null +++ b/backend/migrations/20250618164000_add_agent_token_blacklist.down.sql @@ -0,0 +1,2 @@ +-- Remove agent token blacklist table +DROP TABLE IF EXISTS agent_token_blacklist; \ No newline at end of file diff --git a/backend/migrations/20250618164000_add_agent_token_blacklist.up.sql b/backend/migrations/20250618164000_add_agent_token_blacklist.up.sql new file mode 100644 index 0000000000..aefff3913d --- /dev/null +++ b/backend/migrations/20250618164000_add_agent_token_blacklist.up.sql @@ -0,0 +1,14 @@ +-- Add agent token blacklist table +CREATE TABLE agent_token_blacklist ( + token VARCHAR PRIMARY KEY, + expires_at TIMESTAMP NOT NULL, + blacklisted_at TIMESTAMP NOT NULL DEFAULT NOW(), + blacklisted_by VARCHAR NOT NULL +); + +-- Add index for efficient expiry cleanup +CREATE INDEX idx_agent_token_blacklist_expires_at ON agent_token_blacklist(expires_at); + +-- Grant permissions to windmill users +GRANT ALL ON agent_token_blacklist TO windmill_user; +GRANT ALL ON agent_token_blacklist TO windmill_admin; \ No newline at end of file diff --git a/backend/parsers/windmill-parser-bash/src/lib.rs b/backend/parsers/windmill-parser-bash/src/lib.rs index 39f1d2667b..69d96400dc 100644 --- a/backend/parsers/windmill-parser-bash/src/lib.rs +++ b/backend/parsers/windmill-parser-bash/src/lib.rs @@ -45,7 +45,7 @@ pub fn parse_powershell_sig(code: &str) -> anyhow::Result { } lazy_static::lazy_static! { - static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?$"#).unwrap(); + static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?$"#).unwrap(); pub static ref RE_POWERSHELL_PARAM: Regex = Regex::new(r#"(?m)param[\t ]*\(([^)]*)\)"#).unwrap(); static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[(\w+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?"#).unwrap(); @@ -57,11 +57,12 @@ fn parse_bash_file(code: &str) -> anyhow::Result>> { hm.insert( cap.get(2) .or(cap.get(3)) + .or(cap.get(4)) .and_then(|x| x.as_str().parse::().ok()) .ok_or_else(|| anyhow!("Impossible to parse arg digit"))?, ( cap[1].to_string(), - cap.get(4).map(|x| x.as_str().to_string()), + cap.get(5).map(|x| x.as_str().to_string()), ), ); } diff --git a/backend/parsers/windmill-parser-java/Cargo.toml b/backend/parsers/windmill-parser-java/Cargo.toml new file mode 100644 index 0000000000..19809e3120 --- /dev/null +++ b/backend/parsers/windmill-parser-java/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "windmill-parser-java" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +name = "windmill_parser_java" +path = "./src/lib.rs" + +[dependencies] +windmill-parser.workspace = true +tree-sitter.workspace = true +tree-sitter-java.workspace = true +anyhow.workspace = true +wasm-bindgen.workspace = true +serde_json.workspace = true +# convert_case.workspace = true +# lazy_static.workspace = true +# regex.workspace = true + diff --git a/backend/parsers/windmill-parser-java/src/lib.rs b/backend/parsers/windmill-parser-java/src/lib.rs new file mode 100644 index 0000000000..93aa348cec --- /dev/null +++ b/backend/parsers/windmill-parser-java/src/lib.rs @@ -0,0 +1,446 @@ +#![cfg_attr(target_arch = "wasm32", feature(c_variadic))] + +#[cfg(target_arch = "wasm32")] +pub mod wasm_libc; + +use anyhow::anyhow; +use anyhow::bail; +use serde_json::Value; +use tree_sitter::Node; +use windmill_parser::Arg; +use windmill_parser::MainArgSignature; +use windmill_parser::Typ; + +#[derive(Debug)] +pub struct JavaMainSigMeta { + pub is_public: bool, + pub returns_void: bool, + pub class_name: Option, + pub main_sig: MainArgSignature, +} + +pub fn parse_java_sig_meta(code: &str) -> anyhow::Result { + let mut parser = tree_sitter::Parser::new(); + let language = tree_sitter_java::LANGUAGE; + parser + .set_language(&language.into()) + .map_err(|e| anyhow!("Error setting Java as language: {e}"))?; + + // Parse code + let tree = parser + .parse(code, None) + .ok_or(anyhow!("Failed to parse code"))?; + let root_node = tree.root_node(); + + // Traverse the AST to find the Main method signature + let main_sig = find_main_signature(root_node, code); + let no_main_func = Some(main_sig.is_none()); + let mut is_public = false; + let mut returns_void = false; + let mut class_name = None; + + let mut args = vec![]; + if let Some((sig, name)) = main_sig { + class_name = name; + for sig_node in sig.children(&mut sig.walk()) { + if sig_node.kind() == "modifier" && sig_node.utf8_text(code.as_bytes())? == "public" { + is_public = true; + } + } + if let Some(return_type) = sig.child_by_field_name("type") { + let return_type = return_type.utf8_text(code.as_bytes())?; + + if return_type == "void" { + returns_void = true; + } + } + if let Some(param_list) = sig.child_by_field_name("parameters") { + for p_list_node in param_list.children(&mut param_list.walk()) { + if p_list_node.kind() == "formal_parameter" { + let (otyp, typ, name, default) = parse_java_typ(p_list_node, code)?; + args.push(Arg { + name, + otyp, + typ, + has_default: default.is_some(), + default, + oidx: None, + }); + } + } + } + } + + let main_sig = MainArgSignature { + star_args: false, + star_kwargs: false, + args, + has_preprocessor: None, + no_main_func, + }; + + Ok(JavaMainSigMeta { returns_void, class_name, main_sig, is_public }) +} + +pub fn parse_java_signature(code: &str) -> anyhow::Result { + Ok(parse_java_sig_meta(code)?.main_sig) +} + +fn find_typ<'a>(typ_node: Node<'a>, code: &str) -> anyhow::Result<(Typ, Option)> { + let null = Some(serde_json::Value::Null); + let res = match typ_node.kind() { + #[rustfmt::skip] + "type_identifier" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("String") => (Typ::Str(None), null), + Ok("Byte") => (Typ::Bytes, null), + Ok("Short") => (Typ::Int, null), + Ok("Integer") => (Typ::Int, null), + Ok("Long") => (Typ::Int, null), + Ok("Float") => (Typ::Float, null), + Ok("Double") => (Typ::Float, null), + Ok("Boolean") => (Typ::Bool, null), + Ok("Character") => (Typ::Str(None), null), + Ok("Object") => (Typ::Object(vec![]),null), // TODO: Complete the object type + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + #[rustfmt::skip] + "integral_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("byte") => (Typ::Bytes, None), + Ok("short") => (Typ::Int, None), + Ok("int") => (Typ::Int, None), + Ok("long") => (Typ::Int, None), + Ok("char") => (Typ::Str(None), None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "floating_point_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("float") => (Typ::Float, None), + Ok("double") => (Typ::Float, None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "boolean_type" => { + match typ_node.utf8_text(code.as_bytes()) { + Ok("boolean") => (Typ::Bool, None), + Ok(s) => bail!("Unknown type `{s}`"), + Err(e) => bail!("Error getting type name: {}", e), + } + } + "array_type" => { + let new_typ_node = typ_node + .named_child(0) + .ok_or(anyhow!("Failed to find inner type of array type"))?; + (Typ::List(Box::new(find_typ(new_typ_node, code)?.0)), null) + } + wc => bail!( + "Unexpected Java type node kind: {} for '{}'. This type is not handled by Windmill, please open an issue if this seems to be an error", + wc, + typ_node.utf8_text(code.as_bytes())? + ), + + }; + Ok(res) +} + +fn parse_java_typ<'a>( + param_node: Node<'a>, + code: &str, +) -> anyhow::Result<(Option, Typ, String, Option)> { + let name = param_node + .child_by_field_name("name") + .and_then(|n| n.utf8_text(code.as_bytes()).ok()) + .unwrap_or(""); + let otyp_node = param_node.child_by_field_name("type"); + let otyp = otyp_node + .and_then(|n| n.utf8_text(code.as_bytes()).ok()) + .map(|s| s.to_string()); + + let (typ, default) = find_typ( + otyp_node.ok_or(anyhow!( + "Internal error: Failed to get child by field name 'type'" + ))?, + code, + )?; + + Ok((otyp, typ, name.to_string(), default)) +} + +// Function to find the Main method's signature +fn find_main_signature<'a>(root_node: Node<'a>, code: &str) -> Option<(Node<'a>, Option)> { + let mut cursor = root_node.walk(); + for x in root_node.children(&mut cursor) { + if x.kind() == "class_declaration" { + let class_name = x + .child_by_field_name("name") + .and_then(|n| n.utf8_text(code.as_bytes()).ok().map(|s| s.to_string())); + for c in x.children(&mut x.walk()) { + if c.kind() == "class_body" { + for w in c.children(&mut c.walk()) { + if w.kind() == "method_declaration" { + for child in w.children(&mut w.walk()) { + if child + .utf8_text(code.as_bytes()) + .map(|name| name == "main") + .unwrap_or(false) + { + return Some((w, class_name)); + } + } + } + } + } + } + } + } + return None; +} + +#[cfg(test)] +mod test { + + use serde_json::json; + + use super::*; + #[test] + fn test_parse_java_return_void() { + let code = r#" +class Main { + public static void main() {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + assert!(sig_meta.returns_void); + } + #[test] + fn test_parse_java_return_object() { + let code = r#" +class Main { + public static Object main() {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + assert!(!sig_meta.returns_void); + } + #[test] + fn test_parse_java_primitive_types() { + let code = r#" +class Main { + public static string main(byte a, short b, int c, long d, float e, double f, boolean g, char h) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("byte".into()), + typ: Typ::Bytes, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("short".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("int".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "d".into(), + otyp: Some("long".into()), + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "e".into(), + otyp: Some("float".into()), + typ: Typ::Float, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "f".into(), + otyp: Some("double".into()), + typ: Typ::Float, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "g".into(), + otyp: Some("boolean".into()), + typ: Typ::Bool, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "h".into(), + otyp: Some("char".into()), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None + }, + ] + ); + } + + #[test] + fn test_parse_java_objects() { + let code = r#" +class Main { + public static string main(Byte a, Short b, Integer c, Long d, Float e, Double f, Boolean g, Character h, Object i) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("Byte".into()), + typ: Typ::Bytes, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("Short".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("Integer".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "d".into(), + otyp: Some("Long".into()), + typ: Typ::Int, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "e".into(), + otyp: Some("Float".into()), + typ: Typ::Float, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "f".into(), + otyp: Some("Double".into()), + typ: Typ::Float, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "g".into(), + otyp: Some("Boolean".into()), + typ: Typ::Bool, + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "h".into(), + otyp: Some("Character".into()), + typ: Typ::Str(None), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "i".into(), + otyp: Some("Object".into()), + typ: Typ::Object(vec![]), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + ] + ); + } + #[test] + fn test_parse_java_array() { + let code = r#" +class Main { + public static string main(int[] a, Object[] b, String[] c) {} +}"#; + let sig_meta = parse_java_sig_meta(code).unwrap(); + + assert_eq!(sig_meta.class_name, Some("Main".to_string())); + + let ret = sig_meta.main_sig; + assert_eq!( + ret.args, + vec![ + Arg { + name: "a".into(), + otyp: Some("int[]".into()), + typ: Typ::List(Box::new(Typ::Int)), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "b".into(), + otyp: Some("Object[]".into()), + typ: Typ::List(Box::new(Typ::Object(vec![]))), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + Arg { + name: "c".into(), + otyp: Some("String[]".into()), + typ: Typ::List(Box::new(Typ::Str(None))), + default: Some(json!(null)), + has_default: true, + oidx: None + }, + ] + ); + } +} diff --git a/backend/parsers/windmill-parser-java/src/wasm_libc.rs b/backend/parsers/windmill-parser-java/src/wasm_libc.rs new file mode 100644 index 0000000000..7d260af2e8 --- /dev/null +++ b/backend/parsers/windmill-parser-java/src/wasm_libc.rs @@ -0,0 +1,207 @@ +use std::collections::BTreeMap; +use std::sync::{Mutex, OnceLock}; +use std::{ + alloc::{self, Layout}, + ffi::{c_char, c_int, c_void}, + mem::align_of, + ptr, +}; +use wasm_bindgen::prelude::*; + +/* -------------------------------- stdlib.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn abort() { + panic!("Aborted from C"); +} + +macro_rules! console_log { + ($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string()) }) +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(a: &str); +} + +#[no_mangle] +pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void { + if size == 0 { + return ptr::null_mut(); + } + + let (layout, offset_to_data) = layout_for_size_prepended(size); + let buf = alloc::alloc(layout); + store_layout(buf, layout, offset_to_data) +} + +#[no_mangle] +pub unsafe extern "C" fn calloc(count: usize, size: usize) -> *mut c_void { + if count == 0 || size == 0 { + return ptr::null_mut(); + } + + let (layout, offset_to_data) = layout_for_size_prepended(size * count); + let buf = alloc::alloc_zeroed(layout); + store_layout(buf, layout, offset_to_data) +} + +#[no_mangle] +pub unsafe extern "C" fn realloc(buf: *mut c_void, new_size: usize) -> *mut c_void { + if buf.is_null() { + malloc(new_size) + } else if new_size == 0 { + free(buf); + ptr::null_mut() + } else { + let (old_buf, old_layout) = retrieve_layout(buf); + let (new_layout, offset_to_data) = layout_for_size_prepended(new_size); + let new_buf = alloc::realloc(old_buf, old_layout, new_layout.size()); + store_layout(new_buf, new_layout, offset_to_data) + } +} + +#[no_mangle] +pub unsafe extern "C" fn free(buf: *mut c_void) { + if buf.is_null() { + return; + } + let (buf, layout) = retrieve_layout(buf); + alloc::dealloc(buf, layout); +} + +// In all these allocations, we store the layout before the data for later retrieval. +// This is because we need to know the layout when deallocating the memory. +// Here are some helper methods for that: + +/// Given a pointer to the data, retrieve the layout and the pointer to the layout. +unsafe fn retrieve_layout(buf: *mut c_void) -> (*mut u8, Layout) { + let (_, layout_offset) = Layout::new::() + .extend(Layout::from_size_align(0, align_of::<*const u8>() * 2).unwrap()) + .unwrap(); + + let buf = (buf as *mut u8).offset(-(layout_offset as isize)); + let layout = *(buf as *mut Layout); + + (buf, layout) +} + +/// Calculate a layout for a given size with space for storing a layout at the start. +/// Returns the layout and the offset to the data. +fn layout_for_size_prepended(size: usize) -> (Layout, usize) { + Layout::new::() + .extend(Layout::from_size_align(size, align_of::<*const u8>() * 2).unwrap()) + .unwrap() +} + +/// Store a layout in the pointer, returning a pointer to where the data should be stored. +unsafe fn store_layout(buf: *mut u8, layout: Layout, offset_to_data: usize) -> *mut c_void { + *(buf as *mut Layout) = layout; + (buf as *mut u8).offset(offset_to_data as isize) as *mut c_void +} + +/* -------------------------------- string.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn strncmp(ptr1: *const c_void, ptr2: *const c_void, n: usize) -> c_int { + let s1 = std::slice::from_raw_parts(ptr1 as *const u8, n); + let s2 = std::slice::from_raw_parts(ptr2 as *const u8, n); + + for (a, b) in s1.iter().zip(s2.iter()) { + if *a != *b || *a == 0 { + return (*a as i32) - (*b as i32); + } + } + + 0 +} + +/* -------------------------------- wctype.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn iswspace(c: c_int) -> bool { + char::from_u32(c as u32).map_or(false, |c| c.is_whitespace()) +} + +#[no_mangle] +pub unsafe extern "C" fn iswalnum(c: c_int) -> bool { + char::from_u32(c as u32).map_or(false, |c| c.is_alphanumeric()) +} + +/* --------------------------------- time.h --------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn clock() -> u64 { + panic!("clock is not supported"); +} + +/* --------------------------------- ctype.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn isprint(c: c_int) -> bool { + c >= 32 && c <= 126 +} + +/* --------------------------------- stdio.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn fprintf(_file: *mut c_void, _format: *const c_void, _args: ...) -> c_int { + panic!("fprintf is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fputs(_s: *const c_void, _file: *mut c_void) -> c_int { + panic!("fputs is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fputc(_c: c_int, _file: *mut c_void) -> c_int { + panic!("fputc is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fdopen(_fd: c_int, _mode: *const c_void) -> *mut c_void { + panic!("fdopen is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int { + panic!("fclose is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fwrite( + _ptr: *const c_void, + _size: usize, + _nmemb: usize, + _stream: *mut c_void, +) -> usize { + panic!("fwrite is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn vsnprintf( + _buf: *mut c_char, + _size: usize, + _format: *const c_char, + _args: ... +) -> c_int { + panic!("vsnprintf is not supported"); +} + +#[no_mangle] +pub extern "C" fn clock_gettime(ptr: usize, new_size: usize) { + panic!("clock_gettime is not supported"); +} + +// int snprintf( char* restrict buffer, size_t bufsz, const char* restrict format, ... ); +#[no_mangle] +pub extern "C" fn snprintf() { + panic!("snprintf is not supported"); +} + +#[no_mangle] +pub extern "C" fn __assert_fail(_: *const i32, _: *const i32, _: *const i32, _: *const i32) { + panic!("oh no"); +} diff --git a/backend/parsers/windmill-parser-nu/Cargo.toml b/backend/parsers/windmill-parser-nu/Cargo.toml new file mode 100644 index 0000000000..c8c3e0d616 --- /dev/null +++ b/backend/parsers/windmill-parser-nu/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "windmill-parser-nu" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_parser_nu" +path = "./src/lib.rs" + +[dependencies] +windmill-parser.workspace = true +anyhow.workspace = true +nu-parser.workspace = true +wasm-bindgen.workspace = true +serde_json.workspace = true diff --git a/backend/parsers/windmill-parser-nu/src/lib.rs b/backend/parsers/windmill-parser-nu/src/lib.rs new file mode 100644 index 0000000000..ddd049f609 --- /dev/null +++ b/backend/parsers/windmill-parser-nu/src/lib.rs @@ -0,0 +1,261 @@ +#![cfg_attr(target_arch = "wasm32", feature(c_variadic))] + +use anyhow::{anyhow, bail}; +use nu_parser::lex; + +use serde_json::{json, Value}; +use windmill_parser::{Arg, MainArgSignature, Typ}; + +pub fn parse_nu_signature(code: &str) -> anyhow::Result { + let (tokens, ..) = lex(code.as_bytes(), 0, &[], &[], true); + let src = code.to_owned(); + #[derive(Debug)] + enum LastToken { + None, + Def, + Main, + Args(String), + } + let mut last_token = LastToken::None; + for token in tokens { + let s = token.span; + let cont = src.get(s.start..s.end).ok_or(anyhow!("Parsing error"))?; + last_token = match last_token { + LastToken::None if cont == "def" => LastToken::Def, + LastToken::Def if cont == "main" => LastToken::Main, + LastToken::Main => { + LastToken::Args(cont.get(1..(cont.len() - 1)).unwrap_or("Error").to_owned()) + } + LastToken::Args(_) => break, + _ => LastToken::None, + }; + } + + let LastToken::Args(args) = last_token else { + bail!("Cannot find main function."); + }; + + let mut sig = MainArgSignature::default(); + sig.no_main_func = Some(false); + + let batches = args + .lines() + .filter_map(|el| { + if el.trim_start().starts_with('#') { + None + } else { + Some( + el.split(',') + .map(|el| el.trim()) + .filter(|el| el != &"") + .collect::>(), + ) + } + }) + .flatten() + .collect::>(); + + let mut compensate_lookahead = 0; + for (i, batch) in batches.iter().enumerate() { + // parse_default can lookahead and if it does we need to compensate + // otherwise we would try to parse data already parsed but not yielded by parse_default + if compensate_lookahead > 0 { + compensate_lookahead -= 1; + continue; + } + + let type_start = batch.find(":"); + let default_start = batch.find("="); + + let (name, typ, default) = match (type_start, default_start) { + (None, None) => (batch.trim(), None, None), + (None, Some(d)) => ( + batch + .get(0..d) + .ok_or(anyhow!("Cannot parse argument ident"))? + .trim(), + None, + Some(parse_default( + &batch + .get(d..) + .ok_or(anyhow!("Cannot parse default value for argument"))?, + &batches, + i, + &mut compensate_lookahead, + )?), + ), + (Some(t), None) => ( + batch + .get(0..t) + .ok_or(anyhow!("Cannot parse argument ident"))? + .trim(), + Some(parse_type( + &batch + .get(t..) + .ok_or(anyhow!("Cannot parse type of argument"))?, + )?), + None, + ), + (Some(t), Some(d)) => { + if t < d { + ( + batch + .get(0..t) + .ok_or(anyhow!("Cannot parse argument ident"))? + .trim(), + Some(parse_type( + &batch + .get(t..d) + .ok_or(anyhow!("Cannot parse type of argument"))?, + )?), + Some(parse_default( + &batch + .get(d..) + .ok_or(anyhow!("Cannot parse default value of argument"))?, + &batches, + i, + &mut compensate_lookahead, + )?), + ) + } else { + bail!("Parsing error `:` should be before `=`\nit likely means you are trying to set default value to record or table which is not supported at the moment.") + } + } + }; + + // Check if it is optional + let optional = { + let Some(element) = name.chars().last() else { + bail!("Internal error, cannot check if argument is optional") + }; + element == '?' + }; + + // Rest parameters are not supported + if matches!(name.get(0..3), Some("...")) { + bail!("Rest (...) parameters are not supported") + } + + // Flags are not supported + if matches!(name.get(0..2), Some("--")) { + bail!("Flags are not supported") + } + + sig.args.push(Arg { + name: if optional { + name.get(..name.len() - 1).unwrap_or("Error").to_owned() + } else { + name.to_owned() + }, + typ: typ.unwrap_or(Typ::Unknown), + otyp: None, + has_default: default.is_some() || optional, + default: default.or_else(|| if optional { Some(json!(null)) } else { None }), + oidx: None, + }); + } + + fn parse_type(content: &str) -> anyhow::Result { + let c = content.replace(":", "").trim().to_owned(); + let typ = match c.as_str() { + "string" => Typ::Str(None), + "int" => Typ::Int, + "float" => Typ::Float, + "number" => Typ::Float, + "record" => Typ::Object(vec![]), + "table" => Typ::List(Box::new(Typ::Object(vec![]))), + "nothing" => Typ::Unknown, + // TODO: needs additional work on literal parsing + // "binary" => Typ::Bytes, + "datetime" => Typ::Datetime, + "any" => Typ::Unknown, + "bool" => Typ::Bool, + // Lists + "list" | "list" | "list" => Typ::List(Box::new(Typ::Unknown)), + "list" => Typ::List(Box::new(Typ::Float)), + "list" => Typ::List(Box::new(Typ::Bool)), + "list" => Typ::List(Box::new(Typ::Str(None))), + // list is not supported + // Records and Tables + // TODO: Support in V1? + s if s.contains("record<") => { + bail!("typed records are not supported, use `ident: record`") + } + s if s.contains("table<") => { + bail!("typed tables are not supported, use `ident: table`") + } + s => bail!("{s} is not supported"), + }; + Ok(typ) + } + fn parse_default( + content: &str, + ctx: &[&str], + i: usize, + skip: &mut usize, + ) -> anyhow::Result { + let mut c = content.replace("=", "").trim().to_owned(); + + fn parse_object_literal( + (open, close): (char, char), + mut c_2: String, + ctx: &[&str], + i: usize, + skip: &mut usize, + ) -> anyhow::Result { + // It is list + // if c.contains("[") { + let mut closed = false; + if c_2 != open.to_string() { + // [a ~ , ~ ... + // Add ^ + c_2 += ","; + } + // else { + // [ + // a < Do not add "," + // ... + // } + let remainder = &ctx + .iter() + .skip(i + 1) + .map_while(|el| { + let el = el.trim(); + + if closed { + None + } else { + if el.chars().last() == Some(close) { + closed = true; + } + *skip += 1; + Some(el) + } + }) + .collect::>() + .join(","); + + if remainder.contains(&['{', '[']) { + bail!("Nesting is not supported") + } + + Ok((c_2 + remainder) + // Remove trailing comma if there is any + .replace(&format!(",{close}"), &close.to_string())) + } + // It is list + if c.contains("[") { + if c.chars().last() != Some(']') { + c = parse_object_literal(('[', ']'), c.clone(), ctx, i, skip)?; + } + } + // It is record + if c.contains("{") { + if c.chars().last() != Some('}') { + c = parse_object_literal(('{', '}'), c.clone(), ctx, i, skip)?; + } + } + Ok(serde_json::from_str(&c)?) + } + Ok(sig) +} diff --git a/backend/parsers/windmill-parser-nu/tests/tests.rs b/backend/parsers/windmill-parser-nu/tests/tests.rs new file mode 100644 index 0000000000..13c54930d6 --- /dev/null +++ b/backend/parsers/windmill-parser-nu/tests/tests.rs @@ -0,0 +1,690 @@ +#[cfg(test)] +mod test { + use serde_json::json; + use windmill_parser::{Arg, MainArgSignature, Typ}; + use windmill_parser_nu::parse_nu_signature; + + #[test] + fn test_nu_no_main_sig() { + assert!(parse_nu_signature("").is_err()); + } + #[test] + fn test_nu_any_sig() { + let sig = parse_nu_signature( + r#" + def main [ a, b , c, d] {} + "#, + ) + .unwrap(); + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "a".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "b".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "c".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "d".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + } + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_optional_sig() { + let sig = parse_nu_signature( + r#" + def main [foo?] {} + "#, + ) + .unwrap(); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![Arg { + name: "foo".into(), + otyp: None, + typ: Typ::Unknown, + default: Some(serde_json::Value::Null), + has_default: true, + oidx: None + },], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_simple_typed_sig() { + let sig = parse_nu_signature( + r#" + def main [ foo: string, bar: int] {} + "#, + ) + .unwrap(); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "foo".into(), + otyp: None, + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "bar".into(), + otyp: None, + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_complete_typed_sig() { + let sig = parse_nu_signature( + r#" + def main [ + a1: any, + a2: bool, + a3: int, + a4: float, + a5: datetime, + a6: string, + a7: record, + a8: list, + a9: table, + a10: nothing, + ] {} + "#, + ) + .unwrap(); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "a1".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a2".into(), + otyp: None, + typ: Typ::Bool, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a3".into(), + otyp: None, + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a4".into(), + otyp: None, + typ: Typ::Float, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a5".into(), + otyp: None, + typ: Typ::Datetime, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a6".into(), + otyp: None, + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a7".into(), + otyp: None, + typ: Typ::Object(vec![]), + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a8".into(), + otyp: None, + typ: Typ::List(Box::new(Typ::Unknown)), + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a9".into(), + otyp: None, + typ: Typ::List(Box::new(Typ::Object(vec![]))), + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "a10".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_default_sig() { + let sig = parse_nu_signature( + r#" + def main [ foo = "Foo", bar: string = "Bar", bazz = 3 ] {} + "#, + ) + .unwrap(); + + println!("{}", serde_json::to_string_pretty(&sig).unwrap()); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "foo".into(), + otyp: None, + typ: Typ::Unknown, + default: Some(json!("Foo")), + has_default: true, + oidx: None + }, + Arg { + name: "bar".into(), + otyp: None, + typ: Typ::Str(None), + default: Some(json!("Bar")), + has_default: true, + oidx: None + }, + Arg { + name: "bazz".into(), + otyp: None, + typ: Typ::Unknown, + default: Some(json!(3)), + has_default: true, + oidx: None + }, + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_preprocessor_sig() {} + + #[test] + fn test_nu_flags_sig() { + assert!(parse_nu_signature( + r#" + def main [--flag] {} + "#, + ) + .is_err()); + } + #[test] + fn test_nu_rest_sig() { + assert!(parse_nu_signature( + r#" + def main [...foo: string] {} + "#, + ) + .is_err()) + } + + // #[test] + // fn test_nu_dynamically_sized_sig() { + // parse_nu_signature( + // r#" + // def main [] { + + // } + // "#, + // ); + // } + // #[test] + // fn test_nu_record_sig() { + // let sig = parse_nu_signature( + // r#" + // def main [ foo: record ] { } + // "#, + // ) + // .unwrap(); + + // assert_eq!( + // MainArgSignature { + // star_args: false, + // star_kwargs: false, + // args: vec![Arg { + // name: "foo".into(), + // otyp: None, + // typ: Typ::Object(vec![ + // ObjectProperty { key: "a".into(), typ: Box::new(Typ::Str(None)) }, + // ObjectProperty { key: "b".into(), typ: Box::new(Typ::Unknown) }, + // ObjectProperty { key: "c".into(), typ: Box::new(Typ::Float) }, + // ObjectProperty { key: "d".into(), typ: Box::new(Typ::Unknown) }, + // ]), + // default: None, + // has_default: false, + // oidx: None + // },], + // no_main_func: Some(false), + // has_preprocessor: None, + // }, + // sig + // ); + // } + + #[test] + fn test_nu_list_sig() { + let sig = parse_nu_signature( + r#" + def main [ foo: list ] { } + "#, + ) + .unwrap(); + + println!("{}", serde_json::to_string_pretty(&sig).unwrap()); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![Arg { + name: "foo".into(), + otyp: None, + typ: Typ::List(Box::new(Typ::Float)), + default: None, + has_default: false, + oidx: None + },], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + #[test] + fn test_nu_list_full_sig() { + let sig = parse_nu_signature( + r#" + def main [ a, foo: list = [ 2, 3, 4 ], b ] { } + "#, + ) + .unwrap(); + + println!("{}", serde_json::to_string_pretty(&sig).unwrap()); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "a".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "foo".into(), + otyp: None, + typ: Typ::List(Box::new(Typ::Float)), + default: Some(json!([2, 3, 4])), + has_default: true, + oidx: None + }, + Arg { + name: "b".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + + #[test] + fn test_nu_datetime_sig() { + let sig = parse_nu_signature( + r#" + def main [ foo: datetime ] { } + "#, + ) + .unwrap(); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![Arg { + name: "foo".into(), + otyp: None, + typ: Typ::Datetime, + default: None, + has_default: false, + oidx: None + },], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + // TODO: Re-enable for V1 + // #[test] + // fn test_nu_table_sig() { + // let sig = parse_nu_signature( + // r#" + // def main [ foo: table] { } + // "#, + // ) + // .unwrap(); + + // assert_eq!( + // MainArgSignature { + // star_args: false, + // star_kwargs: false, + // args: vec![Arg { + // name: "foo".into(), + // otyp: None, + // typ: Typ::List(Box::new(Typ::Object(vec![ + // ObjectProperty { key: "a".into(), typ: Box::new(Typ::Unknown) }, + // ObjectProperty { key: "b".into(), typ: Box::new(Typ::Float) }, + // ObjectProperty { key: "c".into(), typ: Box::new(Typ::Str(None)) }, + // ]))), + // default: None, + // has_default: false, + // oidx: None + // },], + // no_main_func: Some(false), + // has_preprocessor: None, + // }, + // sig + // ); + // } + + #[test] + fn test_nu_wrapup_sig() { + let sig = parse_nu_signature( + r#" + def main [a ,b :int,c? , d: string = "foo", bi?: any] {} + "#, + ) + .unwrap(); + + assert_eq!( + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + name: "a".into(), + otyp: None, + typ: Typ::Unknown, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "b".into(), + otyp: None, + typ: Typ::Int, + default: None, + has_default: false, + oidx: None + }, + Arg { + name: "c".into(), + otyp: None, + typ: Typ::Unknown, + default: Some(serde_json::Value::Null), + has_default: true, + oidx: None + }, + Arg { + name: "d".into(), + otyp: None, + typ: Typ::Str(None), + default: Some(json!("foo")), + has_default: true, + oidx: None + }, + Arg { + name: "bi".into(), + otyp: None, + typ: Typ::Unknown, + default: Some(serde_json::Value::Null), + has_default: true, + oidx: None + } + ], + no_main_func: Some(false), + has_preprocessor: None, + }, + sig + ); + } + // #[test] + // fn test_nu_wrapup_nested_sig() { + // let sig = parse_nu_signature( + // r#" + // def main [ + // baz: string, + // foo: record, + // d: record> + // = + // { + // a: "a", + // b: 3, + // c: [ 2, 3, 4 ], + // d: { + // a: true, + // b: false, + // c: true + // } + // } + // ] { } + // "#, + // ) + // .unwrap(); + + // println!("{}", serde_json::to_string_pretty(&sig).unwrap()); + + // assert_eq!( + // MainArgSignature { + // star_args: false, + // star_kwargs: false, + // args: vec![ + // Arg { + // name: "baz".into(), + // otyp: None, + // typ: Typ::Str(None), + // default: None, + // has_default: false, + // oidx: None + // }, + // Arg { + // name: "foo".into(), + // otyp: None, + // typ: Typ::Object(vec![ + // ObjectProperty { key: "a".into(), typ: Box::new(Typ::Str(None)) }, + // ObjectProperty { key: "b".into(), typ: Box::new(Typ::Unknown) }, + // ObjectProperty { + // key: "c".into(), + // typ: Box::new(Typ::List(Box::new(Typ::Float))) + // }, + // ObjectProperty { + // key: "d".into(), + // typ: Box::new(Typ::Object(vec![ + // ObjectProperty { + // key: "a1".into(), + // typ: Box::new(Typ::Unknown) + // }, + // ObjectProperty { + // key: "b1".into(), + // typ: Box::new(Typ::Unknown) + // }, + // ObjectProperty { + // key: "c1".into(), + // typ: Box::new(Typ::Unknown) + // } + // ])) + // }, + // ]), + // default: Some(json!({ + // "a": "a", + // "b": 3, + // "c": [ + // 2, + // 3, + // 4 + // ], + // "d": { + // "a": true, + // "b": false, + // "c": true + // } + // })), + // has_default: true, + // oidx: None + // }, + // ], + // no_main_func: Some(false), + // has_preprocessor: None, + // }, + // sig + // ); + // } + #[test] + fn test_nu_nested_extra_types() { + assert_eq!( + parse_nu_signature( + r#" + def main [a: list] {} + "#, + ) + .is_err(), + true + ); + assert_eq!( + parse_nu_signature( + r#" + def main [a: list] {} + "#, + ) + .is_err(), + true + ); + assert_eq!( + parse_nu_signature( + r#" + def main [a: list] {} + "#, + ) + .is_err(), + true + ); + assert_eq!( + parse_nu_signature( + r#" + def main [a: list] {} + "#, + ) + .is_err(), + true + ); + assert_eq!( + parse_nu_signature( + r#" + def main [a: list>] {} + "#, + ) + .is_err(), + true + ); + } +} diff --git a/backend/parsers/windmill-parser-py-imports/Cargo.toml b/backend/parsers/windmill-parser-py-imports/Cargo.toml index 7bc558f9c0..abd363b42b 100644 --- a/backend/parsers/windmill-parser-py-imports/Cargo.toml +++ b/backend/parsers/windmill-parser-py-imports/Cargo.toml @@ -27,3 +27,6 @@ anyhow.workspace = true lazy_static.workspace = true sqlx.workspace = true async-recursion.workspace = true +toml.workspace = true +serde.workspace = true +pep440_rs.workspace = true diff --git a/backend/parsers/windmill-parser-py-imports/src/lib.rs b/backend/parsers/windmill-parser-py-imports/src/lib.rs index 77324f2384..97ac1c848f 100644 --- a/backend/parsers/windmill-parser-py-imports/src/lib.rs +++ b/backend/parsers/windmill-parser-py-imports/src/lib.rs @@ -6,11 +6,14 @@ * LICENSE-AGPL for a copy of the license. */ +mod mapping; + use async_recursion::async_recursion; use itertools::Itertools; use lazy_static::lazy_static; -use phf::phf_map; +use std::{collections::HashMap, str::FromStr}; +use mapping::{FULL_IMPORTS_MAP, SHORT_IMPORTS_MAP}; #[cfg(not(target_arch = "wasm32"))] use regex::Regex; #[cfg(target_arch = "wasm32")] @@ -18,69 +21,37 @@ use regex_lite::Regex; use rustpython_parser::{ ast::{Stmt, StmtImport, StmtImportFrom, Suite}, + text_size::TextRange, Parse, }; use sqlx::{Pool, Postgres}; -use windmill_common::{error, worker::PythonAnnotations}; - -const DEF_MAIN: &str = "def main("; - -static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_map! { - "psycopg2" => "psycopg2-binary", - "psycopg" => "psycopg[binary, pool]", - "yaml" => "pyyaml", - "git" => "GitPython", - "shopify" => "ShopifyAPI", - "seleniumwire" => "selenium-wire", - "openbb-terminal" => "openbb[all]", - "riskfolio" => "riskfolio-lib", - "smb" => "pysmb", - "PIL" => "Pillow", - "googleapiclient" => "google-api-python-client", - "googlecloudbigquery" => "google-cloud-bigquery", - "dateutil" => "python-dateutil", - "mailparser" => "mail-parser", - "mailparser-reply" => "mail-parser-reply", - "gitlab" => "python-gitlab", - "smbclient" => "smbprotocol", - "playhouse" => "peewee", - "dns" => "dnspython", - "msoffcrypto" => "msoffcrypto-tool", - "tabula" => "tabula-py", - "shapefile" => "pyshp", - "sklearn" => "scikit-learn", - "umap" => "umap-learn", - "cv2" => "opencv-python", - "atlassian" => "atlassian-python-api", - "mysql" => "mysql-connector-python", - "tenable" => "pytenable", - "ns1" => "ns1-python", - "pymsql" => "PyMySQL", - "haystack" => "haystack-ai", - "github" => "PyGithub", - "ldap" => "python-ldap", - "opensearchpy" => "opensearch-py", - "lokalise" => "python-lokalise-api", - "msgraph" => "msgraph-sdk", - "pythonjsonlogger" => "python-json-logger", - "socks" => "PySocks", - "taiga" => "python-taiga", - "docx" => "python-docx", +use windmill_common::{ + error::{self, to_anyhow}, + worker::PythonAnnotations, }; fn replace_import(x: String) -> String { - PYTHON_IMPORTS_REPLACEMENT + SHORT_IMPORTS_MAP .get(&x) .map(|x| x.to_owned()) .unwrap_or(&x) .to_string() } -lazy_static! { - static ref RE: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap(); +fn replace_full_import(x: &str) -> Option { + FULL_IMPORTS_MAP.get(x).map(|x| (*x).to_owned()) } -fn process_import(module: Option, path: &str, level: usize) -> Vec { +lazy_static! { + static ref RE: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap(); + static ref PIN_RE: Regex = Regex::new(r"(?:\s*#\s*(pin|repin):\s*)(\S*)").unwrap(); + static ref PKG_RE: Regex = Regex::new(r"^([^!=<>]+)(?:[!=<>]|$)").unwrap(); + // Regex to properly match main function definition at line start, + // capturing both sync and async variants + static ref DEF_MAIN_RE: Regex = Regex::new(r"(?m)^(async\s+)?def\s+main\s*\(").unwrap(); +} + +fn process_import(module: Option, path: &str, level: usize) -> Vec { if level > 0 { let mut imports = vec![]; let splitted_path = path.split("/"); @@ -89,17 +60,18 @@ fn process_import(module: Option, path: &str, level: usize) -> Vec error::Result Some(path), + _ => None, }) .collect()); } -fn parse_code_for_imports(code: &str, path: &str) -> error::Result> { - let mut code = code.split(DEF_MAIN).next().unwrap_or("").to_string(); +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +enum NImport { + // Order matters! First we want to resolve all repins + + // manually repinned requirement + // e.g.: + // import pandas # repin: pandas==x.y.z + Repin { + pin: ImportPin, + key: String, + }, + // manually pinned requirements + // e.g.: + // import pandas # pin: pandas>=x.y.z + // import pandas # pin: pandas<=x.y.z + // + // NOTE: It is possible for multiple pins exist on same import + // That's why we store vector of pins + Pin { + pins: Vec, + key: String, + }, + // Automatically inferred requirement + // e.g.: + // import pandas + Auto { + // Take `x.y.z` for example + // x is going to be the `root` + // and x.y.z is `full` + // + // `full` will be None if it is equal to root + // + // We will use `root` as a requirement name and pass to `uv pip compile` if it was not replaced with any pin + pkg: String, + + // However we still need full, since all pins pin against full import names + key: Option, + }, + // Relative imports + Relative(String), +} +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +enum NImportResolved { + Repin { pin: ImportPin, key: String }, + Pin { pins: Vec, key: String }, + Auto { pkg: String, key: Option }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +struct ImportPin { + pkg: String, + path: String, +} + +fn parse_code_for_imports(code: &str, path: &str) -> error::Result> { + // Use regex to safely find the main function definition + let mut code = DEF_MAIN_RE + .split(code) + .next() + .unwrap_or_default() + .to_string(); // remove main function decorator from end of file if it exists if code @@ -138,22 +166,64 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result> + "\n"; } - let ast = Suite::parse(&code, "main.py").map_err(|e| { + // Add a fake main function to ensure the parser can process the code correctly + // This is needed because we've split off the real main function above + let code_with_fake_main = format!("{}\n\ndef main(): pass", code); + + let ast = Suite::parse(&code_with_fake_main, "main.py").map_err(|e| { error::Error::ExecutionErr(format!("Error parsing code for imports: {}", e.to_string())) })?; - let nimports: Vec = ast + + // Note: We're still using the original code for finding pins, + // as the TextRange values from the parsed AST would be based on code_with_fake_main + // but we want to match against the original code + let find_pin = |range: TextRange, key: String| { + let hs = code + .chars() + .skip(range.end().to_usize()) + .take_while(|e| *e != '\n') + .collect::(); + + if hs.trim_start().is_empty() { + return None; + } + + PIN_RE.captures(&hs).and_then(|x| { + x.get(1).zip(x.get(2)).and_then(|(ty_m, pkg_m)| { + let pkg = pkg_m.as_str().to_owned(); + if ty_m.as_str() == "pin" { + Some(vec![NImport::Pin { + pins: vec![ImportPin { pkg, path: path.to_owned() }], + key, + }]) + } else if ty_m.as_str() == "repin" { + Some(vec![NImport::Repin { + pin: ImportPin { pkg, path: path.to_owned() }, + key, + }]) + } else { + None + } + }) + }) + }; + + let mut nimports: Vec = ast .into_iter() .filter_map(|x| match x { - Stmt::Import(StmtImport { names, .. }) => Some( - names - .into_iter() - .map(|x| { - let name = x.name.to_string(); - process_import(Some(name), path, 0) - }) - .flatten() - .collect::>(), - ), + Stmt::Import(StmtImport { names, range }) => names + .get(0) + .and_then(|al| find_pin(range, al.name.to_string())) + .or(Some( + names + .into_iter() + .map(|x| { + let name = x.name.to_string(); + process_import(Some(name), path, 0) + }) + .flatten() + .collect::>(), + )), Stmt::ImportFrom(StmtImportFrom { level: Some(i), module, .. }) if i.to_u32() > 0 => { Some(process_import( module.map(|x| x.to_string()), @@ -161,15 +231,25 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result> i.to_usize(), )) } - Stmt::ImportFrom(StmtImportFrom { level: _, module, .. }) => { - Some(process_import(module.map(|x| x.to_string()), path, 0)) - } + Stmt::ImportFrom(StmtImportFrom { level: _, module, range, .. }) => find_pin( + range, + module.clone().map(|x| x.to_string()).unwrap_or_default(), + ) + .or(Some(process_import(module.map(|x| x.to_string()), path, 0))), _ => None, }) .flatten() - .filter(|x| !STDIMPORTS.contains(&x.as_str())) + .filter(|x| { + if let NImport::Auto { ref pkg, .. } = x { + !STDIMPORTS.contains(&(*pkg).as_str()) + } else { + true + } + }) .unique() .collect(); + + nimports.sort(); return Ok(nimports); } @@ -178,19 +258,60 @@ pub async fn parse_python_imports( w_id: &str, path: &str, db: &Pool, - already_visited: &mut Vec, - annotated_pyv_numeric: &mut Option, -) -> error::Result> { - parse_python_imports_inner( + version_specifiers: &mut Vec, +) -> error::Result<(Vec, Option)> { + let mut compile_error_hint: Option = None; + let mut imports = parse_python_imports_inner( code, w_id, path, db, - already_visited, - annotated_pyv_numeric, - &mut annotated_pyv_numeric.and_then(|_| Some(path.to_owned())), + &mut vec![], + version_specifiers, + // &mut version_specifier.and_then(|_| Some(path.to_owned())), + &mut None ) - .await + .await? + .into_values() + .map(|nimport| match nimport { + NImportResolved::Pin { pins, .. } => pins.into_iter().map(|p| { + if let Some(hint) = &mut compile_error_hint{ + hint.push_str(&format!("\n - pin to {} in {}", p.pkg, p.path)); + } else { + compile_error_hint = Some("\n\nMultiple pins can cause problems during lockfile resolution.\nMake sure you checked every pin for conflicts:\n".into()) + }; + Ok(p.pkg) + }).collect_vec(), + NImportResolved::Repin { pin: ImportPin { pkg, .. }, .. } => vec![Ok(pkg)], + NImportResolved::Auto { pkg, key } => vec![ + + if let Some(key) = key { + Ok(format!("{pkg} # (mapped from {key})")) + } else { + Ok(pkg) + } + ], + }) + .flatten() + .collect::>>()? + .into_iter() + .filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty()) + .unique() + .collect_vec(); + + imports.sort(); + + compile_error_hint + .as_mut() + .map(|e| e.push_str("\n\nNOTE: You can also `repin` to override all pins")); + Ok((imports, compile_error_hint)) +} + +fn extract_pkg_name(requirement: &str) -> String { + PKG_RE + .captures(requirement) + .map(|x| x.get(1).map(|m| m.as_str().to_string()).unwrap_or_default()) + .unwrap_or_default() } #[async_recursion] @@ -200,11 +321,34 @@ async fn parse_python_imports_inner( path: &str, db: &Pool, already_visited: &mut Vec, - annotated_pyv_numeric: &mut Option, + version_specifiers: &mut Vec, path_where_annotated_pyv: &mut Option, -) -> error::Result> { +) -> error::Result> { 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: @@ -219,101 +363,271 @@ 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 - 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()) - ))); + #[derive(serde::Serialize, serde::Deserialize)] + struct InlineMetadata { + requires_python: String, + dependencies: Vec, + } + + 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::() + .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())?; } - } else { - *annotated_pyv_numeric = Some(numeric); - } + }; - *path_where_annotated_pyv = Some(path.to_owned()); + 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(); + let key = extract_pkg_name(&requirement); + requirements.insert( + key.clone(), + NImportResolved::Pin { + pins: vec![ImportPin { + pkg: requirement.clone(), + path: Default::default(), + }], + 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(); } - Ok(()) - }; - - check(py310, 310)?; - check(py311, 311)?; - check(py312, 312)?; - check(py313, 313)?; - - let find_requirements = code - .lines() - .find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:")); - if let Some((pos, _)) = find_requirements { - let lines = code - .lines() - .skip(pos + 1) - .map_while(|x| { - RE.captures(x) - .map(|x| x.get(1).unwrap().as_str().to_string()) - }) - .collect(); - Ok(lines) + Ok(requirements) } else { let find_extra_requirements = code.lines().find_position(|x| { x.starts_with("#extra_requirements:") || x.starts_with("# extra_requirements:") }); - let mut imports: Vec = vec![]; + let mut imports: HashMap = HashMap::new(); if let Some((pos, _)) = find_extra_requirements { - let lines: Vec = code - .lines() + code.lines() .skip(pos + 1) .map_while(|x| { - RE.captures(x) - .map(|x| x.get(1).unwrap().as_str().to_string()) + RE.captures(x).and_then(|x| { + x.get(1).map(|m| { + let requirement = m.as_str().to_string(); + let key = extract_pkg_name(&requirement); + imports.insert( + key.clone(), + NImportResolved::Pin { + pins: vec![ImportPin { + pkg: requirement, + path: Default::default(), + }], + key, + }, + ); + }) + }) }) - .collect(); - imports.extend(lines); + .collect_vec(); } - let nimports = parse_code_for_imports(code, path)?; - for n in nimports.iter() { - let nested = if n.starts_with("relative:") { - let rpath = n.replace("relative:", ""); - let code = sqlx::query_scalar!( - r#" - SELECT content FROM script WHERE path = $1 AND workspace_id = $2 - AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND - workspace_id = $2) - "#, - &rpath, - w_id - ) - .fetch_optional(db) - .await? - .unwrap_or_else(|| "".to_string()); + // Will get unsorted vector of imports found in current script + let mut nimports = parse_code_for_imports(code, path)?; - if already_visited.contains(&rpath) { - vec![] - } else { - already_visited.push(rpath.clone()); - parse_python_imports_inner( - &code, - w_id, + // It is important to note, that sorting is important and will always result in this pattern: + // 1. All Repins go first + // 2. All Pins go second + // 3. All Auto go third + // 4. All relative imports go the last + // + // This way we make sure all repins are resolved before (re)pins inside imported relative scripts. + nimports.sort(); + + for n in nimports.into_iter() { + let mut nested = match n { + NImport::Relative(rpath) => { + let code = sqlx::query_scalar!( + r#" + SELECT content FROM script WHERE path = $1 AND workspace_id = $2 + AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND + workspace_id = $2) + "#, &rpath, - db, - already_visited, - annotated_pyv_numeric, - path_where_annotated_pyv, + w_id ) + .fetch_optional(db) .await? + .unwrap_or_else(|| "".to_string()); + + if already_visited.contains(&rpath) { + vec![] + } else { + already_visited.push(rpath.clone()); + // Because the algo goes depth first, this function will never return relative import + // This why we can safely assume later, that there is no relative imports + parse_python_imports_inner( + &code, + w_id, + &rpath, + db, + already_visited, + version_specifiers, + path_where_annotated_pyv, + ) + .await? + .into_values() + .collect_vec() + } } - } else { - vec![replace_import(n.to_string())] + NImport::Repin { pin, key } => vec![NImportResolved::Repin { pin, key }], + NImport::Pin { pins, key } => vec![NImportResolved::Pin { pins, key }], + NImport::Auto { pkg, key } => vec![NImportResolved::Auto { pkg, key }], }; + + // Nested should also be sorted for the same reason + nested.sort(); + + // At this point there should be no NImport::Relative in `nested` for imp in nested { - if !imports.contains(&imp) { - imports.push(imp); + let key = match imp.clone() { + NImportResolved::Pin { key, .. } => key, + NImportResolved::Repin { key, .. } => key, + NImportResolved::Auto { key, pkg } => key.unwrap_or(pkg), + }; + // Handled cases: + // + // 1. + // Error: Imported windmill scripts have different pins + // + // auto + // ├── pin:2 + // └── pin:1 + // + // Fix 1: + // + // auto + // ├── pin:1 + // └── pin:1 + // + // Fix 2: + // + // repin:1 + // ├── pin:2 + // └── pin:1 + // + // 2. + // Error: Imported windmill scripts have different pins + // + // pin:2 + // └── pin:1 + // + // Fix 1: + // + // auto + // └── pin:1 + // + // Fix 2: + // + // repin:2 + // └── pin:1 + // + // 3. repins allowed to be repinned again + // + // repin:2 + // └── repin:1 + // + match imp.clone() { + NImportResolved::Repin { .. } => { + if let Some(existing_import) = imports.get(&key) { + match existing_import { + // replace + p if matches!( + p, + NImportResolved::Pin { .. } | NImportResolved::Auto { .. } + ) => + { + imports.insert(key, imp); + } + // do nothing (older repins have greater precedence) + NImportResolved::Repin { .. } => {} + // Should not be possible + _ => { + return Err(anyhow::anyhow!( + "Internal error: cannot resolve requirement pins", + ) + .into()); + } + } + } else { + imports.insert(key, imp.clone()); + } + } + NImportResolved::Pin { pins: new_pins, .. } => { + if let Some(existing_import) = imports.get_mut(&key) { + match existing_import { + // Check if pin is the same version, if same, do nothing, if not error + NImportResolved::Pin { pins: existing_pins, .. } => { + existing_pins.extend(new_pins) + } + // do nothing + NImportResolved::Repin { .. } => {} + // Replace with new pin + NImportResolved::Auto { .. } => { + imports.insert(key, imp); + } + } + } else { + imports.insert(key, imp.clone()); + } + } + NImportResolved::Auto { .. } => { + if !imports.contains_key(&key) { + imports.insert(key, imp); + } + } } } } - imports.sort(); Ok(imports) } } diff --git a/backend/parsers/windmill-parser-py-imports/src/mapping.rs b/backend/parsers/windmill-parser-py-imports/src/mapping.rs new file mode 100644 index 0000000000..1bd0688951 --- /dev/null +++ b/backend/parsers/windmill-parser-py-imports/src/mapping.rs @@ -0,0 +1,384 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use phf::phf_map; +type PyMap = phf::Map<&'static str, &'static str>; + +/// In some cases inferring requirement from import is not possible. +/// That's why we need to map import to requirement ident. +/// These two maps allows us to do so. + +/// import x.y.z +/// ^^^^^ replaces entire x.y.z import with [?] +/// +pub static FULL_IMPORTS_MAP: PyMap = phf_map! { + // import => requirement + "google.cloud.bigquery_storage" => "google-cloud-bigquery-storage", + "google.cloud.bigquery" => "google-cloud-bigquery", + "google.cloud.parametermanager" => "google-cloud-parametermanager", + "google.cloud.oracledatabase" => "google-cloud-oracledatabase", + "google.cloud.deploy" => "google-cloud-deploy", + "google.cloud.pubsub" => "google-cloud-pubsub", + "google.cloud.workflows" => "google-cloud-workflows", + "google.cloud.managedkafka" => "google-cloud-managedkafka", + "google.cloud.iam" => "google-cloud-iam", + "google.cloud.documentai" => "google-cloud-documentai", + "google.cloud.dlp" => "google-cloud-dlp", + "google.cloud.compute" => "google-cloud-compute", + "google.cloud.alloydb" => "google-cloud-alloydb", + "google.cloud.aiplatform" => "google-cloud-aiplatform", + "google.cloud.bigtable" => "google-cloud-bigtable", + "google.cloud.workstations" => "google-cloud-workstations", + "google.cloud.websecurityscanner" => "google-cloud-websecurityscanner", + "google.cloud.webrisk" => "google-cloud-webrisk", + "google.cloud.vmwareengine" => "google-cloud-vmwareengine", + "google.cloud.visionai" => "google-cloud-visionai", + "google.cloud.vision" => "google-cloud-vision", + "google.cloud.videointelligence" => "google-cloud-videointelligence", + "google.cloud.translate" => "google-cloud-translate", + "google.cloud.trace" => "google-cloud-trace", + "google.cloud.tpu" => "google-cloud-tpu", + "google.cloud.texttospeech" => "google-cloud-texttospeech", + "google.cloud.telcoautomation" => "google-cloud-telcoautomation", + "google.cloud.tasks" => "google-cloud-tasks", + "google.cloud.talent" => "google-cloud-talent", + "google.cloud.support" => "google-cloud-support", + "google.cloud.storageinsights" => "google-cloud-storageinsights", + "google.cloud.speech" => "google-cloud-speech", + "google.cloud.shell" => "google-cloud-shell", + "google.cloud.servicehealth" => "google-cloud-servicehealth", + "google.cloud.securitycentermanagement" => "google-cloud-securitycentermanagement", + "google.cloud.securitycenter" => "google-cloud-securitycenter", + "google.cloud.securesourcemanager" => "google-cloud-securesourcemanager", + "google.cloud.scheduler" => "google-cloud-scheduler", + "google.cloud.run" => "google-cloud-run", + "google.cloud.retail" => "google-cloud-retail", + "google.cloud.recommender" => "google-cloud-recommender", + "google.cloud.rapidmigrationassessment" => "google-cloud-rapidmigrationassessment", + "google.cloud.privilegedaccessmanager" => "google-cloud-privilegedaccessmanager", + "google.cloud.policytroubleshooter_iam" => "google-cloud-policytroubleshooter-iam", + "google.cloud.policysimulator" => "google-cloud-policysimulator", + "google.cloud.parallelstore" => "google-cloud-parallelstore", + "google.cloud.optimization" => "google-cloud-optimization", + "google.cloud.notebooks" => "google-cloud-notebooks", + "google.cloud.network_services" => "google-cloud-network-services", + "google.cloud.network_security" => "google-cloud-network-security", + "google.cloud.netapp" => "google-cloud-netapp", + "google.cloud.monitoring" => "google-cloud-monitoring", + "google.cloud.modelarmor" => "google-cloud-modelarmor", + "google.cloud.migrationcenter" => "google-cloud-migrationcenter", + "google.cloud.memorystore" => "google-cloud-memorystore", + "google.cloud.memcache" => "google-cloud-memcache", + "google.cloud.language" => "google-cloud-language", + "google.cloud.kms" => "google-cloud-kms", + "google.cloud.kms_inventory" => "google-cloud-kms-inventory", + "google.cloud.ids" => "google-cloud-ids", + "google.cloud.iap" => "google-cloud-iap", + "google.cloud.gsuiteaddons" => "google-cloud-gsuiteaddons", + "google.cloud.gke_multicloud" => "google-cloud-gke-multicloud", + "google.cloud.gke_backup" => "google-cloud-gke-backup", + "google.cloud.gdchardwaremanagement" => "google-cloud-gdchardwaremanagement", + "google.cloud.functions" => "google-cloud-functions", + "google.cloud.financialservices" => "google-cloud-financialservices", + "google.cloud.filestore" => "google-cloud-filestore", + "google.cloud.eventarc" => "google-cloud-eventarc", + "google.cloud.eventarc_publishing" => "google-cloud-eventarc-publishing", + "google.cloud.essential_contacts" => "google-cloud-essential-contacts", + "google.cloud.enterpriseknowledgegraph" => "google-cloud-enterpriseknowledgegraph", + "google.cloud.edgenetwork" => "google-cloud-edgenetwork", + "google.cloud.edgecontainer" => "google-cloud-edgecontainer", + "google.cloud.domains" => "google-cloud-domains", + "google.cloud.discoveryengine" => "google-cloud-discoveryengine", + "google.cloud.dialogflow" => "google-cloud-dialogflow", + "google.cloud.developerconnect" => "google-cloud-developerconnect", + "google.cloud.datastream" => "google-cloud-datastream", + "google.cloud.dataproc" => "google-cloud-dataproc", + "google.cloud.dataplex" => "google-cloud-dataplex", + "google.cloud.datalabeling" => "google-cloud-datalabeling", + "google.cloud.dataform" => "google-cloud-dataform", + "google.cloud.datacatalog" => "google-cloud-datacatalog", + "google.cloud.datacatalog_lineage" => "google-cloud-datacatalog-lineage", + "google.cloud.data_fusion" => "google-cloud-data-fusion", + "google.cloud.contentwarehouse" => "google-cloud-contentwarehouse", + "google.cloud.container" => "google-cloud-container", + "google.cloud.config" => "google-cloud-config", + "google.cloud.confidentialcomputing" => "google-cloud-confidentialcomputing", + "google.cloud.common" => "google-cloud-common", + "google.cloud.cloudcontrolspartner" => "google-cloud-cloudcontrolspartner", + "google.cloud.channel" => "google-cloud-channel", + "google.cloud.certificate_manager" => "google-cloud-certificate-manager", + "google.cloud.billing" => "google-cloud-billing", + "google.cloud.bigquery_migration" => "google-cloud-bigquery-migration", + "google.cloud.bigquery_logging" => "google-cloud-bigquery-logging", + "google.cloud.bigquery_datatransfer" => "google-cloud-bigquery-datatransfer", + "google.cloud.bigquery_datapolicies" => "google-cloud-bigquery-datapolicies", + "google.cloud.bigquery_connection" => "google-cloud-bigquery-connection", + "google.cloud.bigquery_biglake" => "google-cloud-bigquery-biglake", + "google.cloud.bigquery_analyticshub" => "google-cloud-bigquery-analyticshub", + "google.cloud.beyondcorp_clientgateways" => "google-cloud-beyondcorp-clientgateways", + "google.cloud.beyondcorp_clientconnectorservices" => "google-cloud-beyondcorp-clientconnectorservices", + "google.cloud.beyondcorp_appgateways" => "google-cloud-beyondcorp-appgateways", + "google.cloud.beyondcorp_appconnectors" => "google-cloud-beyondcorp-appconnectors", + "google.cloud.beyondcorp_appconnections" => "google-cloud-beyondcorp-appconnections", + "google.cloud.batch" => "google-cloud-batch", + "google.cloud.bare_metal_solution" => "google-cloud-bare-metal-solution", + "google.cloud.backupdr" => "google-cloud-backupdr", + "google.cloud.automl" => "google-cloud-automl", + "google.cloud.asset" => "google-cloud-asset", + "google.cloud.apphub" => "google-cloud-apphub", + "google.cloud.apihub" => "google-cloud-apihub", + "google.cloud.advisorynotifications" => "google-cloud-advisorynotifications", + "google.cloud.spanner" => "google-cloud-spanner", + "google.cloud.storage" => "google-cloud-storage", + "google.cloud.firestore" => "google-cloud-firestore", + "google.cloud.pubsublite" => "google-cloud-pubsublite", + "google.cloud.datastore" => "google-cloud-datastore", + "google.cloud.ndb" => "google-cloud-ndb", + "google.cloud.dns" => "google-cloud-dns", + "google.cloud.runtimeconfig" => "google-cloud-runtimeconfig", + "google.cloud.iot" => "google-cloud-iot", + "google.generativeai" => "google-generativeai", + "google.genai" => "google-genai", + // Azure + "azure.mgmt.hybridkubernetes" => "azure-mgmt-hybridkubernetes", + "azure.mgmt.sql" => "azure-mgmt-sql", + "azure.mgmt.compute" => "azure-mgmt-compute", + "azure.mgmt.eventgrid" => "azure-mgmt-eventgrid", + "azure.mgmt.containerservice" => "azure-mgmt-containerservice", + "azure.mgmt.databox" => "azure-mgmt-databox", + "azure.mgmt.keyvault" => "azure-mgmt-keyvault", + "azure.mgmt.applicationinsights" => "azure-mgmt-applicationinsights", + "azure.mgmt.storage" => "azure-mgmt-storage", + "azure.mgmt.quota" => "azure-mgmt-quota", + "azure.mgmt.nginx" => "azure-mgmt-nginx", + "azure.mgmt.netapp" => "azure-mgmt-netapp", + "azure.mgmt.resource" => "azure-mgmt-resource", + "azure.mgmt.containerregistry" => "azure-mgmt-containerregistry", + "azure.mgmt.databoxedge" => "azure-mgmt-databoxedge", + "azure.mgmt.logz" => "azure-mgmt-logz", + "azure.mgmt.monitor" => "azure-mgmt-monitor", + "azure.mgmt.servicenetworking" => "azure-mgmt-servicenetworking", + "azure.mgmt.kusto" => "azure-mgmt-kusto", + "azure.mgmt.web" => "azure-mgmt-web", + "azure.mgmt.eventhub" => "azure-mgmt-eventhub", + "azure.mgmt.redis" => "azure-mgmt-redis", + "azure.mgmt.cosmosdb" => "azure-mgmt-cosmosdb", + "azure.mgmt.network" => "azure-mgmt-network", + "azure.mgmt.cognitiveservices" => "azure-mgmt-cognitiveservices", + "azure.mgmt.servicefabricmanagedclusters" => "azure-mgmt-servicefabricmanagedclusters", + "azure.mgmt.datafactory" => "azure-mgmt-datafactory", + "azure.mgmt.hybridcompute" => "azure-mgmt-hybridcompute", + "azure.mgmt.servicebus" => "azure-mgmt-servicebus", + "azure.mgmt.marketplaceordering" => "azure-mgmt-marketplaceordering", + "azure.mgmt.managedservices" => "azure-mgmt-managedservices", + "azure.mgmt.managementgroups" => "azure-mgmt-managementgroups", + "azure.mgmt.loganalytics" => "azure-mgmt-loganalytics", + "azure.mgmt.automation" => "azure-mgmt-automation", + "azure.mgmt.devtestlabs" => "azure-mgmt-devtestlabs", + "azure.mgmt.documentdb" => "azure-mgmt-documentdb", + "azure.mgmt.scheduler" => "azure-mgmt-scheduler", + "azure.mgmt.core" => "azure-mgmt-core", + "azure.mgmt.servermanager" => "azure-mgmt-servermanager", + "azure.mgmt.batchai" => "azure-mgmt-batchai", + "azure.mgmt.extendedlocation" => "azure-mgmt-extendedlocation", + "azure.mgmt.digitaltwins" => "azure-mgmt-digitaltwins", + "azure.mgmt.appconfiguration" => "azure-mgmt-appconfiguration", + "azure.mgmt.edgeorder" => "azure-mgmt-edgeorder", + "azure.mgmt.resourcehealth" => "azure-mgmt-resourcehealth", + "azure.mgmt.redhatopenshift" => "azure-mgmt-redhatopenshift", + "azure.mgmt.appplatform" => "azure-mgmt-appplatform", + "azure.mgmt.appcontainers" => "azure-mgmt-appcontainers", + "azure.mgmt.elastic" => "azure-mgmt-elastic", + "azure.mgmt.dnsresolver" => "azure-mgmt-dnsresolver", + "azure.mgmt.containerinstance" => "azure-mgmt-containerinstance", + "azure.mgmt.elasticsan" => "azure-mgmt-elasticsan", + "azure.mgmt.dns" => "azure-mgmt-dns", + "azure.mgmt.redisenterprise" => "azure-mgmt-redisenterprise", + "azure.mgmt.servicelinker" => "azure-mgmt-servicelinker", + "azure.mgmt.rdbms" => "azure-mgmt-rdbms", + "azure.mgmt.batch" => "azure-mgmt-batch", + "azure.mgmt.avs" => "azure-mgmt-avs", + "azure.mgmt.webpubsub" => "azure-mgmt-webpubsub", + "azure.mgmt.desktopvirtualization" => "azure-mgmt-desktopvirtualization", + "azure.mgmt.privatedns" => "azure-mgmt-privatedns", + "azure.mgmt.hdinsight" => "azure-mgmt-hdinsight", + "azure.mgmt.billing" => "azure-mgmt-billing", + "azure.mgmt.azurestackhci" => "azure-mgmt-azurestackhci", + "azure.mgmt.dataprotection" => "azure-mgmt-dataprotection", + "azure.mgmt.search" => "azure-mgmt-search", + "azure.mgmt.appcomplianceautomation" => "azure-mgmt-appcomplianceautomation", + "azure.mgmt.scvmm" => "azure-mgmt-scvmm", + "azure.mgmt.powerbiembedded" => "azure-mgmt-powerbiembedded", + "azure.mgmt.imagebuilder" => "azure-mgmt-imagebuilder", + "azure.mgmt.storagemover" => "azure-mgmt-storagemover", + "azure.mgmt.mobilenetwork" => "azure-mgmt-mobilenetwork", + "azure.mgmt.cdn" => "azure-mgmt-cdn", + "azure.mgmt.storagecache" => "azure-mgmt-storagecache", + "azure.mgmt.maintenance" => "azure-mgmt-maintenance", + "azure.mgmt.security" => "azure-mgmt-security", + "azure.mgmt.devcenter" => "azure-mgmt-devcenter", + "azure.mgmt.support" => "azure-mgmt-support", + "azure.mgmt.recoveryservicesbackup" => "azure-mgmt-recoveryservicesbackup", + "azure.mgmt.recoveryservices" => "azure-mgmt-recoveryservices", + "azure.mgmt.confidentialledger" => "azure-mgmt-confidentialledger", + "azure.mgmt.healthcareapis" => "azure-mgmt-healthcareapis", + "azure.mgmt.frontdoor" => "azure-mgmt-frontdoor", + "azure.mgmt.notificationhubs" => "azure-mgmt-notificationhubs", + "azure.mgmt.quantum" => "azure-mgmt-quantum", + "azure.mgmt.apimanagement" => "azure-mgmt-apimanagement", + "azure.mgmt.communication" => "azure-mgmt-communication", + "azure.mgmt.newrelicobservability" => "azure-mgmt-newrelicobservability", + "azure.mgmt.confluent" => "azure-mgmt-confluent", + "azure.mgmt.chaos" => "azure-mgmt-chaos", + "azure.mgmt.recoveryservicessiterecovery" => "azure-mgmt-recoveryservicessiterecovery", + "azure.mgmt.servicefabric" => "azure-mgmt-servicefabric", + "azure.mgmt.hybridcontainerservice" => "azure-mgmt-hybridcontainerservice", + "azure.mgmt.streamanalytics" => "azure-mgmt-streamanalytics", + "azure.mgmt.deviceupdate" => "azure-mgmt-deviceupdate", + "azure.mgmt.hybridnetwork" => "azure-mgmt-hybridnetwork", + "azure.mgmt.dashboard" => "azure-mgmt-dashboard", + "azure.mgmt.connectedvmware" => "azure-mgmt-connectedvmware", + "azure.mgmt.datadog" => "azure-mgmt-datadog", + "azure.mgmt.baremetalinfrastructure" => "azure-mgmt-baremetalinfrastructure", + "azure.mgmt.signalr" => "azure-mgmt-signalr", + "azure.mgmt.resourcemover" => "azure-mgmt-resourcemover", + "azure.mgmt.kubernetesconfiguration" => "azure-mgmt-kubernetesconfiguration", + "azure.mgmt.iothub" => "azure-mgmt-iothub", + "azure.mgmt.maps" => "azure-mgmt-maps", + "azure.mgmt.devspaces" => "azure-mgmt-devspaces", + "azure.mgmt.dynatrace" => "azure-mgmt-dynatrace", + "azure.mgmt.resourceconnector" => "azure-mgmt-resourceconnector", + "azure.mgmt.authorization" => "azure-mgmt-authorization", + "azure.mgmt.costmanagement" => "azure-mgmt-costmanagement", + "azure.mgmt.databricks" => "azure-mgmt-databricks", + "azure.mgmt.graphservices" => "azure-mgmt-graphservices", + "azure.mgmt.iothubprovisioningservices" => "azure-mgmt-iothubprovisioningservices", + "azure.mgmt.sqlvirtualmachine" => "azure-mgmt-sqlvirtualmachine", + "azure.mgmt.trafficmanager" => "azure-mgmt-trafficmanager", + "azure.mgmt.agfood" => "azure-mgmt-agfood", + "azure.mgmt.azureadb2c" => "azure-mgmt-azureadb2c", + "azure.mgmt.voiceservices" => "azure-mgmt-voiceservices", + "azure.mgmt.machinelearningservices" => "azure-mgmt-machinelearningservices", + "azure.mgmt.workloads" => "azure-mgmt-workloads", + "azure.mgmt.reservations" => "azure-mgmt-reservations", + "azure.mgmt.orbital" => "azure-mgmt-orbital", + "azure.mgmt.defendereasm" => "azure-mgmt-defendereasm", + "azure.mgmt.msi" => "azure-mgmt-msi", + "azure.mgmt.synapse" => "azure-mgmt-synapse", + "azure.mgmt.commerce" => "azure-mgmt-commerce", + "azure.mgmt.loadtesting" => "azure-mgmt-loadtesting", + "azure.mgmt.botservice" => "azure-mgmt-botservice", + "azure.mgmt.media" => "azure-mgmt-media", + "azure.mgmt.securitydevops" => "azure-mgmt-securitydevops", + "azure.mgmt.policyinsights" => "azure-mgmt-policyinsights", + "azure.mgmt.securityinsight" => "azure-mgmt-securityinsight", + "azure.mgmt.subscription" => "azure-mgmt-subscription", + "azure.mgmt.agrifood" => "azure-mgmt-agrifood", + "azure.mgmt.resourcegraph" => "azure-mgmt-resourcegraph", + "azure.mgmt.alertsmanagement" => "azure-mgmt-alertsmanagement", + "azure.mgmt.labservices" => "azure-mgmt-labservices", + "azure.mgmt.fluidrelay" => "azure-mgmt-fluidrelay", + "azure.mgmt.automanage" => "azure-mgmt-automanage", + "azure.mgmt.billingbenefits" => "azure-mgmt-billingbenefits", + "azure.mgmt.education" => "azure-mgmt-education", + "azure.mgmt.consumption" => "azure-mgmt-consumption", + "azure.mgmt.workloadmonitor" => "azure-mgmt-workloadmonitor", + "azure.mgmt.iotcentral" => "azure-mgmt-iotcentral", + "azure.mgmt.datamigration" => "azure-mgmt-datamigration", + "azure.mgmt.azurearcdata" => "azure-mgmt-azurearcdata", + "azure.mgmt.azurestack" => "azure-mgmt-azurestack", + "azure.mgmt.networkfunction" => "azure-mgmt-networkfunction", + "azure.mgmt.oep" => "azure-mgmt-oep", + "azure.mgmt.storagepool" => "azure-mgmt-storagepool", + "azure.mgmt.relay" => "azure-mgmt-relay", + "azure.mgmt.purview" => "azure-mgmt-purview", + "azure.mgmt.vmwarecloudsimple" => "azure-mgmt-vmwarecloudsimple", + "azure.mgmt.guestconfig" => "azure-mgmt-guestconfig", + "azure.mgmt.testbase" => "azure-mgmt-testbase", + "azure.mgmt.logic" => "azure-mgmt-logic", + "azure.mgmt.storageimportexport" => "azure-mgmt-storageimportexport", + "azure.mgmt.managementpartner" => "azure-mgmt-managementpartner", + "azure.mgmt.serialconsole" => "azure-mgmt-serialconsole", + "azure.mgmt.portal" => "azure-mgmt-portal", + "azure.mgmt.deploymentmanager" => "azure-mgmt-deploymentmanager", + "azure.mgmt.machinelearningcompute" => "azure-mgmt-machinelearningcompute", + "azure.mgmt.mixedreality" => "azure-mgmt-mixedreality", + "azure.mgmt.peering" => "azure-mgmt-peering", + "azure.mgmt.storagesync" => "azure-mgmt-storagesync", + "azure.mgmt.customproviders" => "azure-mgmt-customproviders", + "azure.mgmt.hanaonazure" => "azure-mgmt-hanaonazure", + "azure.mgmt.datashare" => "azure-mgmt-datashare", + "azure.mgmt.powerbidedicated" => "azure-mgmt-powerbidedicated", + "azure.mgmt.timeseriesinsights" => "azure-mgmt-timeseriesinsights", + "azure.mgmt.healthbot" => "azure-mgmt-healthbot", + "azure.mgmt.attestation" => "azure-mgmt-attestation", + "azure.mgmt.advisor" => "azure-mgmt-advisor", + "azure.mgmt.operationsmanagement" => "azure-mgmt-operationsmanagement", + "azure.mgmt.videoanalyzer" => "azure-mgmt-videoanalyzer", + "azure.mgmt.app" => "azure-mgmt-app", + "azure.mgmt.changeanalysis" => "azure-mgmt-changeanalysis", + "azure.mgmt.regionmove" => "azure-mgmt-regionmove", + "azure.mgmt.edgegateway" => "azure-mgmt-edgegateway", + "azure.mgmt.nspkg" => "azure-mgmt-nspkg", + "azure.keyvault.secrets" => "azure-keyvault-secrets", + "azure.storage.blob" => "azure-storage-blob", + "azure.storage.filedatalake" => "azure-storage-file-datalake", + // Add new entry here ^ +}; + +/// import x.y.z +/// ^ replaces x with [?] +/// +/// Additional rules: +/// 1. in x "_" are replaced with "-" +/// 2. If full imports had a hit, this one will not be called +pub static SHORT_IMPORTS_MAP: PyMap = phf_map! { + // import => requirement + "psycopg2" => "psycopg2-binary", + "psycopg" => "psycopg[binary, pool]", + "yaml" => "pyyaml", + "git" => "GitPython", + "shopify" => "ShopifyAPI", + "seleniumwire" => "selenium-wire", + "openbb-terminal" => "openbb[all]", + "riskfolio" => "riskfolio-lib", + "smb" => "pysmb", + "PIL" => "Pillow", + "googleapiclient" => "google-api-python-client", + "googlecloudbigquery" => "google-cloud-bigquery", + "dateutil" => "python-dateutil", + "mailparser" => "mail-parser", + "mailparser-reply" => "mail-parser-reply", + "gitlab" => "python-gitlab", + "smbclient" => "smbprotocol", + "playhouse" => "peewee", + "dns" => "dnspython", + "msoffcrypto" => "msoffcrypto-tool", + "tabula" => "tabula-py", + "shapefile" => "pyshp", + "sklearn" => "scikit-learn", + "umap" => "umap-learn", + "cv2" => "opencv-python", + "atlassian" => "atlassian-python-api", + "mysql" => "mysql-connector-python", + "tenable" => "pytenable", + "ns1" => "ns1-python", + "pymsql" => "PyMySQL", + "haystack" => "haystack-ai", + "github" => "PyGithub", + "ldap" => "python-ldap", + "opensearchpy" => "opensearch-py", + "lokalise" => "python-lokalise-api", + "msgraph" => "msgraph-sdk", + "pythonjsonlogger" => "python-json-logger", + "socks" => "PySocks", + "taiga" => "python-taiga", + "docx" => "python-docx", + "vt" => "vt-py", + // Add new entry here ^ +}; diff --git a/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql b/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql index d75bf4396c..590ce1bd5f 100644 --- a/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql +++ b/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql @@ -2580,4 +2580,4 @@ import innerdifffolder '{}', '', '', -'f/foobar/bar', -28028598712388159, 'python3', ''); \ No newline at end of file +'f/foobar/bar', -28028598712388159, 'python3', ''); diff --git a/backend/parsers/windmill-parser-py-imports/tests/tests.rs b/backend/parsers/windmill-parser-py-imports/tests/tests.rs index a634f247dd..b7fd2c0737 100644 --- a/backend/parsers/windmill-parser-py-imports/tests/tests.rs +++ b/backend/parsers/windmill-parser-py-imports/tests/tests.rs @@ -18,18 +18,18 @@ def main(): pass "; - let mut already_visited = vec![]; - let r = parse_python_imports( - code, - "test-workspace", - "f/foo/bar", - &db, - &mut already_visited, - &mut None, - ) - .await?; + let (r, ..) = + parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; // println!("{}", serde_json::to_string(&r)?); - assert_eq!(r, vec!["matplotlib", "wmill", "zanzibar"]); + assert_eq!( + r, + vec![ + "matplotlib # (mapped from matplotlib.pyplot)", + "wmill", + "zanzibar # (mapped from zanzibar.estonie)" + ] + ); + Ok(()) } @@ -51,16 +51,8 @@ def main(): pass "; - let mut already_visited = vec![]; - let r = parse_python_imports( - code, - "test-workspace", - "f/foo/bar", - &db, - &mut already_visited, - &mut None, - ) - .await?; + let (r, ..) = + parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; println!("{}", serde_json::to_string(&r)?); assert_eq!(r, vec!["burkina=0.4", "nigeria"]); @@ -81,17 +73,9 @@ def main(): pass "; - let mut already_visited = vec![]; - let r = parse_python_imports( - code, - "test-workspace", - "f/foo/bar", - &db, - &mut already_visited, - &mut None, - ) - .await?; + let (r, ..) = + parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; println!("{}", serde_json::to_string(&r)?); assert_eq!( r, diff --git a/backend/parsers/windmill-parser-py/src/lib.rs b/backend/parsers/windmill-parser-py/src/lib.rs index f5f9574888..22d2ea6516 100644 --- a/backend/parsers/windmill-parser-py/src/lib.rs +++ b/backend/parsers/windmill-parser-py/src/lib.rs @@ -57,6 +57,8 @@ fn filter_non_main(code: &str, main_name: &str) -> String { return filtered_code; } +/// skip_params is a micro optimization for when we just want to find the main +/// function without parsing all the params. pub fn parse_python_signature( code: &str, override_main: Option, @@ -205,7 +207,7 @@ fn parse_expr(e: &Box) -> (Typ, bool) { }; (Typ::Str(values), false) } - "List" => (Typ::List(Box::new(parse_expr(&x.slice).0)), false), + "List" | "list" => (Typ::List(Box::new(parse_expr(&x.slice).0)), false), "Optional" => (parse_expr(&x.slice).0, true), _ => (Typ::Unknown, false), }, @@ -230,7 +232,14 @@ fn parse_typ(id: &str) -> Typ { x @ _ if x.starts_with("DynSelect_") => { Typ::DynSelect(x.strip_prefix("DynSelect_").unwrap().to_string()) } - _ => Typ::Resource(id.to_string()), + _ => Typ::Resource(map_resource_name(id)), + } +} + +fn map_resource_name(x: &str) -> String { + match x { + "S3Object" => "s3_object".to_string(), + _ => x.to_string(), } } @@ -466,7 +475,7 @@ def main(test1: str, Arg { otyp: None, name: "s3o".to_string(), - typ: Typ::Resource("S3Object".to_string()), + typ: Typ::Resource("s3_object".to_string()), default: None, has_default: false, oidx: None diff --git a/backend/parsers/windmill-parser-sql/src/lib.rs b/backend/parsers/windmill-parser-sql/src/lib.rs index f5ddb73151..0fe72f0296 100644 --- a/backend/parsers/windmill-parser-sql/src/lib.rs +++ b/backend/parsers/windmill-parser-sql/src/lib.rs @@ -16,6 +16,9 @@ use std::{ }; pub use windmill_parser::{Arg, MainArgSignature, Typ}; +pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__"; +pub const SANITIZED_RAW_STRING_STR: &str = "__sanitized_raw_string__"; + pub fn parse_mysql_sig(code: &str) -> anyhow::Result { let parsed = parse_mysql_file(&code)?; if let Some(x) = parsed { @@ -80,6 +83,21 @@ pub fn parse_bigquery_sig(code: &str) -> anyhow::Result { } } +pub fn parse_duckdb_sig(code: &str) -> anyhow::Result { + 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 { let parsed = parse_snowflake_file(&code)?; if let Some(x) = parsed { @@ -117,6 +135,60 @@ pub fn parse_db_resource(code: &str) -> Option { cap.map(|x| x.get(1).map(|x| x.as_str().to_string()).unwrap()) } +#[derive(Clone, Copy, Debug)] +pub enum S3ModeFormat { + Json, + Csv, + Parquet, +} +pub fn s3_mode_extension(format: S3ModeFormat) -> &'static str { + match format { + S3ModeFormat::Json => "json", + S3ModeFormat::Csv => "csv", + S3ModeFormat::Parquet => "parquet", + } +} +pub struct S3ModeArgs { + pub prefix: Option, + pub storage: Option, + pub format: S3ModeFormat, +} +pub fn parse_s3_mode(code: &str) -> anyhow::Result> { + let cap = match RE_S3_MODE.captures(code) { + Some(x) => x, + None => return Ok(None), + }; + let args_str = cap + .get(1) + .map(|x| x.as_str().to_string()) + .unwrap_or_default(); + + let mut prefix = None; + let mut storage = None; + let mut format = S3ModeFormat::Json; + + for kv in args_str.split(' ').map(|kv| kv.trim()) { + if kv.is_empty() { + continue; + } + let mut it = kv.split('='); + let (Some(key), Some(value)) = (it.next(), it.next()) else { + return Err(anyhow!("Invalid S3 mode argument: {}", kv)); + }; + match (key.trim(), value.trim()) { + ("prefix", _) => prefix = Some(value.to_string()), + ("storage", _) => storage = Some(value.to_string()), + ("format", "json") => format = S3ModeFormat::Json, + ("format", "parquet") => format = S3ModeFormat::Parquet, + ("format", "csv") => format = S3ModeFormat::Csv, + ("format", format) => return Err(anyhow!("Invalid S3 mode format: {}", format)), + (_, _) => return Err(anyhow!("Invalid S3 mode argument: {}", kv)), + } + } + + Ok(Some(S3ModeArgs { prefix, storage, format })) +} + pub fn parse_sql_blocks(code: &str) -> Vec<&str> { let mut blocks = vec![]; let mut last_idx = 0; @@ -144,21 +216,28 @@ lazy_static::lazy_static! { static ref RE_NONEMPTY_SQL_BLOCK: Regex = Regex::new(r#"(?m)^\s*[^\s](?:[^-]|$)"#).unwrap(); static ref RE_DB: Regex = Regex::new(r#"(?m)^-- database (\S+) *(?:\r|\n|$)"#).unwrap(); + static ref RE_S3_MODE: Regex = Regex::new(r#"(?m)^-- s3( (.+))? *(?:\r|\n|$)"#).unwrap(); // -- $1 name (type) = default static ref RE_ARG_MYSQL: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); - pub static ref RE_ARG_MYSQL_NAMED: Regex = Regex::new(r#"(?m)^-- :([a-z_][a-z0-9_]*) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); + pub static ref RE_ARG_MYSQL_NAMED: Regex = Regex::new(r#"(?m)^-- :([a-z_][a-z0-9_]*) \((\w+(?:\([\w, ]+\))?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); static ref RE_ARG_PGSQL: Regex = Regex::new(r#"(?m)^-- \$(\d+) (\w+)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); // -- @name (type) = default static ref RE_ARG_BIGQUERY: Regex = Regex::new(r#"(?m)^-- @(\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); + // -- $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(); static ref RE_ARG_MSSQL: Regex = Regex::new(r#"(?m)^-- @(?:P|p)\d+ (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); + // used for `unsafe` sql interpolation + // -- %%name%% (type) = default + static ref RE_ARG_SQL_INTERPOLATION: Regex = Regex::new(r#"(?m)^--\s*%%([a-z_][a-z0-9_]*)%%\s*([\s\w\/]+)?(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); } fn parsed_default(parsed_typ: &Typ, default: String) -> Option { @@ -225,9 +304,34 @@ fn parse_oracledb_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } +fn parse_sql_sanitized_interpolation(code: &str) -> Vec { + let mut args: Vec = vec![]; + + for cap in RE_ARG_SQL_INTERPOLATION.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()); + let default = cap.get(3).map(|x| x.as_str().to_string()); + let has_default = default.is_some(); + let (parsed_typ, otyp) = parse_unsafe_typ(typ); + + let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x)); + args.push(Arg { + name, + typ: parsed_typ, + default: parsed_default, + otyp: Some(otyp.to_string()), + has_default, + oidx: None, + }); + } + + args +} + fn parse_mysql_file(code: &str) -> anyhow::Result>> { let mut args: Vec = vec![]; @@ -279,6 +383,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -431,6 +536,7 @@ fn parse_pg_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -485,6 +591,36 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); + Ok(Some(args)) +} + +fn parse_duckdb_file(code: &str) -> anyhow::Result>> { + let mut args: Vec = 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)) } @@ -513,6 +649,7 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -541,9 +678,25 @@ fn parse_mssql_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } +fn parse_unsafe_typ(typ: Option<&str>) -> (Typ, &'static str) { + match typ { + Some(s) => { + let variants = s + .split("/") + .map(|x| x.trim().to_string()) + .filter(|x| !x.is_empty()) + .collect(); + + (Typ::Str(Some(variants)), SANITIZED_ENUM_STR) + } + None => (Typ::Str(None), SANITIZED_RAW_STRING_STR), + } +} + pub fn parse_mysql_typ(typ: &str) -> Typ { match typ { "varchar" | "char" | "binary" | "varbinary" | "blob" | "text" | "enum" | "set" => { @@ -623,6 +776,33 @@ 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), @@ -1048,6 +1228,55 @@ SELECT @P2; } ); + Ok(()) + } + #[test] + fn test_parse_oracledb_sig() -> anyhow::Result<()> { + let code = r#" +-- :name1 (int) = 3 +-- :name2 (text) +-- :name4 (text) +SELECT :name, :name2; +SELECT * FROM table_name WHERE thing = :name4; +"#; + + println!("{:#?}", parse_oracledb_sig(code)?); + assert_eq!( + parse_oracledb_sig(code)?, + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + otyp: Some("int".to_string()), + name: "name1".to_string(), + typ: Typ::Int, + default: Some(json!(3)), + has_default: true, + oidx: None, + }, + Arg { + otyp: Some("text".to_string()), + name: "name2".to_string(), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None, + }, + Arg { + otyp: Some("text".to_string()), + name: "name4".to_string(), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None, + }, + ], + no_main_func: None, + has_preprocessor: None + } + ); + Ok(()) } } diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 78a83cd929..8ace23a1d3 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -29,12 +29,36 @@ use wasm_bindgen::prelude::*; struct ImportsFinder { imports: HashSet, + skip_type_only: bool, } impl Visit for ImportsFinder { noop_visit_type!(); fn visit_import_decl(&mut self, n: &swc_ecma_ast::ImportDecl) { + if self.skip_type_only { + if n.type_only { + return; + } + if n.specifiers.len() > 0 { + let mut is_type_only = true; + + for specifier in n.specifiers.iter() { + match specifier { + swc_ecma_ast::ImportSpecifier::Named( + swc_ecma_ast::ImportNamedSpecifier { is_type_only, .. }, + ) if *is_type_only => (), + _ => { + is_type_only = false; + break; + } + } + } + if is_type_only { + return; + } + } + } if let Some(ref s) = n.src.raw { let s = s.to_string(); if s.starts_with("'") && s.ends_with("'") { @@ -46,11 +70,15 @@ impl Visit for ImportsFinder { } } -pub fn parse_expr_for_imports(code: &str) -> anyhow::Result> { +pub fn parse_expr_for_imports(code: &str, skip_type_only: bool) -> anyhow::Result> { let cm: Lrc = Default::default(); let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into()); + let mut tss = TsSyntax::default(); + tss.disallow_ambiguous_jsx_like; + tss.tsx = true; + tss.no_early_errors = true; let lexer = Lexer::new( - Syntax::Typescript(TsSyntax::default()), + Syntax::Typescript(tss), // EsVersion defaults to es5 Default::default(), StringInput::from(&*fm), @@ -68,7 +96,7 @@ pub fn parse_expr_for_imports(code: &str) -> anyhow::Result> { anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}") })?; - let mut visitor = ImportsFinder { imports: HashSet::new() }; + let mut visitor = ImportsFinder { imports: HashSet::new(), skip_type_only }; visitor.visit_module(&expr); let mut imports: Vec<_> = visitor.imports.into_iter().collect(); @@ -131,6 +159,8 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result> { Ok(visitor.idents.into_iter().collect()) } +/// skip_params is a micro optimization for when we just want to find the main +/// function without parsing all the params. pub fn parse_deno_signature( code: &str, skip_dflt: bool, @@ -312,7 +342,7 @@ lazy_static::lazy_static! { } pub fn remove_pinned_imports(code: &str) -> anyhow::Result { - let mut imports = parse_expr_for_imports(code)?; + let mut imports = parse_expr_for_imports(code, false)?; imports.sort_by_key(|f| 0 - (f.len() as i32)); let mut content = code.to_string(); for import in imports { @@ -500,7 +530,7 @@ fn one_of_label(members: &Vec) -> Option { let Expr::Ident(Ident { sym, .. }) = &**key else { return None; }; - if sym != "label" { + if sym != "label" && sym != "kind" { return None; } diff --git a/backend/parsers/windmill-parser-ts/tests/tests.rs b/backend/parsers/windmill-parser-ts/tests/tests.rs new file mode 100644 index 0000000000..f0ffc2d7c2 --- /dev/null +++ b/backend/parsers/windmill-parser-ts/tests/tests.rs @@ -0,0 +1,19 @@ +mod tests { + use windmill_parser_ts::parse_expr_for_imports; + + #[test] + fn test_imports() { + let code = r#" + import { foo } from "bar"; + import type { foo } from "bar2"; + import { type foo, bar } from "bar3"; + import { bar, type foo } from "bar7"; + + import { type foo, type bar } from "bar4"; + import * as foo from "bar5"; + import foo from "bar6"; + "#; + let imports = parse_expr_for_imports(code, true).unwrap(); + assert_eq!(imports, vec!["bar", "bar3", "bar5", "bar6", "bar7"]); + } +} diff --git a/backend/parsers/windmill-parser-wasm/.envrc b/backend/parsers/windmill-parser-wasm/.envrc new file mode 100644 index 0000000000..f7604555f4 --- /dev/null +++ b/backend/parsers/windmill-parser-wasm/.envrc @@ -0,0 +1 @@ +use flake ../../#wasm diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index c78a9da69f..5b1c884143 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -27,6 +27,8 @@ rust-parser = [ "dep:windmill-parser-rust"] graphql-parser = [ "dep:windmill-parser-graphql"] ansible-parser = [ "dep:windmill-parser-yaml"] csharp-parser = [ "dep:windmill-parser-csharp"] +nu-parser = [ "dep:windmill-parser-nu"] +java-parser = [ "dep:windmill-parser-java"] [dependencies] anyhow.workspace = true @@ -41,6 +43,8 @@ windmill-parser-graphql = { workspace = true, optional = true } windmill-parser-rust = { workspace = true, optional = true } windmill-parser-yaml = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } +windmill-parser-nu = { workspace = true, optional = true } +windmill-parser-java = { workspace = true, optional = true } wasm-bindgen.workspace = true serde_json.workspace = true getrandom = { workspace = true, features = ["js"] } diff --git a/backend/parsers/windmill-parser-wasm/README_DEV.md b/backend/parsers/windmill-parser-wasm/README_DEV.md index ff3f866f4e..1de11be4bf 100644 --- a/backend/parsers/windmill-parser-wasm/README_DEV.md +++ b/backend/parsers/windmill-parser-wasm/README_DEV.md @@ -1,3 +1,4 @@ + ### Windmill parser wasm How to build @@ -14,7 +15,20 @@ Install wasm-pack cargo install wasm-pack ``` -#### To use it on a dev environment +Or enter nix devshell + +``` +nix develop ../../#wasm +``` + +#### Dev locally + +``` +./dev.nu +``` + + +#### Or how to use it on a dev environment manually Go to frontend and run: @@ -23,3 +37,15 @@ npm install ../backend/parsers/windmill-parser-wasm/pkg ``` Make sure to not reset the package.json before commiting + +#### Testing with docker + +Go to the root +``` +sudo docker/dev.nu up --features "," --wasm-pkg +``` + +For example to test `nu`: +``` +sudo docker/dev.nu up --features "static_frontend,nu" --wasm-pkg nu +``` diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh b/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh new file mode 100755 index 0000000000..33c6701e52 --- /dev/null +++ b/backend/parsers/windmill-parser-wasm/build-pkgs-cli.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eou pipefail + +#-# bun and deno +OUT_DIR="../../../cli/wasm/ts" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "ts-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-ts"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# sql languages, graphql and bash/powershell, since they all use regex +OUT_DIR="../../../cli/wasm/regex" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR \ + --features "sql-parser,graphql-parser,bash-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-regex"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# python +OUT_DIR="../../../cli/wasm/python" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "py-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-py"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# go +OUT_DIR="../../../cli/wasm/go" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "go-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-go"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# php +OUT_DIR="../../../cli/wasm/php" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "php-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-php"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# rust +OUT_DIR="../../../cli/wasm/rust" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "rust-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# ansible +OUT_DIR="../../../cli/wasm/yaml" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "ansible-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# C# (needs some more stuff to compile C tree sitter into wasm) +OUT_DIR="../../../cli/wasm/csharp" +mkdir -p $OUT_DIR +CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target deno --out-dir $OUT_DIR --features "csharp-parser" +# sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-csharp"/' $OUT_DIR/package.json +rm $OUT_DIR/.gitignore + +#-# Nu +OUT_DIR="../../../cli/wasm/nu" +mkdir -p $OUT_DIR +wasm-pack build --release --target deno --out-dir $OUT_DIR --features "nu-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +rm $OUT_DIR/.gitignore + +#-# Java +OUT_DIR="../../../cli/wasm/java" +mkdir -p $OUT_DIR +CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target deno --out-dir $OUT_DIR --features "java-parser" +rm $OUT_DIR/.gitignore diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh b/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh index dcd5a78275..109a48e9b3 100755 --- a/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh +++ b/backend/parsers/windmill-parser-wasm/build-pkgs-mac.sh @@ -1,56 +1,68 @@ #!/bin/bash set -eou pipefail -# full pkg +#-# full pkg OUT_DIR="pkg" wasm-pack build --release --target web --out-dir $OUT_DIR --all-features \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort -# bun and deno +#-# bun and deno OUT_DIR="pkg-ts" wasm-pack build --release --target web --out-dir $OUT_DIR --features "ts-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-ts"/' $OUT_DIR/package.json -# sql languages, graphql and bash/powershell, since they all use regex +#-# sql languages, graphql and bash/powershell, since they all use regex OUT_DIR="pkg-regex" wasm-pack build --release --target web --out-dir $OUT_DIR \ --features "sql-parser,graphql-parser,bash-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-regex"/' $OUT_DIR/package.json -# python +#-# python OUT_DIR="pkg-py" wasm-pack build --release --target web --out-dir $OUT_DIR --features "py-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-py"/' $OUT_DIR/package.json -# go +#-# go OUT_DIR="pkg-go" wasm-pack build --release --target web --out-dir $OUT_DIR --features "go-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-go"/' $OUT_DIR/package.json -# php +#-# php OUT_DIR="pkg-php" wasm-pack build --release --target web --out-dir $OUT_DIR --features "php-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-php"/' $OUT_DIR/package.json -# rust +#-# rust OUT_DIR="pkg-rust" wasm-pack build --release --target web --out-dir $OUT_DIR --features "rust-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json -# ansible +#-# ansible OUT_DIR="pkg-yaml" wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json -# C# (needs some more stuff to compile C tree sitter into wasm) +#-# C# (needs some more stuff to compile C tree sitter into wasm) # TODO: hasn't been tested on mac, might need fixing OUT_DIR="pkg-csharp" CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "csharp-parser" sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-csharp"/' $OUT_DIR/package.json + +#-# nu +# TODO: hasn't been tested on mac, might need fixing +OUT_DIR="pkg-nu" +wasm-pack build --release --target web --out-dir $OUT_DIR --features "nu-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-nu"/' $OUT_DIR/package.json + +#-# Java (needs some more stuff to compile C tree sitter into wasm) +OUT_DIR="pkg-java" +CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "java-parser" +sed -i '' 's/"windmill-parser-wasm"/"windmill-parser-wasm-java"/' $OUT_DIR/package.json diff --git a/backend/parsers/windmill-parser-wasm/build-pkgs.sh b/backend/parsers/windmill-parser-wasm/build-pkgs.sh index ec7a1f5ee4..0d492e9dcb 100755 --- a/backend/parsers/windmill-parser-wasm/build-pkgs.sh +++ b/backend/parsers/windmill-parser-wasm/build-pkgs.sh @@ -1,55 +1,66 @@ -#!/bin/bash +#!/usr/bin/env bash set -eou pipefail -# full pkg +#-# full pkg OUT_DIR="pkg" wasm-pack build --release --target web --out-dir $OUT_DIR --all-features \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort -# bun and deno +#-# bun and deno OUT_DIR="pkg-ts" wasm-pack build --release --target web --out-dir $OUT_DIR --features "ts-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-ts"/' $OUT_DIR/package.json -# sql languages, graphql and bash/powershell, since they all use regex +#-# sql languages, graphql and bash/powershell, since they all use regex OUT_DIR="pkg-regex" wasm-pack build --release --target web --out-dir $OUT_DIR \ --features "sql-parser,graphql-parser,bash-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-regex"/' $OUT_DIR/package.json -# python +#-# python OUT_DIR="pkg-py" wasm-pack build --release --target web --out-dir $OUT_DIR --features "py-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-py"/' $OUT_DIR/package.json -# go +#-# go OUT_DIR="pkg-go" wasm-pack build --release --target web --out-dir $OUT_DIR --features "go-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-go"/' $OUT_DIR/package.json -# php +#-# php OUT_DIR="pkg-php" wasm-pack build --release --target web --out-dir $OUT_DIR --features "php-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-php"/' $OUT_DIR/package.json -# rust +#-# rust OUT_DIR="pkg-rust" wasm-pack build --release --target web --out-dir $OUT_DIR --features "rust-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json -# ansible +#-# ansible OUT_DIR="pkg-yaml" wasm-pack build --release --target web --out-dir $OUT_DIR --features "ansible-parser" \ -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-yaml"/' $OUT_DIR/package.json -# C# (needs some more stuff to compile C tree sitter into wasm) +#-# C# (needs some more stuff to compile C tree sitter into wasm) OUT_DIR="pkg-csharp" CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "csharp-parser" sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-csharp"/' $OUT_DIR/package.json + +#-# Nu +OUT_DIR="pkg-nu" +wasm-pack build --release --target web --out-dir $OUT_DIR --features "nu-parser" \ + -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort +sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-nu"/' $OUT_DIR/package.json + +#-# Java (needs some more stuff to compile C tree sitter into wasm) +OUT_DIR="pkg-java" +CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" RUSTFLAGS="-Zwasm-c-abi=spec" wasm-pack build --release --target web --out-dir $OUT_DIR --features "java-parser" +sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-java"/' $OUT_DIR/package.json diff --git a/backend/parsers/windmill-parser-wasm/build.sh b/backend/parsers/windmill-parser-wasm/build.sh deleted file mode 100755 index b872e2c912..0000000000 --- a/backend/parsers/windmill-parser-wasm/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -eou pipefail - -deno task wasmbuild --out ../../../cli/wasm/ -p windmill-parser-wasm --all-features diff --git a/backend/parsers/windmill-parser-wasm/deno.json b/backend/parsers/windmill-parser-wasm/deno.json index abdd5e39d1..1887a74f76 100644 --- a/backend/parsers/windmill-parser-wasm/deno.json +++ b/backend/parsers/windmill-parser-wasm/deno.json @@ -1,5 +1,5 @@ { "tasks": { - "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.17.2" + "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.19.0" } } diff --git a/backend/parsers/windmill-parser-wasm/dev.nu b/backend/parsers/windmill-parser-wasm/dev.nu new file mode 100755 index 0000000000..e80f3c302f --- /dev/null +++ b/backend/parsers/windmill-parser-wasm/dev.nu @@ -0,0 +1,25 @@ +#!/usr/bin/env nu + +# Build in debug mode specified lang parser to wasm +# and perform installation to frontend +def "main" [ + lang: string # Example: nu + --release(-r) +] { + let out_dir = $'pkg-($lang)' + if $release { + open build-pkgs.sh + | split row '#-' + | find $out_dir + | bash -c $"RUST_LOG=trace ($in.0)" + } else { + open build-pkgs.sh + | split row '#-' + | find $out_dir + | str replace "--release" "--no-opt" + | bash -c $"WASM_OPT=-Oz ($in.0)" + } + ( + cd ../../../frontend; npm install ../backend/parsers/windmill-parser-wasm/($out_dir) + ) +} diff --git a/backend/parsers/windmill-parser-wasm/flake.lock b/backend/parsers/windmill-parser-wasm/flake.lock deleted file mode 100644 index c8733c1921..0000000000 --- a/backend/parsers/windmill-parser-wasm/flake.lock +++ /dev/null @@ -1,95 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1737885589, - "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1736320768, - "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1738117527, - "narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "6a3dc6ce4132bd57359214d986db376f2333c14d", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/backend/parsers/windmill-parser-wasm/flake.nix b/backend/parsers/windmill-parser-wasm/flake.nix deleted file mode 100644 index ac97d8c80a..0000000000 --- a/backend/parsers/windmill-parser-wasm/flake.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ - inputs = { - nixpkgs.url = "nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - rust-overlay.url = "github:oxalica/rust-overlay"; - }; - - outputs = { - nixpkgs, - flake-utils, - rust-overlay, - ... - }: - flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { - inherit system; - overlays = [(import rust-overlay)]; - }; - rust = pkgs.rust-bin.nightly.latest.default.override { - extensions = [ - "rust-src" - ]; - targets = ["wasm32-unknown-unknown"]; - }; - in { - devShell = pkgs.mkShell { - buildInputs = with pkgs; [ - rust - nodejs - wasm-pack - sccache - ]; - RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; - CARGO_PATH = "${rust}/bin/cargo"; - }; - }); -} diff --git a/backend/parsers/windmill-parser-wasm/publish-pkgs.sh b/backend/parsers/windmill-parser-wasm/publish-pkgs.sh index a4af8bb684..4180853b8b 100755 --- a/backend/parsers/windmill-parser-wasm/publish-pkgs.sh +++ b/backend/parsers/windmill-parser-wasm/publish-pkgs.sh @@ -27,3 +27,6 @@ popd pushd "pkg-csharp" && npm publish ${args} popd + +pushd "pkg-nu" && npm publish ${args} +popd diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index 306d67751c..2ca730cf72 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -17,7 +17,7 @@ fn wrap_sig(r: anyhow::Result) -> String { #[cfg(feature = "ts-parser")] #[wasm_bindgen] -pub fn parse_deno(code: &str, main_override: Option, skip_params: Option) -> String { +pub fn parse_deno(code: &str, main_override: Option) -> String { wrap_sig(windmill_parser_ts::parse_deno_signature( code, false, @@ -41,7 +41,7 @@ pub fn parse_outputs(code: &str) -> String { #[cfg(feature = "ts-parser")] #[wasm_bindgen] pub fn parse_ts_imports(code: &str) -> String { - let parsed = parse_expr_for_imports(code); + let parsed = parse_expr_for_imports(code, false); let r = if let Ok(parsed) = parsed { json!({ "imports": parsed }) } else { @@ -96,6 +96,12 @@ 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 { @@ -149,3 +155,17 @@ pub fn parse_ansible(code: &str) -> String { pub fn parse_csharp(code: &str) -> String { wrap_sig(windmill_parser_csharp::parse_csharp_signature(code)) } + +#[cfg(feature = "nu-parser")] +#[wasm_bindgen] +pub fn parse_nu(code: &str) -> String { + wrap_sig(windmill_parser_nu::parse_nu_signature(code)) +} + +#[cfg(feature = "java-parser")] +#[wasm_bindgen] +pub fn parse_java(code: &str) -> String { + wrap_sig(windmill_parser_java::parse_java_signature(code)) +} + +// for related places search: ADD_NEW_LANG diff --git a/backend/parsers/windmill-parser-wasm/tests/wasm.rs b/backend/parsers/windmill-parser-wasm/tests/wasm.rs index e8dc1e986b..35845e179a 100644 --- a/backend/parsers/windmill-parser-wasm/tests/wasm.rs +++ b/backend/parsers/windmill-parser-wasm/tests/wasm.rs @@ -339,7 +339,7 @@ fn test_parse_imports() -> anyhow::Result<()> { import { bar } from \"bar/foo/d\"; import { bar as baroof } from \"bar\"; "; - let mut l = parse_expr_for_imports(code)?; + let mut l = parse_expr_for_imports(code, false)?; l.sort(); assert_eq!( l, @@ -360,7 +360,7 @@ fn test_parse_imports_dts() -> anyhow::Result<()> { let code = " export type foo = number "; - let mut l = parse_expr_for_imports(code)?; + let mut l = parse_expr_for_imports(code, false)?; l.sort(); assert_eq!(l, vec![] as Vec); diff --git a/backend/parsers/windmill-parser-yaml/src/lib.rs b/backend/parsers/windmill-parser-yaml/src/lib.rs index c2d417eb6c..06adac1ee3 100644 --- a/backend/parsers/windmill-parser-yaml/src/lib.rs +++ b/backend/parsers/windmill-parser-yaml/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use anyhow::anyhow; use serde_json::json; use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ}; @@ -208,15 +210,52 @@ pub struct AnsibleInventory { resource_type: Option, pub pinned_resource: Option, } + +#[derive(Debug, Clone)] +pub struct GitRepo { + pub url: String, + pub commit: Option, + pub branch: Option, + pub target_path: String, +} + #[derive(Debug, Clone)] pub struct AnsibleRequirements { pub python_reqs: Vec, - pub collections: Option, + pub roles_and_collections: Option, pub file_resources: Vec, pub inventories: Vec, pub vars: Vec<(String, String)>, pub resources: Vec<(String, String)>, pub options: AnsiblePlaybookOptions, + pub vault_password: Option, + pub vault_id: Vec, + pub git_repos: Vec, + pub git_ssh_identity: Vec, +} + +impl Default for AnsibleRequirements { + fn default() -> Self { + Self { + python_reqs: vec![], + roles_and_collections: None, + file_resources: vec![], + inventories: vec![], + vars: vec![], + resources: vec![], + options: AnsiblePlaybookOptions { + verbosity: None, + forks: None, + timeout: None, + flush_cache: None, + force_handlers: None, + }, + vault_password: None, + vault_id: vec![], + git_repos: vec![], + git_ssh_identity: vec![], + } + } } fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result> { @@ -275,22 +314,7 @@ pub fn parse_ansible_reqs( return Ok((logs, None, inner_content.to_string())); } - let opts = AnsiblePlaybookOptions { - verbosity: None, - forks: None, - timeout: None, - flush_cache: None, - force_handlers: None, - }; - let mut ret = AnsibleRequirements { - python_reqs: vec![], - collections: None, - file_resources: vec![], - inventories: vec![], - vars: vec![], - resources: vec![], - options: opts, - }; + let mut ret = AnsibleRequirements::default(); if let Yaml::Hash(doc) = &docs[0] { for (key, value) in doc { @@ -303,7 +327,7 @@ pub fn parse_ansible_reqs( let mut out_str = String::new(); let mut emitter = YamlEmitter::new(&mut out_str); emitter.dump(galaxy_requirements)?; - ret.collections = Some(out_str); + ret.roles_and_collections = Some(out_str); } if let Some(Yaml::Array(py_reqs)) = deps.get(&Yaml::String("python".to_string())) @@ -345,11 +369,60 @@ pub fn parse_ansible_reqs( Yaml::String(key) if key == "inventory" => { ret.inventories = parse_inventories(value)?; } + Yaml::String(key) if key == "vault_password" => { + let Yaml::String(filename) = value else { + return Err(anyhow!( + "Vault Password File expects a String containing the file name" + )); + }; + ret.vault_password = Some(filename.to_string()); + } + Yaml::String(key) if key == "vault_id" => { + let Yaml::Array(filenames) = value else { + return Err(anyhow!("Vault ID field expects an array of strings in the format: `label@filename`")); + }; + + for f in filenames { + let Yaml::String(filename) = f else { + return Err(anyhow!("The elements of the vault_id field should be strings in the format: `label@filename`")); + }; + ret.vault_id.push(filename.to_string()); + } + } Yaml::String(key) if key == "options" => { if let Yaml::Array(opts) = &value { ret.options = parse_ansible_options(opts); } } + Yaml::String(key) if key == "git_repos" => { + let Yaml::Array(repos) = &value else { + return Err(anyhow!("git_repos field expects an array of repos")); + }; + + for r in repos { + ret.git_repos.push( + parse_git_repo(r) + .map_err(|e| anyhow!("Failed to parse git repo: {e}"))?, + ); + } + } + Yaml::String(key) if key == "git_ssh_identity" => { + let Yaml::Array(indentities) = &value else { + return Err(anyhow!( + "git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs" + )); + }; + + for r in indentities { + let Yaml::String(file_name) = r else { + return Err(anyhow!( + "Git ssh identity file must be a string path to a Windmill variable/secret" + )); + }; + + ret.git_ssh_identity.push(file_name.clone()); + } + } Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)), _ => (), } @@ -364,6 +437,38 @@ pub fn parse_ansible_reqs( Ok((logs, Some(ret), out_str)) } +fn parse_git_repo(r: &Yaml) -> anyhow::Result { + let Yaml::Hash(repo) = r else { + return Err(anyhow!("Should be a Map")); + }; + + let url = repo + .get(&Yaml::String("url".to_string())) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .ok_or(anyhow!("Expected `url` field"))?; + + let target_path = repo + .get(&Yaml::String("target".to_string())) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .ok_or(anyhow!( + "Expected `target` field (target directory for cloning the repo)" + ))?; + + let branch = repo + .get(&Yaml::String("branch".to_string())) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + + let commit = repo + .get(&Yaml::String("commit".to_string())) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + + Ok(GitRepo { url, commit, branch, target_path }) +} + fn parse_ansible_options(opts: &Vec) -> AnsiblePlaybookOptions { let mut ret = AnsiblePlaybookOptions { verbosity: None, @@ -529,3 +634,78 @@ fn yaml_to_json(yaml: &Yaml) -> serde_json::Value { _ => serde_json::Value::Null, } } + +fn update_versions( + section: &str, + yaml: &mut Yaml, + versions: &HashMap, +) -> anyhow::Result { + let mut logs = String::new(); + + let Yaml::Hash(ref mut m) = yaml else { + return Err(anyhow!("{section} dependency should be a map")); + }; + + if let Some(Yaml::Array(elements)) = m.get_mut(&Yaml::String(section.to_string())) { + for el in elements { + let Yaml::Hash(ref mut h) = el else { + return Err(anyhow!("{section} dependency element should be a map")); + }; + + if let Some(name) = h + .get(&Yaml::String("name".to_string())) + .and_then(|n| n.as_str()) + { + if let Some(version) = versions.get(name) { + h.insert( + Yaml::String("version".to_string()), + Yaml::String(version.to_string()), + ); + } else { + logs.push_str(&format!("WARNING: {section} dependency `{name}` has no locked version, using the latest or system installed version.\n")); + } + } else { + return Err(anyhow!( + "{section} dependency element: missing or invalid `name` field" + )); + } + } + } + + Ok(logs) +} + +pub fn add_versions_to_requirements_yaml( + input: &str, + role_versions: &HashMap, + collection_versions: &HashMap, +) -> anyhow::Result<(String,String)> { + let mut docs = + YamlLoader::load_from_str(input).map_err(|e| anyhow!("YAML parse error: {}", e))?; + let doc = &mut docs[0]; + + let mut logs = String::new(); + + logs.push_str( + &update_versions("roles", doc, role_versions) + .map_err(|e| anyhow!("Error updating role versions: {e}"))?, + ); + logs.push_str( + &update_versions("collections", doc, collection_versions) + .map_err(|e| anyhow!("Error updating collection versions: {e}"))?, + ); + + if !logs.is_empty() { + logs.push_str("WARNING: You might want to try adding manual versions for these, otherwise there could be breaking changes on deployed scripts\n"); + } + + let mut out_str = String::new(); + { + let mut emitter = YamlEmitter::new(&mut out_str); + emitter + .dump(doc) + .map_err(|e| anyhow!("YAML emit error: {}", e))?; + } + + Ok((out_str, logs)) +} diff --git a/backend/parsers/windmill-parser/src/lib.rs b/backend/parsers/windmill-parser/src/lib.rs index 6f793d0cc7..0cb662c91b 100644 --- a/backend/parsers/windmill-parser/src/lib.rs +++ b/backend/parsers/windmill-parser/src/lib.rs @@ -10,7 +10,7 @@ use convert_case::{Boundary, Case, Casing}; use serde::Serialize; use serde_json::Value; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Serialize, Debug, PartialEq, Default)] pub struct MainArgSignature { pub star_args: bool, pub star_kwargs: bool, @@ -92,6 +92,12 @@ mod test { fn test_snake_case() { assert_eq!("s3", to_snake_case("S3")); assert_eq!("s3", to_snake_case("s3")); + assert_eq!("s3_object", to_snake_case("S3Object")); + assert_eq!("s3_object", to_snake_case("S3object")); + assert_eq!("s3_object", to_snake_case("s3object")); + assert_eq!("abc", to_snake_case("ABC")); + assert_eq!("aa_bc", to_snake_case("AaBC")); + assert_eq!("a_b_c", to_snake_case("A_B_C")); assert_eq!("s_3", to_snake_case("S_3")); assert_eq!("type_name_here", to_snake_case("typeNameHere")); } diff --git a/backend/plot2.py b/backend/plot2.py new file mode 100644 index 0000000000..5a6624bc14 --- /dev/null +++ b/backend/plot2.py @@ -0,0 +1,33 @@ +import json +import matplotlib.pyplot as plt + +# Path to the profiling JSON file +# file_path = "/tmp/windmill/profiling_main.json" +file_path = "/tmp/profiling.json" + +# Load the JSON data +with open(file_path, "r") as f: + data = json.load(f) + +# Extract timings for "pre pull->post pull" +pre_post_pull_timings = [ + timing / 1000000.0 for entry in data["timings"] + for step, timing in entry["timings"] + # if step == "pre pull->post pull" + if step == "->job pulled from DB" +] + +# Plotting the distribution +plt.figure(figsize=(10, 6)) +# plt.hist(pre_post_pull_timings, bins=10, edgecolor='black') +plt.scatter(range(len(pre_post_pull_timings)), pre_post_pull_timings, + alpha=1.0, # Transparency level + s=40) # Size of the dots`) +plt.title("Distribution of 'pre pull->post pull' timings") +# plt.xlabel("Time (ms)") +# plt.ylabel("Frequency") +plt.xlabel("Sample Index") +plt.ylabel("Time (ms)") +plt.grid(True) +plt.tight_layout() +plt.show() \ No newline at end of file diff --git a/backend/rust-best-practices.mdc b/backend/rust-best-practices.mdc new file mode 100644 index 0000000000..bdbdb0d24d --- /dev/null +++ b/backend/rust-best-practices.mdc @@ -0,0 +1,110 @@ +--- +description: +globs: backend/**/*.rs +alwaysApply: false +--- +# 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` or `JsonResult` 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 +- Do not forget to update backend/windmill-api/openapi.yaml after modifying an api endpoint + +## 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 \ No newline at end of file diff --git a/backend/src/ee.rs b/backend/src/ee_oss.rs similarity index 53% rename from backend/src/ee.rs rename to backend/src/ee_oss.rs index 91816cd1ba..4791a243a3 100644 --- a/backend/src/ee.rs +++ b/backend/src/ee_oss.rs @@ -1,8 +1,13 @@ +#[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(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn verify_license_key() -> () { // Implementation is not open source } diff --git a/backend/src/main.rs b/backend/src/main.rs index 3795a157ec..aa3eee4a18 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -5,48 +5,62 @@ * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. */ - use anyhow::Context; use monitor::{ - load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config, - reload_instance_python_version_setting, reload_nuget_config_setting, + load_base_url, load_otel, reload_critical_alerts_on_db_oversize, + reload_delete_logs_periodically_setting, reload_indexer_config, + reload_instance_python_version_setting, reload_maven_repos_setting, + reload_no_default_maven_setting, reload_nuget_config_setting, reload_timeout_wait_result_setting, send_current_log_file_to_object_store, - send_logs_to_object_store, + send_logs_to_object_store, WORKERS_NAMES, }; use rand::Rng; -use sqlx::{postgres::PgListener, Pool, Postgres}; +use sqlx::postgres::PgListener; use std::{ collections::HashMap, fs::{create_dir_all, DirBuilder}, net::{IpAddr, Ipv4Addr, SocketAddr}, - time::Duration, + time::{Duration, Instant}, }; +use strum::IntoEnumIterator; use tokio::{fs::File, io::AsyncReadExt, task::JoinHandle}; use uuid::Uuid; use windmill_api::HTTP_CLIENT; #[cfg(feature = "enterprise")] -use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID}; +use windmill_common::ee_oss::{ + maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID, +}; use windmill_common::{ + agent_workers::build_agent_http_client, + get_database_url, global_settings::{ - BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, - CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, - DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, - EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, - HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING, - JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, - LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, - NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, - REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, - RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, - TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING, + BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, + CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, + DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, + ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, + EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, + INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, + KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, + MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING, + NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, + PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, + SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, + TIMEOUT_WAIT_RESULT_SETTING, }, scripts::ScriptLang, - stats_ee::schedule_stats, - utils::{hostname, rd_string, Mode, GIT_VERSION, MODE_AND_ADDONS}, - worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP}, - DB, METRICS_ENABLED, + stats_oss::schedule_stats, + triggers::TriggerKind, + utils::{ + create_default_worker_suffix, create_ssh_agent_worker_suffix, worker_name_with_suffix, + Mode, GIT_VERSION, HOSTNAME, MODE_AND_ADDONS, + }, + worker::{ + reload_custom_tags_setting, Connection, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP, + }, + KillpillSender, METRICS_ENABLED, }; #[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))] @@ -60,14 +74,13 @@ use tikv_jemallocator::Jemalloc; static GLOBAL: Jemalloc = Jemalloc; #[cfg(feature = "parquet")] -use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING; +use windmill_common::global_settings::OBJECT_STORE_CONFIG_SETTING; use windmill_worker::{ get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, - POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR, - RUST_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, - TAR_PY313_CACHE_DIR, UV_CACHE_DIR, + JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, + PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR, }; use crate::monitor::{ @@ -83,15 +96,42 @@ use crate::monitor::{ }; #[cfg(feature = "parquet")] -use crate::monitor::reload_s3_cache_setting; +use windmill_common::s3_helpers::reload_object_store_setting; const DEFAULT_NUM_WORKERS: usize = 1; const DEFAULT_PORT: u16 = 8000; const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); -mod ee; +#[cfg(feature = "private")] +pub mod ee; +mod ee_oss; mod monitor; +pub fn setup_deno_runtime() -> anyhow::Result<()> { + // https://github.com/denoland/deno/blob/main/cli/main.rs#L477 + #[cfg(feature = "deno_core")] + let unrecognized_v8_flags = deno_core::v8_set_flags(vec![ + "--stack-size=1024".to_string(), + // TODO(bartlomieju): I think this can be removed as it's handled by `deno_core` + // and its settings. + // deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript + // TODO(petamoriken): Need to check TypeScript `assert` keywords in deno_ast + "--no-harmony-import-assertions".to_string(), + ]) + .into_iter() + .skip(1) + .collect::>(); + + #[cfg(feature = "deno_core")] + if !unrecognized_v8_flags.is_empty() { + println!("Unrecognized V8 flags: {:?}", unrecognized_v8_flags); + } + + #[cfg(feature = "deno_core")] + deno_core::JsRuntime::init_platform(None, false); + Ok(()) +} + #[inline(always)] fn create_and_run_current_thread_inner(future: F) -> R where @@ -114,28 +154,16 @@ where rt.block_on(future) } +lazy_static::lazy_static! { + static ref PG_LISTENER_REFRESH_PERIOD_SECS: u64 = std::env::var("PG_LISTENER_REFRESH_PERIOD_SECS") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(3600 * 12); + +} + pub fn main() -> anyhow::Result<()> { - // https://github.com/denoland/deno/blob/main/cli/main.rs#L477 - #[cfg(feature = "deno_core")] - let unrecognized_v8_flags = deno_core::v8_set_flags(vec![ - "--stack-size=1024".to_string(), - // TODO(bartlomieju): I think this can be removed as it's handled by `deno_core` - // and its settings. - // deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript - // TODO(petamoriken): Need to check TypeScript `assert` keywords in deno_ast - "--no-harmony-import-assertions".to_string(), - ]) - .into_iter() - .skip(1) - .collect::>(); - - #[cfg(feature = "deno_core")] - if !unrecognized_v8_flags.is_empty() { - println!("Unrecognized V8 flags: {:?}", unrecognized_v8_flags); - } - - #[cfg(feature = "deno_core")] - deno_core::JsRuntime::init_platform(None, false); + setup_deno_runtime()?; create_and_run_current_thread_inner(windmill_main()) } @@ -236,13 +264,19 @@ async fn windmill_main() -> anyhow::Result<()> { std::env::set_var("RUST_LOG", "info") } - let hostname = hostname(); + if let Err(_e) = rustls::crypto::ring::default_provider().install_default() { + tracing::error!("Failed to install rustls crypto provider"); + } + + let hostname = HOSTNAME.to_owned(); let mode_and_addons = MODE_AND_ADDONS.clone(); let mode = mode_and_addons.mode; if mode == Mode::Standalone { println!("Running in standalone mode"); + } else if mode == Mode::MCP { + println!("Running in MCP mode"); } #[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))] @@ -275,7 +309,7 @@ async fn windmill_main() -> anyhow::Result<()> { } #[allow(unused_mut)] - let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer { + let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer || mode == Mode::MCP { 0 } else { std::env::var("NUM_WORKERS") @@ -297,8 +331,9 @@ async fn windmill_main() -> anyhow::Result<()> { && (mode == Mode::Server || mode == Mode::Standalone); let indexer_mode = mode == Mode::Indexer; + let mcp_mode = mode == Mode::MCP; - let server_bind_address: IpAddr = if server_mode || indexer_mode { + let server_bind_address: IpAddr = if server_mode || indexer_mode || mcp_mode { std::env::var("SERVER_BIND_ADDR") .ok() .and_then(|x| x.parse().ok()) @@ -307,23 +342,37 @@ async fn windmill_main() -> anyhow::Result<()> { IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)) }; - println!("Connecting to database..."); - let db = windmill_common::initial_connection().await?; + let (conn, first_suffix) = if mode == Mode::Agent { + tracing::info!( + "Creating http client for cluster using base internal url {}", + std::env::var("BASE_INTERNAL_URL").unwrap_or_default() + ); + let suffix = create_ssh_agent_worker_suffix(&hostname); + ( + Connection::Http(build_agent_http_client(&suffix)), + Some(suffix), + ) + } else { + println!("Connecting to database..."); - let num_version = sqlx::query_scalar!("SELECT version()").fetch_one(&db).await; + let db = windmill_common::initial_connection().await?; - tracing::info!( - "PostgreSQL version: {} (windmill require PG >= 14)", - num_version - .ok() - .flatten() - .unwrap_or_else(|| "UNKNOWN".to_string()) - ); - load_otel(&db).await; + let num_version = sqlx::query_scalar!("SELECT version()").fetch_one(&db).await; - tracing::info!("Database connected"); + tracing::info!( + "PostgreSQL version: {} (windmill require PG >= 14)", + num_version + .ok() + .flatten() + .unwrap_or_else(|| "UNKNOWN".to_string()) + ); + load_otel(&db).await; - let environment = load_base_url(&db) + tracing::info!("Database connected"); + (Connection::Sql(db), None) + }; + + let environment = load_base_url(&conn) .await .unwrap_or_else(|_| "local".to_string()) .trim_start_matches("https://") @@ -343,25 +392,32 @@ async fn windmill_main() -> anyhow::Result<()> { .ok() .is_some_and(|x| x == "1" || x == "true"); - if !is_agent && !indexer_mode { - let skip_migration = std::env::var("SKIP_MIGRATION") - .map(|val| val == "true") - .unwrap_or(false); + if let Some(db) = conn.as_sql() { + if !is_agent && !indexer_mode && !mcp_mode { + let skip_migration = std::env::var("SKIP_MIGRATION") + .map(|val| val == "true") + .unwrap_or(false); - if !skip_migration { - // migration code to avoid break - migration_handle = windmill_api::migrate_db(&db).await?; - } else { - tracing::info!("SKIP_MIGRATION set, skipping db migration...") + if !skip_migration { + // migration code to avoid break + migration_handle = windmill_api::migrate_db(&db).await?; + } else { + tracing::info!("SKIP_MIGRATION set, skipping db migration...") + } } } - drop(db); let worker_mode = num_workers > 0; - let db = windmill_common::connect_db(server_mode, indexer_mode, worker_mode).await?; + let conn = if mode == Mode::Agent { + conn + } else { + // This time we use a pool of connections + let db = windmill_common::connect_db(server_mode, indexer_mode, worker_mode).await?; + Connection::Sql(db) + }; - let (killpill_tx, mut killpill_rx) = tokio::sync::broadcast::channel::<()>(2); + let (killpill_tx, mut killpill_rx) = KillpillSender::new(2); let mut monitor_killpill_rx = killpill_tx.subscribe(); let (killpill_phase2_tx, _killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2); let server_killpill_rx = killpill_phase2_tx.subscribe(); @@ -387,47 +443,43 @@ Windmill Community Edition {GIT_VERSION} display_config(&ENV_SETTINGS); - if let Err(e) = reload_base_url_setting(&db).await { - tracing::error!("Error loading base url: {:?}", e) - } - - if let Err(e) = reload_critical_error_channels_setting(&db).await { - tracing::error!("Could loading critical error emails setting: {:?}", e); - } - #[cfg(feature = "enterprise")] { // load the license key and check if it's valid // if not valid and not server mode just quit // if not expired and server mode then force renewal // if key still invalid and num_workers > 0, set to 0 - if let Err(err) = reload_license_key(&db).await { + if let Err(err) = reload_license_key(&conn).await { tracing::error!("Failed to reload license key: {err:#}"); } let valid_key = *LICENSE_KEY_VALID.read().await; if !valid_key && !server_mode { tracing::error!("Invalid license key, workers require a valid license key"); } - if server_mode { - // only force renewal if invalid but not empty (= expired) - let renewed_now = maybe_renew_license_key_on_start( - &HTTP_CLIENT, - &db, - !valid_key && !LICENSE_KEY_ID.read().await.is_empty(), - ) - .await; - if renewed_now { - if let Err(err) = reload_license_key(&db).await { - tracing::error!("Failed to reload license key: {err:#}"); + if server_mode || mcp_mode { + if let Some(db) = conn.as_sql() { + // only force renewal if invalid but not empty (= expired) + let renewed_now = maybe_renew_license_key_on_start( + &HTTP_CLIENT, + &db, + !valid_key && !LICENSE_KEY_ID.read().await.is_empty(), + ) + .await; + if renewed_now { + if let Err(err) = reload_license_key(&conn).await { + tracing::error!("Failed to reload license key: {err:#}"); + } } + } else { + panic!("Server mode requires a database connection"); } } } - if server_mode || worker_mode || indexer_mode { + if server_mode || worker_mode || indexer_mode || mcp_mode { let port_var = std::env::var("PORT").ok().and_then(|x| x.parse().ok()); - let port = if server_mode || indexer_mode { + let port = if server_mode || indexer_mode || mcp_mode { port_var.unwrap_or(DEFAULT_PORT as u16) } else { port_var.unwrap_or(0) @@ -447,7 +499,7 @@ Windmill Community Edition {GIT_VERSION} }; initial_load( - &db, + &conn, killpill_tx.clone(), worker_mode, server_mode, @@ -457,7 +509,7 @@ Windmill Community Edition {GIT_VERSION} .await; monitor_db( - &db, + &conn, &base_internal_url, server_mode, worker_mode, @@ -467,9 +519,11 @@ Windmill Community Edition {GIT_VERSION} .await; #[cfg(feature = "prometheus")] - crate::monitor::monitor_pool(&db).await; + if let Some(db) = conn.as_sql() { + crate::monitor::monitor_pool(&db).await; + } - send_logs_to_object_store(&db, &hostname, &mode); + send_logs_to_object_store(&conn, &hostname, &mode); #[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))] if !worker_mode { @@ -488,25 +542,34 @@ Windmill Community Edition {GIT_VERSION} #[cfg(feature = "tantivy")] let should_index_jobs = mode == Mode::Indexer || mode_and_addons.indexer; - reload_indexer_config(&db).await; + #[cfg(feature = "tantivy")] + if should_index_jobs { + if let Some(db) = conn.as_sql() { + reload_indexer_config(&db).await; + } + } #[cfg(feature = "tantivy")] let (index_reader, index_writer) = if should_index_jobs { - let mut indexer_rx = killpill_rx.resubscribe(); + if let Some(db) = conn.as_sql() { + let mut indexer_rx = killpill_rx.resubscribe(); - let (mut reader, mut writer) = (None, None); - tokio::select! { + let (mut reader, mut writer) = (None, None); + tokio::select! { _ = indexer_rx.recv() => { tracing::info!("Received killpill, aborting index initialization"); }, - res = windmill_indexer::completed_runs_ee::init_index(&db) => { + res = windmill_indexer::completed_runs_oss::init_index(&db) => { let res = res?; reader = Some(res.0); writer = Some(res.1); } + } + (reader, writer) + } else { + (None, None) } - (reader, writer) } else { (None, None) }; @@ -516,13 +579,15 @@ Windmill Community Edition {GIT_VERSION} let indexer_rx = killpill_rx.resubscribe(); let index_writer2 = index_writer.clone(); async { - if let Some(index_writer) = index_writer2 { - windmill_indexer::completed_runs_ee::run_indexer( - db.clone(), - index_writer, - indexer_rx, - ) - .await?; + if let Some(db) = conn.as_sql() { + if let Some(index_writer) = index_writer2 { + windmill_indexer::completed_runs_oss::run_indexer( + db.clone(), + index_writer, + indexer_rx, + ) + .await?; + } } Ok(()) } @@ -530,21 +595,25 @@ Windmill Community Edition {GIT_VERSION} #[cfg(all(feature = "tantivy", feature = "parquet"))] let (log_index_reader, log_index_writer) = if should_index_jobs { - let mut indexer_rx = killpill_rx.resubscribe(); + if let Some(db) = conn.as_sql() { + let mut indexer_rx = killpill_rx.resubscribe(); + + let (mut reader, mut writer) = (None, None); + tokio::select! { + _ = indexer_rx.recv() => { + tracing::info!("Received killpill, aborting index initialization"); + }, + res = windmill_indexer::service_logs_oss::init_index(&db, killpill_tx.clone()) => { + let res = res?; + reader = Some(res.0); + writer = Some(res.1); + } - let (mut reader, mut writer) = (None, None); - tokio::select! { - _ = indexer_rx.recv() => { - tracing::info!("Received killpill, aborting index initialization"); - }, - res = windmill_indexer::service_logs_ee::init_index(&db, killpill_tx.clone()) => { - let res = res?; - reader = Some(res.0); - writer = Some(res.1); } - + (reader, writer) + } else { + (None, None) } - (reader, writer) } else { (None, None) }; @@ -554,13 +623,15 @@ Windmill Community Edition {GIT_VERSION} let log_indexer_rx = killpill_rx.resubscribe(); let log_index_writer2 = log_index_writer.clone(); async { - if let Some(log_index_writer) = log_index_writer2 { - windmill_indexer::service_logs_ee::run_indexer( - db.clone(), - log_index_writer, - log_indexer_rx, - ) - .await?; + if let Some(db) = conn.as_sql() { + if let Some(log_index_writer) = log_index_writer2 { + windmill_indexer::service_logs_oss::run_indexer( + db.clone(), + log_index_writer, + log_indexer_rx, + ) + .await?; + } } Ok(()) } @@ -580,18 +651,20 @@ Windmill Community Edition {GIT_VERSION} let server_f = async { if !is_agent { - windmill_api::run_server( - db.clone(), - index_reader, - log_index_reader, - addr, - server_killpill_rx, - base_internal_tx, - server_mode, - #[cfg(feature = "smtp")] - base_internal_url.clone(), - ) - .await?; + if let Some(db) = conn.as_sql() { + windmill_api::run_server( + db.clone(), + index_reader, + log_index_reader, + addr, + server_killpill_rx, + base_internal_tx, + server_mode, + mcp_mode, + base_internal_url.clone(), + ) + .await?; + } } else { base_internal_tx .send(base_internal_url.clone()) @@ -608,18 +681,40 @@ Windmill Community Edition {GIT_VERSION} if !killpill_rx.try_recv().is_ok() { let base_internal_url = base_internal_rx.await?; if worker_mode { + let mut workers = vec![]; + + for i in 0..num_workers { + let suffix = if i == 0 && first_suffix.is_some() { + first_suffix.as_ref().unwrap().clone() + } else { + create_default_worker_suffix(&hostname) + }; + + let worker_conn = WorkerConn { + conn: if i == 0 || mode != Mode::Agent { + conn.clone() + } else { + Connection::Http(build_agent_http_client(&suffix)) + }, + worker_name: worker_name_with_suffix( + mode == Mode::Agent, + WORKER_GROUP.as_str(), + &suffix, + ), + }; + workers.push(worker_conn); + } + run_workers( - db.clone(), rx, killpill_tx.clone(), - num_workers, base_internal_url.clone(), - is_agent, hostname.clone(), + &workers, ) .await?; tracing::info!("All workers exited."); - killpill_tx.send(())?; + killpill_tx.send(); } else { rx.recv().await?; } @@ -637,235 +732,388 @@ Windmill Community Edition {GIT_VERSION} }; let monitor_f = async { - let db = db.clone(); let tx = killpill_tx.clone(); - - let base_internal_url = base_internal_url.to_string(); - let h = tokio::spawn(async move { - let mut listener = retry_listen_pg(&db).await; - - loop { - tokio::select! { - biased; - Some(_) = async { if let Some(jh) = migration_handle.take() { - tracing::info!("migration job finished"); - Some(jh.await) - } else { - None - }} => { - continue; - }, - _ = monitor_killpill_rx.recv() => { - tracing::info!("received killpill for monitor job"); - break; - }, - _ = tokio::time::sleep(Duration::from_secs(30)) => { - monitor_db( - &db, - &base_internal_url, - server_mode, - worker_mode, - false, - tx.clone(), - ) - .await; - }, - notification = listener.recv() => { - match notification { - Ok(n) => { - tracing::info!("Received new pg notification: {n:?}"); - match n.channel() { - "notify_config_change" => { - match n.payload() { - "server" if server_mode => { - tracing::error!("Server config change detected but server config is obsolete: {}", n.payload()); + let conn = conn.clone(); + match conn { + Connection::Sql(ref db) => { + let base_internal_url = base_internal_url.to_string(); + let db_url: String = get_database_url().await?; + let db = db.clone(); + let h = tokio::spawn(async move { + let mut listener = retry_listen_pg(&db_url).await; + let mut last_listener_refresh = Instant::now(); + loop { + let db = db.clone(); + tokio::select! { + biased; + Some(_) = async { if let Some(jh) = migration_handle.take() { + tracing::info!("migration job finished"); + Some(jh.await) + } else { + None + }} => { + continue; + }, + _ = monitor_killpill_rx.recv() => { + tracing::info!("received killpill for monitor job"); + break; + }, + notification = listener.try_recv() => { + match notification { + Ok(n) => { + if n.is_none() { + tracing::error!("Could not receive notification, attempting to reconnect to pg listener"); + continue; + } + let n = n.unwrap(); + tracing::info!("Received new pg notification: {n:?}"); + match n.channel() { + "notify_config_change" => { + match n.payload() { + "server" if server_mode => { + tracing::error!("Server config change detected but server config is obsolete: {}", n.payload()); + }, + a@ _ if worker_mode && a == format!("worker__{}", *WORKER_GROUP) => { + tracing::info!("Worker config change detected: {}", n.payload()); + reload_worker_config(&db, tx.clone(), true).await; + }, + _ => { + tracing::debug!("config changed but did not target this server/worker"); + } + } }, - a@ _ if worker_mode && a == format!("worker__{}", *WORKER_GROUP) => { - tracing::info!("Worker config change detected: {}", n.payload()); - reload_worker_config(&db, tx.clone(), true).await; + "notify_webhook_change" => { + let workspace_id = n.payload(); + tracing::info!("Webhook change detected, invalidating webhook cache: {}", workspace_id); + windmill_api::webhook_util::WEBHOOK_CACHE.remove(workspace_id); + }, + "notify_workspace_envs_change" => { + let workspace_id = n.payload(); + tracing::info!("Workspace envs change detected, invalidating workspace envs cache: {}", workspace_id); + windmill_common::variables::CUSTOM_ENVS_CACHE.remove(workspace_id); + }, + "notify_workspace_premium_change" => { + let workspace_id = n.payload(); + tracing::info!("Workspace premium change detected, invalidating workspace premium cache: {}", workspace_id); + windmill_common::workspaces::IS_PREMIUM_CACHE.remove(workspace_id); + }, + "notify_runnable_version_change" => { + let payload = n.payload(); + tracing::info!("Runnable version change detected: {}", payload); + match payload.split(':').collect::>().as_slice() { + [workspace_id, source_type, path, kind] => { + let key = (workspace_id.to_string(), path.to_string()); + match source_type { + &"script" => { + windmill_common::DEPLOYED_SCRIPT_HASH_CACHE.remove(&key); + match kind { + &"preprocessor" => { + match sqlx::query_scalar!( + "SELECT fv.id + FROM flow f + INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)] + WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2", + path, + workspace_id + ).fetch_all(&db).await { + Ok(flow_versions) => { + tracing::debug!("Workspace preprocessor {} changed, removing runnable format version cache for flow versions {:?}", path, flow_versions); + for version in flow_versions { + for trigger_kind in TriggerKind::iter() { + let key = (windmill_common::triggers::HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), version, trigger_kind); + windmill_common::triggers::RUNNABLE_FORMAT_VERSION_CACHE.remove(&key); + } + } + } + Err(e) => { + tracing::error!("Error fetching flow paths: {e:#}"); + } + } + }, + _ => {} + } + } + &"flow" => { + windmill_common::FLOW_VERSION_CACHE.remove(&key); + }, + _ => { + tracing::warn!("Unknown runnable version change payload: {}", payload); + } + } + }, + _ => { + tracing::warn!("Unknown runnable version change payload: {}", payload); + } + } + }, + #[cfg(feature = "http_trigger")] + "notify_http_trigger_change" => { + tracing::info!("HTTP trigger change detected: {}", n.payload()); + match windmill_api::http_triggers::refresh_routers(&db).await { + Ok((true, _)) => { + tracing::info!("Refreshed HTTP routers (trigger change)"); + }, + Ok((false, _)) => { + tracing::warn!("Should have refreshed HTTP routers (trigger change) but did not"); + }, + Err(err) => { + tracing::error!("Error refreshing HTTP routers (trigger change): {err:#}"); + } + }; + }, + "notify_token_invalidation" => { + let token = n.payload(); + tracing::info!("Token invalidation detected for token: {}...", &token[..token.len().min(8)]); + windmill_api::auth::invalidate_token_from_cache(token); + }, + "notify_global_setting_change" => { + tracing::info!("Global setting change detected: {}", n.payload()); + match n.payload() { + BASE_URL_SETTING => { + if let Err(e) = reload_base_url_setting(&conn).await { + tracing::error!(error = %e, "Could not reload base url setting"); + } + }, + OAUTH_SETTING => { + if let Err(e) = reload_base_url_setting(&conn).await { + tracing::error!(error = %e, "Could not reload oauth setting"); + } + }, + CUSTOM_TAGS_SETTING => { + if let Err(e) = reload_custom_tags_setting(&db).await { + tracing::error!(error = %e, "Could not reload custom tags setting"); + } + }, + LICENSE_KEY_SETTING => { + if let Err(e) = reload_license_key(&db.into()).await { + tracing::error!("Failed to reload license key: {e:#}"); + } + }, + DEFAULT_TAGS_PER_WORKSPACE_SETTING => { + if let Err(e) = load_tag_per_workspace_enabled(&db).await { + tracing::error!("Error loading default tag per workspace: {e:#}"); + } + }, + DEFAULT_TAGS_WORKSPACES_SETTING => { + if let Err(e) = load_tag_per_workspace_workspaces(&db).await { + tracing::error!("Error loading default tag per workspace workspaces: {e:#}"); + } + }, + SMTP_SETTING => { + reload_smtp_config(&db).await; + }, + TEAMS_SETTING => { + tracing::info!("Teams setting changed."); + }, + INDEXER_SETTING => { + reload_indexer_config(&db).await; + }, + TIMEOUT_WAIT_RESULT_SETTING => { + reload_timeout_wait_result_setting(&conn).await + }, + RETENTION_PERIOD_SECS_SETTING => { + reload_retention_period_setting(&conn).await + }, + MONITOR_LOGS_ON_OBJECT_STORE_SETTING => { + reload_delete_logs_periodically_setting(&conn).await + }, + JOB_DEFAULT_TIMEOUT_SECS_SETTING => { + reload_job_default_timeout_setting(&conn).await + }, + #[cfg(feature = "parquet")] + OBJECT_STORE_CONFIG_SETTING => { + if !disable_s3_store { + reload_object_store_setting(&db).await; + } + }, + SCIM_TOKEN_SETTING => { + reload_scim_token_setting(&conn).await + }, + EXTRA_PIP_INDEX_URL_SETTING => { + reload_extra_pip_index_url_setting(&conn).await + }, + PIP_INDEX_URL_SETTING => { + reload_pip_index_url_setting(&conn).await + }, + INSTANCE_PYTHON_VERSION_SETTING => { + reload_instance_python_version_setting(&conn).await + }, + NPM_CONFIG_REGISTRY_SETTING => { + reload_npm_config_registry_setting(&conn).await + }, + BUNFIG_INSTALL_SCOPES_SETTING => { + reload_bunfig_install_scopes_setting(&conn).await + }, + NUGET_CONFIG_SETTING => { + reload_nuget_config_setting(&conn).await + }, + MAVEN_REPOS_SETTING => { + reload_maven_repos_setting(&conn).await + }, + NO_DEFAULT_MAVEN_SETTING => { + reload_no_default_maven_setting(&conn).await + }, + KEEP_JOB_DIR_SETTING => { + load_keep_job_dir(&conn).await; + }, + REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING => { + load_require_preexisting_user(&db).await; + }, + EXPOSE_METRICS_SETTING => { + tracing::info!("Metrics setting changed, restarting"); + send_delayed_killpill(&tx, 40, "metrics setting change").await; + }, + EMAIL_DOMAIN_SETTING => { + tracing::info!("Email domain setting changed"); + if server_mode { + send_delayed_killpill(&tx, 4, "email domain setting change").await; + } + }, + EXPOSE_DEBUG_METRICS_SETTING => { + if let Err(e) = load_metrics_debug_enabled(&conn).await { + tracing::error!(error = %e, "Could not reload debug metrics setting"); + } + }, + OTEL_SETTING => { + tracing::info!("OTEL setting changed, restarting"); + send_delayed_killpill(&tx, 4, "OTEL setting change").await; + }, + REQUEST_SIZE_LIMIT_SETTING => { + if server_mode { + tracing::info!("Request limit size change detected, killing server expecting to be restarted"); + send_delayed_killpill(&tx, 4, "request size limit change").await; + } + }, + SAML_METADATA_SETTING => { + tracing::info!("SAML metadata change detected, killing server expecting to be restarted"); + send_delayed_killpill(&tx, 0, "SAML metadata change").await; + }, + HUB_BASE_URL_SETTING => { + if let Err(e) = reload_hub_base_url_setting(&conn, server_mode).await { + tracing::error!(error = %e, "Could not reload hub base url setting"); + } + }, + CRITICAL_ERROR_CHANNELS_SETTING => { + if let Err(e) = reload_critical_error_channels_setting(&db).await { + tracing::error!(error = %e, "Could not reload critical error emails setting"); + } + }, + CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING => { + if let Err(e) = reload_critical_alerts_on_db_oversize(&db).await { + tracing::error!(error = %e, "Could not reload critical alerts on db oversize setting"); + } + + }, + JWT_SECRET_SETTING => { + if let Err(e) = reload_jwt_secret_setting(&db).await { + tracing::error!(error = %e, "Could not reload jwt secret setting"); + } + }, + CRITICAL_ALERT_MUTE_UI_SETTING => { + tracing::info!("Critical alert UI setting changed"); + if let Err(e) = reload_critical_alert_mute_ui_setting(&conn).await { + tracing::error!(error = %e, "Could not reload critical alert UI setting"); + } + }, + a @_ => { + tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a); + } + } }, _ => { - tracing::debug!("config changed but did not target this server/worker"); + tracing::warn!("Unknown notification received"); + continue; } } }, - "notify_webhook_change" => { - let workspace_id = n.payload(); - tracing::info!("Webhook change detected, invalidating webhook cache: {}", workspace_id); - windmill_api::webhook_util::WEBHOOK_CACHE.remove(workspace_id); - }, - "notify_global_setting_change" => { - tracing::info!("Global setting change detected: {}", n.payload()); - match n.payload() { - BASE_URL_SETTING => { - if let Err(e) = reload_base_url_setting(&db).await { - tracing::error!(error = %e, "Could not reload base url setting"); - } + Err(e) => { + tracing::error!(error = %e, "Could not receive notification, attempting to reconnect listener"); + tokio::select! { + biased; + _ = monitor_killpill_rx.recv() => { + tracing::info!("received killpill for monitor job"); + break; }, - OAUTH_SETTING => { - if let Err(e) = reload_base_url_setting(&db).await { - tracing::error!(error = %e, "Could not reload oauth setting"); - } - }, - CUSTOM_TAGS_SETTING => { - if let Err(e) = reload_custom_tags_setting(&db).await { - tracing::error!(error = %e, "Could not reload custom tags setting"); - } - }, - LICENSE_KEY_SETTING => { - if let Err(e) = reload_license_key(&db).await { - tracing::error!("Failed to reload license key: {e:#}"); - } - }, - DEFAULT_TAGS_PER_WORKSPACE_SETTING => { - if let Err(e) = load_tag_per_workspace_enabled(&db).await { - tracing::error!("Error loading default tag per workspace: {e:#}"); - } - }, - DEFAULT_TAGS_WORKSPACES_SETTING => { - if let Err(e) = load_tag_per_workspace_workspaces(&db).await { - tracing::error!("Error loading default tag per workspace workspaces: {e:#}"); - } - } - SMTP_SETTING => { - reload_smtp_config(&db).await; - }, - TEAMS_SETTING => { - tracing::info!("Teams setting changed."); - }, - INDEXER_SETTING => { - reload_indexer_config(&db).await; - }, - TIMEOUT_WAIT_RESULT_SETTING => { - reload_timeout_wait_result_setting(&db).await - }, - RETENTION_PERIOD_SECS_SETTING => { - reload_retention_period_setting(&db).await - }, - MONITOR_LOGS_ON_OBJECT_STORE_SETTING => { - reload_delete_logs_periodically_setting(&db).await - }, - JOB_DEFAULT_TIMEOUT_SECS_SETTING => { - reload_job_default_timeout_setting(&db).await - }, - #[cfg(feature = "parquet")] - OBJECT_STORE_CACHE_CONFIG_SETTING => { - if !disable_s3_store { - reload_s3_cache_setting(&db).await - } - }, - SCIM_TOKEN_SETTING => { - reload_scim_token_setting(&db).await - }, - EXTRA_PIP_INDEX_URL_SETTING => { - reload_extra_pip_index_url_setting(&db).await - }, - PIP_INDEX_URL_SETTING => { - reload_pip_index_url_setting(&db).await - }, - INSTANCE_PYTHON_VERSION_SETTING => { - reload_instance_python_version_setting(&db).await - }, - NPM_CONFIG_REGISTRY_SETTING => { - reload_npm_config_registry_setting(&db).await - }, - BUNFIG_INSTALL_SCOPES_SETTING => { - reload_bunfig_install_scopes_setting(&db).await - }, - NUGET_CONFIG_SETTING => { - reload_nuget_config_setting(&db).await - }, - KEEP_JOB_DIR_SETTING => { - load_keep_job_dir(&db).await; - }, - REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING => { - load_require_preexisting_user(&db).await; - }, - EXPOSE_METRICS_SETTING => { - tracing::info!("Metrics setting changed, restarting"); - send_delayed_killpill(&tx, 40, "metrics setting change").await; - }, - EMAIL_DOMAIN_SETTING => { - tracing::info!("Email domain setting changed"); - if server_mode { - send_delayed_killpill(&tx, 4, "email domain setting change").await; - } - }, - EXPOSE_DEBUG_METRICS_SETTING => { - if let Err(e) = load_metrics_debug_enabled(&db).await { - tracing::error!(error = %e, "Could not reload debug metrics setting"); - } - }, - OTEL_SETTING => { - tracing::info!("OTEL setting changed, restarting"); - send_delayed_killpill(&tx, 4, "OTEL setting change").await; - }, - REQUEST_SIZE_LIMIT_SETTING => { - if server_mode { - tracing::info!("Request limit size change detected, killing server expecting to be restarted"); - send_delayed_killpill(&tx, 4, "request size limit change").await; - } - }, - SAML_METADATA_SETTING => { - tracing::info!("SAML metadata change detected, killing server expecting to be restarted"); - send_delayed_killpill(&tx, 0, "SAML metadata change").await; - }, - HUB_BASE_URL_SETTING => { - if let Err(e) = reload_hub_base_url_setting(&db, server_mode).await { - tracing::error!(error = %e, "Could not reload hub base url setting"); - } - }, - CRITICAL_ERROR_CHANNELS_SETTING => { - if let Err(e) = reload_critical_error_channels_setting(&db).await { - tracing::error!(error = %e, "Could not reload critical error emails setting"); - } - }, - JWT_SECRET_SETTING => { - if let Err(e) = reload_jwt_secret_setting(&db).await { - tracing::error!(error = %e, "Could not reload jwt secret setting"); - } - }, - CRITICAL_ALERT_MUTE_UI_SETTING => { - tracing::info!("Critical alert UI setting changed"); - if let Err(e) = reload_critical_alert_mute_ui_setting(&db).await { - tracing::error!(error = %e, "Could not reload critical alert UI setting"); - } - }, - a @_ => { - tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a); + new_listener = retry_listen_pg(&db_url) => { + listener = new_listener; + continue; } } - }, - _ => { - tracing::warn!("Unknown notification received"); - continue; + } + }; + }, + _ = tokio::time::sleep(Duration::from_secs(30)) => { + if last_listener_refresh.elapsed() > Duration::from_secs(*PG_LISTENER_REFRESH_PERIOD_SECS) { + tracing::info!("Refreshing pg listeners, settings and license key after {}s", Duration::from_secs(*PG_LISTENER_REFRESH_PERIOD_SECS).as_secs()); + if let Err(e) = listener.unlisten_all().await { + tracing::error!(error = %e, "Could not unlisten to database"); + } + listener = retry_listen_pg(&db_url).await; + initial_load( + &conn, + tx.clone(), + worker_mode, + server_mode, + #[cfg(feature = "parquet")] + disable_s3_store, + ) + .await; + #[cfg(feature = "enterprise")] + if let Err(err) = reload_license_key(&conn).await { + tracing::error!("Failed to reload license key: {err:#}"); + } + last_listener_refresh = Instant::now(); + } + + if server_mode { + if !*windmill_common::QUIET_LOGS { + tracing::info!("monitor task started"); + } + } + monitor_db( + &conn, + &base_internal_url, + server_mode, + worker_mode, + false, + tx.clone(), + ) + .await; + if server_mode { + if !*windmill_common::QUIET_LOGS { + tracing::info!("monitor task finished"); } } }, - Err(e) => { - tracing::error!(error = %e, "Could not receive notification, attempting to reconnect listener"); - tokio::select! { - biased; - _ = monitor_killpill_rx.recv() => { - tracing::info!("received killpill for monitor job"); - break; - }, - new_listener = retry_listen_pg(&db) => { - listener = new_listener; - continue; - } - } - } - }; + } } + }); + + if let Err(e) = h.await { + tracing::error!("Error waiting for monitor handle: {e:#}") } } - }); + Connection::Http(_) => loop { + tokio::select! { + _ = monitor_killpill_rx.recv() => { + tracing::info!("Received killpill, exiting"); + break; + }, + _ = tokio::time::sleep(Duration::from_secs(12 * 60 * 60)) => { + tracing::info!("Reloading config after 12 hours"); + initial_load(&conn, tx.clone(), worker_mode, server_mode, #[cfg(feature = "parquet")] disable_s3_store).await; + if let Err(e) = reload_license_key(&conn).await { + tracing::error!("Failed to reload license key on agent: {e:#}"); + } + #[cfg(feature = "enterprise")] + ee_oss::verify_license_key().await; + } + } + }, + }; - if let Err(e) = h.await { - tracing::error!("Error waiting for monitor handle: {e:#}") - } tracing::info!("Monitor exited"); + killpill_tx.send(); Ok(()) as anyhow::Result<()> }; @@ -893,37 +1141,45 @@ Windmill Community Edition {GIT_VERSION} }; if server_mode { - schedule_stats(&db, &HTTP_CLIENT).await; + if let Some(db) = conn.as_sql() { + schedule_stats(&db, &HTTP_CLIENT).await; + } } - futures::try_join!( - shutdown_signal, - workers_f, - monitor_f, - server_f, - metrics_f, - indexer_f, - log_indexer_f - )?; + if mcp_mode { + futures::try_join!(shutdown_signal, workers_f, server_f)?; + } else { + futures::try_join!( + shutdown_signal, + workers_f, + monitor_f, + server_f, + metrics_f, + indexer_f, + log_indexer_f + )?; + } } else { tracing::info!("Nothing to do, exiting."); } - send_current_log_file_to_object_store(&db, &hostname, &mode).await; + send_current_log_file_to_object_store(&conn, &hostname, &mode).await; - tracing::info!("Exiting connection pool"); - tokio::select! { - _ = db.close() => { - tracing::info!("Database connection pool closed"); - }, - _ = tokio::time::sleep(Duration::from_secs(15)) => { - tracing::warn!("Could not close database connection pool in time (15s). Exiting anyway."); + if let Some(db) = conn.as_sql() { + tracing::info!("Exiting connection pool"); + tokio::select! { + _ = db.close() => { + tracing::info!("Database connection pool closed"); + }, + _ = tokio::time::sleep(Duration::from_secs(15)) => { + tracing::warn!("Could not close database connection pool in time (15s). Exiting anyway."); + } } } Ok(()) } -async fn listen_pg(db: &DB) -> Option { - let mut listener = match PgListener::connect_with(&db).await { +async fn listen_pg(url: &str) -> Option { + let mut listener = match PgListener::connect(url).await { Ok(l) => l, Err(e) => { tracing::error!(error = %e, "Could not connect to database"); @@ -931,14 +1187,23 @@ async fn listen_pg(db: &DB) -> Option { } }; - if let Err(e) = listener - .listen_all(vec![ - "notify_config_change", - "notify_global_setting_change", - "notify_webhook_change", - ]) - .await - { + #[allow(unused_mut)] + let mut channels = vec![ + "notify_config_change", + "notify_global_setting_change", + "notify_webhook_change", + "notify_workspace_envs_change", + "notify_runnable_version_change", + "notify_token_invalidation", + ]; + + #[cfg(feature = "http_trigger")] + channels.push("notify_http_trigger_change"); + + #[cfg(feature = "cloud")] + channels.push("notify_workspace_premium_change"); + + if let Err(e) = listener.listen_all(channels).await { tracing::error!(error = %e, "Could not listen to database"); return None; } @@ -946,13 +1211,13 @@ async fn listen_pg(db: &DB) -> Option { return Some(listener); } -async fn retry_listen_pg(db: &DB) -> PgListener { - let mut listener = listen_pg(db).await; +async fn retry_listen_pg(url: &str) -> PgListener { + let mut listener = listen_pg(url).await; loop { if listener.is_none() { tracing::info!("Retrying listening to pg listen in 5 seconds"); tokio::time::sleep(Duration::from_secs(5)).await; - listener = listen_pg(db).await; + listener = listen_pg(url).await; } else { tracing::info!("Successfully connected to pg listen"); return listener.unwrap(); @@ -977,16 +1242,20 @@ fn display_config(envs: &[&str]) { ) } +pub struct WorkerConn { + conn: Connection, + worker_name: String, +} + pub async fn run_workers( - db: Pool, mut rx: tokio::sync::broadcast::Receiver<()>, - tx: tokio::sync::broadcast::Sender<()>, - num_workers: i32, + tx: KillpillSender, base_internal_url: String, - agent_mode: bool, hostname: String, + workers: &[WorkerConn], ) -> anyhow::Result<()> { let mut killpill_rxs = vec![]; + let num_workers = workers.len(); for _ in 0..num_workers { killpill_rxs.push(rx.resubscribe()); } @@ -995,14 +1264,6 @@ pub async fn run_workers( tracing::info!("Received killpill, exiting"); return Ok(()); } - let instance_name = hostname - .clone() - .replace(" ", "") - .split("-") - .last() - .unwrap() - .to_ascii_lowercase() - .to_string(); // #[cfg(tokio_unstable)] // let monitor = tokio_metrics::TaskMonitor::new(); @@ -1027,17 +1288,16 @@ pub async fn run_workers( PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR, - TAR_PY310_CACHE_DIR, - TAR_PY311_CACHE_DIR, - TAR_PY312_CACHE_DIR, - TAR_PY313_CACHE_DIR, BUN_BUNDLE_CACHE_DIR, GO_CACHE_DIR, GO_BIN_CACHE_DIR, RUST_CACHE_DIR, CSHARP_CACHE_DIR, + NU_CACHE_DIR, HUB_CACHE_DIR, POWERSHELL_CACHE_DIR, + JAVA_CACHE_DIR, + TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG ] { DirBuilder::new() .recursive(true) @@ -1049,10 +1309,12 @@ pub async fn run_workers( "Starting {num_workers} workers and SLEEP_QUEUE={}ms", *windmill_worker::SLEEP_QUEUE ); + for i in 1..(num_workers + 1) { - let db1 = db.clone(); - let instance_name = instance_name.clone(); - let worker_name = format!("wk-{}-{}-{}", *WORKER_GROUP, &instance_name, rd_string(5)); + let wk_conf = &workers[i as usize - 1]; + let conn1 = wk_conf.conn.clone(); + let worker_name = wk_conf.worker_name.clone(); + WORKERS_NAMES.write().await.push(worker_name.clone()); let ip = ip.clone(); let rx = killpill_rxs.pop().unwrap(); let tx = tx.clone(); @@ -1065,7 +1327,7 @@ pub async fn run_workers( } let f = windmill_worker::run_worker( - &db1, + &conn1, &hostname, worker_name, i as u64, @@ -1074,7 +1336,6 @@ pub async fn run_workers( rx, tx, &base_internal_url, - agent_mode, ); // #[cfg(tokio_unstable)] @@ -1093,11 +1354,7 @@ pub async fn run_workers( Ok(()) } -async fn send_delayed_killpill( - tx: &tokio::sync::broadcast::Sender<()>, - mut max_delay_secs: u64, - context: &str, -) { +async fn send_delayed_killpill(tx: &KillpillSender, mut max_delay_secs: u64, context: &str) { if max_delay_secs == 0 { max_delay_secs = 1; } @@ -1106,7 +1363,5 @@ async fn send_delayed_killpill( tracing::info!("Scheduling {context} shutdown in {rd_delay}s"); tokio::time::sleep(Duration::from_secs(rd_delay)).await; - if let Err(e) = tx.send(()) { - tracing::error!(error = %e, "Could not send killpill for {context}"); - } + tx.send(); } diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 6fe8070014..4b4946e3e4 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -6,14 +6,14 @@ use std::{ str::FromStr, sync::{ atomic::{AtomicU16, Ordering}, - Arc, + Arc, Mutex, }, time::Duration, }; use chrono::{NaiveDateTime, Utc}; use futures::{stream::FuturesUnordered, StreamExt}; -use serde::{de::DeserializeOwned, Deserializer}; +use serde::{de::DeserializeOwned, Deserialize}; use sqlx::{Pool, Postgres}; use tokio::{ join, @@ -29,22 +29,30 @@ use windmill_api::{ }; #[cfg(feature = "enterprise")] -use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts}; +use windmill_common::ee_oss::low_disk_alerts; +#[cfg(feature = "enterprise")] +use windmill_common::ee_oss::{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::{ - ee::CriticalErrorChannel, + agent_workers::DECODED_AGENT_TOKEN, + auth::create_token_for_owner, + ee_oss::CriticalErrorChannel, error, flow_status::{FlowStatus, FlowStatusModule}, global_settings::{ - BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, - CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, - DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, - EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, - JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, - LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, - NUGET_CONFIG_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, + CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, + DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, + EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, + HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, + JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, + MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, + OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING, }, @@ -55,37 +63,33 @@ use windmill_common::{ server::load_smtp_config, tracing_init::JSON_FMT, users::truncate_token, - utils::{now_from_db, rd_string, report_critical_error, Mode}, + utils::{empty_as_none, now_from_db, rd_string, report_critical_error, Mode}, worker::{ - load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting, - update_min_version, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, + load_env_vars, load_init_bash_from_env, load_whitelist_env_vars_from_env, + load_worker_config, reload_custom_tags_setting, store_pull_query, + store_suspended_pull_query, update_min_version, Connection, WorkerConfig, + DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR, WORKER_CONFIG, WORKER_GROUP, }, - BASE_URL, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, - HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED, - MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, - SERVICE_LOG_RETENTION_SECS, + KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERT_MUTE_UI_ENABLED, + CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, + METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, + OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS, }; -use windmill_queue::cancel_job; +use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload}; use windmill_worker::{ - create_token_for_owner, handle_job_error, AuthedClient, SameWorkerPayload, SameWorkerSender, - SendResult, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, - NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, SCRIPT_TOKEN_EXPIRY, + handle_job_error, 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::{ - 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; +use windmill_common::s3_helpers::ObjectStoreReload; #[cfg(feature = "enterprise")] -use crate::ee::verify_license_key; +use crate::ee_oss::verify_license_key; -use crate::ee::set_license_key; +use crate::ee_oss::set_license_key; #[cfg(feature = "prometheus")] lazy_static::lazy_static! { @@ -125,87 +129,162 @@ lazy_static::lazy_static! { .and_then(|x| x.parse::().ok()) .unwrap_or(true); + pub static ref DISABLE_ZOMBIE_JOBS_MONITORING: bool = std::env::var("DISABLE_ZOMBIE_JOBS_MONITORING") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(false); + pub static ref WORKERS_NAMES: Arc>> = Arc::new(RwLock::new(Vec::new())); static ref QUEUE_COUNT_TAGS: Arc>> = Arc::new(RwLock::new(Vec::new())); + static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true"); } pub async fn initial_load( - db: &Pool, - tx: tokio::sync::broadcast::Sender<()>, + conn: &Connection, + tx: KillpillSender, worker_mode: bool, server_mode: bool, #[cfg(feature = "parquet")] disable_s3_store: bool, ) { - if let Err(e) = load_metrics_enabled(db).await { + if let Err(e) = reload_base_url_setting(&conn).await { + tracing::error!("Error loading base url: {:?}", e) + } + + if let Some(db) = conn.as_sql() { + if let Err(e) = reload_critical_error_channels_setting(&db).await { + tracing::error!("Could loading critical error emails setting: {:?}", e); + } + } + + if let Err(e) = load_metrics_enabled(conn).await { tracing::error!("Error loading expose metrics: {e:#}"); } - if let Err(e) = load_metrics_debug_enabled(db).await { + if let Err(e) = load_metrics_debug_enabled(conn).await { tracing::error!("Error loading expose debug metrics: {e:#}"); } - if let Err(e) = reload_critical_alert_mute_ui_setting(db).await { + if let Err(e) = reload_critical_alert_mute_ui_setting(conn).await { tracing::error!("Error loading critical alert mute ui setting: {e:#}"); } - if let Err(e) = load_tag_per_workspace_enabled(db).await { - tracing::error!("Error loading default tag per workpsace: {e:#}"); - } + if let Some(db) = conn.as_sql() { + if let Err(e) = load_tag_per_workspace_enabled(db).await { + tracing::error!("Error loading default tag per workpsace: {e:#}"); + } - if let Err(e) = load_tag_per_workspace_workspaces(db).await { - tracing::error!("Error loading default tag per workpsace workspaces: {e:#}"); + if let Err(e) = load_tag_per_workspace_workspaces(db).await { + tracing::error!("Error loading default tag per workpsace workspaces: {e:#}"); + } } if server_mode { - load_require_preexisting_user(db).await; + if let Some(db) = conn.as_sql() { + load_require_preexisting_user(db).await; + if let Err(e) = reload_critical_alerts_on_db_oversize(db).await { + tracing::error!( + "Error reloading critical alerts on db oversize setting: {:?}", + e + ) + } + } } if worker_mode { - load_keep_job_dir(db).await; - reload_worker_config(&db, tx, false).await; + load_keep_job_dir(conn).await; + match conn { + Connection::Sql(db) => { + reload_worker_config(&db, tx, false).await; + } + Connection::Http(_) => { + // TODO: reload worker config from http + let mut config = WORKER_CONFIG.write().await; + *config = WorkerConfig { + worker_tags: DECODED_AGENT_TOKEN + .as_ref() + .map(|x| x.tags.clone()) + .unwrap_or_default(), + env_vars: load_env_vars( + load_whitelist_env_vars_from_env(), + &std::collections::HashMap::new(), + ), + priority_tags_sorted: vec![], + dedicated_worker: None, + init_bash: load_init_bash_from_env(), + cache_clear: None, + additional_python_paths: None, + pip_local_dependencies: None, + }; + } + } } - if let Err(e) = reload_custom_tags_setting(db).await { - tracing::error!("Error reloading custom tags: {:?}", e) - } - - if let Err(e) = reload_hub_base_url_setting(db, server_mode).await { + if let Err(e) = reload_hub_base_url_setting(conn, server_mode).await { tracing::error!("Error reloading hub base url: {:?}", e) } - if let Err(e) = reload_jwt_secret_setting(&db).await { - tracing::error!("Could not reload jwt secret setting: {:?}", e); + if let Some(db) = conn.as_sql() { + if let Err(e) = reload_jwt_secret_setting(db).await { + tracing::error!("Could not reload jwt secret setting: {:?}", e); + } + + if let Err(e) = reload_custom_tags_setting(db).await { + tracing::error!("Error reloading custom tags: {:?}", e) + } } #[cfg(feature = "parquet")] if !disable_s3_store { - reload_s3_cache_setting(&db).await; + 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_smtp_config(&db).await; + if let Some(db) = conn.as_sql() { + reload_smtp_config(db).await; + } if server_mode { - reload_retention_period_setting(&db).await; - reload_request_size(&db).await; - reload_saml_metadata_setting(&db).await; - reload_scim_token_setting(&db).await; + reload_retention_period_setting(&conn).await; + reload_request_size(&conn).await; + reload_saml_metadata_setting(&conn).await; + reload_scim_token_setting(&conn).await; } if worker_mode { - reload_job_default_timeout_setting(&db).await; - reload_extra_pip_index_url_setting(&db).await; - reload_pip_index_url_setting(&db).await; - reload_npm_config_registry_setting(&db).await; - reload_bunfig_install_scopes_setting(&db).await; - reload_instance_python_version_setting(&db).await; - reload_nuget_config_setting(&db).await; + reload_job_default_timeout_setting(&conn).await; + reload_extra_pip_index_url_setting(&conn).await; + reload_pip_index_url_setting(&conn).await; + reload_npm_config_registry_setting(&conn).await; + reload_bunfig_install_scopes_setting(&conn).await; + reload_instance_python_version_setting(&conn).await; + reload_nuget_config_setting(&conn).await; + reload_maven_repos_setting(&conn).await; + reload_no_default_maven_setting(&conn).await; } } -pub async fn load_metrics_enabled(db: &DB) -> error::Result<()> { - let metrics_enabled = load_value_from_global_settings(db, EXPOSE_METRICS_SETTING).await; +pub async fn load_metrics_enabled(conn: &Connection) -> error::Result<()> { + let metrics_enabled = + load_value_from_global_settings_with_conn(conn, EXPOSE_METRICS_SETTING, true).await; match metrics_enabled { Ok(Some(serde_json::Value::Bool(t))) => METRICS_ENABLED.store(t, Ordering::Relaxed), _ => (), @@ -213,26 +292,18 @@ pub async fn load_metrics_enabled(db: &DB) -> error::Result<()> { Ok(()) } -fn empty_string_as_none<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let option = as serde::Deserialize>::deserialize(deserializer)?; - Ok(option.filter(|s| !s.is_empty())) -} - #[derive(serde::Deserialize)] struct OtelSetting { metrics_enabled: Option, logs_enabled: Option, tracing_enabled: Option, - #[serde(default, deserialize_with = "empty_string_as_none")] + #[serde(default, deserialize_with = "empty_as_none")] otel_exporter_otlp_endpoint: Option, - #[serde(default, deserialize_with = "empty_string_as_none")] + #[serde(default, deserialize_with = "empty_as_none")] otel_exporter_otlp_headers: Option, - #[serde(default, deserialize_with = "empty_string_as_none")] + #[serde(default, deserialize_with = "empty_as_none")] otel_exporter_otlp_protocol: Option, - #[serde(default, deserialize_with = "empty_string_as_none")] + #[serde(default, deserialize_with = "empty_as_none")] otel_exporter_otlp_compression: Option, } @@ -328,26 +399,18 @@ pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> { Ok(()) } -pub async fn reload_critical_alert_mute_ui_setting(db: &DB) -> error::Result<()> { +pub async fn reload_critical_alert_mute_ui_setting(conn: &Connection) -> error::Result<()> { if let Ok(Some(serde_json::Value::Bool(t))) = - load_value_from_global_settings(db, CRITICAL_ALERT_MUTE_UI_SETTING).await + load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true).await { CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed); - - if t { - if let Err(e) = sqlx::query!("UPDATE alerts SET acknowledged = true") - .execute(db) - .await - { - tracing::error!("Error updating alerts: {}", e.to_string()); - } - } } Ok(()) } -pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> { - let metrics_enabled = load_value_from_global_settings(db, EXPOSE_DEBUG_METRICS_SETTING).await; +pub async fn load_metrics_debug_enabled(conn: &Connection) -> error::Result<()> { + let metrics_enabled = + load_value_from_global_settings_with_conn(conn, EXPOSE_DEBUG_METRICS_SETTING, true).await; match metrics_enabled { Ok(Some(serde_json::Value::Bool(t))) => { METRICS_DEBUG_ENABLED.store(t, Ordering::Relaxed); @@ -498,8 +561,8 @@ fn get_worker_group(mode: &Mode) -> Option { } } -pub fn send_logs_to_object_store(db: &DB, hostname: &str, mode: &Mode) { - let db = db.clone(); +pub fn send_logs_to_object_store(conn: &Connection, hostname: &str, mode: &Mode) { + let conn = conn.clone(); let hostname = hostname.to_string(); let mode = mode.clone(); let worker_group = get_worker_group(&mode); @@ -514,7 +577,7 @@ pub fn send_logs_to_object_store(db: &DB, hostname: &str, mode: &Mode) { &hostname, &mode, &worker_group, - &db, + &conn, snd_highest_file, false, ) @@ -523,11 +586,11 @@ pub fn send_logs_to_object_store(db: &DB, hostname: &str, mode: &Mode) { }); } -pub async fn send_current_log_file_to_object_store(db: &DB, hostname: &str, mode: &Mode) { +pub async fn send_current_log_file_to_object_store(conn: &Connection, hostname: &str, mode: &Mode) { tracing::info!("Sending current log file to object store"); let (highest_file, _) = find_two_highest_files(hostname).await; let worker_group = get_worker_group(&mode); - send_log_file_to_object_store(hostname, mode, &worker_group, db, highest_file, true).await; + send_log_file_to_object_store(hostname, mode, &worker_group, conn, highest_file, true).await; } fn get_now_and_str() -> (NaiveDateTime, String) { @@ -539,11 +602,15 @@ fn get_now_and_str() -> (NaiveDateTime, String) { ) } +lazy_static::lazy_static! { + static ref LAST_LOG_FILE_SENT: Arc>> = Arc::new(Mutex::new(None)); +} + async fn send_log_file_to_object_store( hostname: &str, mode: &Mode, worker_group: &Option, - db: &Pool, + conn: &Connection, snd_highest_file: Option, use_now: bool, ) { @@ -566,27 +633,18 @@ async fn send_log_file_to_object_store( .unwrap_or_else(get_now_and_str) }; - let exists = sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM log_file WHERE hostname = $1 AND log_ts = $2)", - hostname, - ts - ) - .fetch_one(db) - .await; + let exists = LAST_LOG_FILE_SENT.lock().map(|last_log_file_sent| { + last_log_file_sent + .map(|last_log_file_sent| last_log_file_sent >= ts) + .unwrap_or(false) + }); - match exists { - Ok(Some(true)) => { - return; - } - Err(e) => { - tracing::error!("Error checking if log file exists: {:?}", e); - return; - } - _ => (), + if exists.unwrap_or(false) { + return; } #[cfg(feature = "parquet")] - let s3_client = OBJECT_STORE_CACHE_SETTINGS.read().await.clone(); + let s3_client = windmill_common::s3_helpers::get_object_store().await; #[cfg(feature = "parquet")] if let Some(s3_client) = s3_client { let path = std::path::Path::new(TMP_WINDMILL_LOGS_SERVICE) @@ -614,11 +672,25 @@ async fn send_log_file_to_object_store( let (ok_lines, err_lines) = read_log_counters(ts_str); - if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)", - hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT) - .execute(db) - .await { - tracing::error!("Error inserting log file: {:?}", e); + if let Some(db) = conn.as_sql() { + if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) + VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8) + ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7", + hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT) + .execute(db) + .await { + tracing::error!("Error inserting log file: {:?}", e); + } else { + if let Err(e) = LAST_LOG_FILE_SENT.lock().map(|mut last_log_file_sent| { + last_log_file_sent.replace(ts); + }) { + tracing::error!("Error updating last log file sent: {:?}", e); + } + tracing::info!("Log file sent: {}", highest_file); + } + } else { + // tracing::warn!("Not sending log file to object store in agent mode"); + () } } } @@ -641,8 +713,8 @@ fn read_log_counters(ts_str: String) -> (usize, usize) { (ok_lines, err_lines) } -pub async fn load_keep_job_dir(db: &DB) { - let value = load_value_from_global_settings(db, KEEP_JOB_DIR_SETTING).await; +pub async fn load_keep_job_dir(conn: &Connection) { + let value = load_value_from_global_settings_with_conn(conn, KEEP_JOB_DIR_SETTING, true).await; match value { Ok(Some(serde_json::Value::Bool(t))) => KEEP_JOB_DIR.store(t, Ordering::Relaxed), Err(e) => { @@ -768,17 +840,31 @@ pub async fn delete_expired_items(db: &DB) -> () { tracing::error!("Error deleting audit log on CE: {:?}", e); } + match sqlx::query_scalar!( + "DELETE FROM agent_token_blacklist WHERE expires_at <= now() RETURNING token", + ) + .fetch_all(db) + .await + { + Ok(deleted_tokens) => { + if deleted_tokens.len() > 0 { + tracing::info!( + "deleted {} expired blacklisted agent tokens: {:?}", + deleted_tokens.len(), + deleted_tokens + ); + } + } + Err(e) => tracing::error!("Error deleting expired blacklisted agent tokens: {:?}", e), + } + let job_retention_secs = *JOB_RETENTION_SECS.read().await; if job_retention_secs > 0 { match db.begin().await { Ok(mut tx) => { let deleted_jobs = sqlx::query_scalar!( "DELETE FROM v2_job_completed c - USING v2_job j - WHERE - created_at <= now() - ($1::bigint::text || ' s')::interval - AND completed_at + ($1::bigint::text || ' s')::interval <= now() - AND c.id = j.id + WHERE completed_at <= now() - ($1::bigint::text || ' s')::interval RETURNING c.id", job_retention_secs ) @@ -862,10 +948,7 @@ async fn delete_log_files_from_disk_and_store( _s3_prefix: &str, ) { #[cfg(feature = "parquet")] - let os = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone(); + let os = windmill_common::s3_helpers::get_object_store().await; #[cfg(not(feature = "parquet"))] let os: Option<()> = None; @@ -909,23 +992,23 @@ async fn delete_log_files_from_disk_and_store( let _: Vec<_> = delete_futures.collect().await; } -pub async fn reload_scim_token_setting(db: &DB) { - reload_option_setting_with_tracing(db, SCIM_TOKEN_SETTING, "SCIM_TOKEN", SCIM_TOKEN.clone()) +pub async fn reload_scim_token_setting(conn: &Connection) { + reload_option_setting_with_tracing(conn, SCIM_TOKEN_SETTING, "SCIM_TOKEN", SCIM_TOKEN.clone()) .await; } -pub async fn reload_timeout_wait_result_setting(db: &DB) { +pub async fn reload_timeout_wait_result_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, TIMEOUT_WAIT_RESULT_SETTING, "TIMEOUT_WAIT_RESULT", TIMEOUT_WAIT_RESULT.clone(), ) .await; } -pub async fn reload_saml_metadata_setting(db: &DB) { +pub async fn reload_saml_metadata_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, SAML_METADATA_SETTING, "SAML_METADATA", SAML_METADATA.clone(), @@ -933,9 +1016,9 @@ pub async fn reload_saml_metadata_setting(db: &DB) { .await; } -pub async fn reload_extra_pip_index_url_setting(db: &DB) { +pub async fn reload_extra_pip_index_url_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, EXTRA_PIP_INDEX_URL_SETTING, "PIP_EXTRA_INDEX_URL", PIP_EXTRA_INDEX_URL.clone(), @@ -943,9 +1026,9 @@ pub async fn reload_extra_pip_index_url_setting(db: &DB) { .await; } -pub async fn reload_pip_index_url_setting(db: &DB) { +pub async fn reload_pip_index_url_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, PIP_INDEX_URL_SETTING, "PIP_INDEX_URL", PIP_INDEX_URL.clone(), @@ -953,9 +1036,9 @@ pub async fn reload_pip_index_url_setting(db: &DB) { .await; } -pub async fn reload_instance_python_version_setting(db: &DB) { +pub async fn reload_instance_python_version_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, INSTANCE_PYTHON_VERSION_SETTING, "INSTANCE_PYTHON_VERSION", INSTANCE_PYTHON_VERSION.clone(), @@ -963,9 +1046,9 @@ pub async fn reload_instance_python_version_setting(db: &DB) { .await; } -pub async fn reload_npm_config_registry_setting(db: &DB) { +pub async fn reload_npm_config_registry_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, NPM_CONFIG_REGISTRY_SETTING, "NPM_CONFIG_REGISTRY", NPM_CONFIG_REGISTRY.clone(), @@ -973,9 +1056,9 @@ pub async fn reload_npm_config_registry_setting(db: &DB) { .await; } -pub async fn reload_bunfig_install_scopes_setting(db: &DB) { +pub async fn reload_bunfig_install_scopes_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, BUNFIG_INSTALL_SCOPES_SETTING, "BUNFIG_INSTALL_SCOPES", BUNFIG_INSTALL_SCOPES.clone(), @@ -983,19 +1066,43 @@ pub async fn reload_bunfig_install_scopes_setting(db: &DB) { .await; } -pub async fn reload_nuget_config_setting(db: &DB) { +pub async fn reload_nuget_config_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, NUGET_CONFIG_SETTING, "NUGET_CONFIG", NUGET_CONFIG.clone(), ) .await; } +pub async fn reload_maven_repos_setting(conn: &Connection) { + reload_option_setting_with_tracing( + conn, + windmill_common::global_settings::MAVEN_REPOS_SETTING, + "MAVEN_REPOS", + MAVEN_REPOS.clone(), + ) + .await; +} +pub async fn reload_no_default_maven_setting(conn: &Connection) { + let value = load_value_from_global_settings_with_conn( + conn, + windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING, + true, + ) + .await; + match value { + Ok(Some(serde_json::Value::Bool(t))) => NO_DEFAULT_MAVEN.store(t, Ordering::Relaxed), + Err(e) => { + tracing::error!("Error loading no default maven repository: {e:#}"); + } + _ => (), + }; +} -pub async fn reload_retention_period_setting(db: &DB) { +pub async fn reload_retention_period_setting(conn: &Connection) { if let Err(e) = reload_setting( - db, + conn, RETENTION_PERIOD_SECS_SETTING, "JOB_RETENTION_SECS", 60 * 60 * 24 * 30, @@ -1007,9 +1114,9 @@ pub async fn reload_retention_period_setting(db: &DB) { tracing::error!("Error reloading retention period: {:?}", e) } } -pub async fn reload_delete_logs_periodically_setting(db: &DB) { +pub async fn reload_delete_logs_periodically_setting(conn: &Connection) { if let Err(e) = reload_setting( - db, + conn, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, "MONITOR_LOGS_ON_OBJECT_STORE", false, @@ -1022,64 +1129,9 @@ pub async fn reload_delete_logs_periodically_setting(db: &DB) { } } -#[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::(v); - if let Err(e) = setting { - tracing::error!("Error parsing s3 cache config: {:?}", e) - } else { - let s3_client = build_object_store_from_settings(setting.unwrap()).await; - if let Err(e) = s3_client { - tracing::error!("Error building s3 client from settings: {:?}", e) - } else { - *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(db: &DB) { +pub async fn reload_job_default_timeout_setting(conn: &Connection) { reload_option_setting_with_tracing( - db, + conn, JOB_DEFAULT_TIMEOUT_SECS_SETTING, "JOB_DEFAULT_TIMEOUT_SECS", JOB_DEFAULT_TIMEOUT.clone(), @@ -1087,9 +1139,9 @@ pub async fn reload_job_default_timeout_setting(db: &DB) { .await; } -pub async fn reload_request_size(db: &DB) { +pub async fn reload_request_size(conn: &Connection) { if let Err(e) = reload_setting( - db, + conn, REQUEST_SIZE_LIMIT_SETTING, "REQUEST_SIZE_LIMIT", DEFAULT_BODY_LIMIT, @@ -1102,8 +1154,8 @@ pub async fn reload_request_size(db: &DB) { } } -pub async fn reload_license_key(db: &DB) -> anyhow::Result<()> { - let q = load_value_from_global_settings(db, LICENSE_KEY_SETTING) +pub async fn reload_license_key(conn: &Connection) -> anyhow::Result<()> { + let q = load_value_from_global_settings_with_conn(conn, LICENSE_KEY_SETTING, true) .await .map_err(|err| anyhow::anyhow!("Error reloading license key: {}", err.to_string()))?; @@ -1128,12 +1180,12 @@ pub async fn reload_license_key(db: &DB) -> anyhow::Result<()> { } pub async fn reload_option_setting_with_tracing( - db: &DB, + conn: &Connection, setting_name: &str, std_env_var: &str, lock: Arc>>, ) { - if let Err(e) = reload_option_setting(db, setting_name, std_env_var, lock.clone()).await { + if let Err(e) = reload_option_setting(conn, setting_name, std_env_var, lock.clone()).await { tracing::error!("Error reloading setting {}: {:?}", setting_name, e) } } @@ -1152,8 +1204,31 @@ pub async fn load_value_from_global_settings( Ok(r) } +pub async fn load_value_from_global_settings_with_conn( + conn: &Connection, + setting_name: &str, + load_from_http: bool, +) -> anyhow::Result> { + match conn { + Connection::Sql(db) => Ok(load_value_from_global_settings(db, setting_name).await?), + Connection::Http(client) => { + if load_from_http { + client + .get::>(&format!( + "/api/agent_workers/get_global_setting/{}", + setting_name + )) + .await + .map_err(|e| anyhow::anyhow!("Error loading setting {}: {}", setting_name, e)) + } else { + Ok(None) + } + } + } +} + pub async fn reload_option_setting( - db: &DB, + conn: &Connection, setting_name: &str, std_env_var: &str, lock: Arc>>, @@ -1168,7 +1243,7 @@ pub async fn reload_option_setting( return Ok(()); } - let q = load_value_from_global_settings(db, setting_name).await?; + let q = load_value_from_global_settings_with_conn(conn, setting_name, true).await?; let mut value = std::env::var(std_env_var) .ok() @@ -1195,14 +1270,14 @@ pub async fn reload_option_setting( } pub async fn reload_setting( - db: &DB, + conn: &Connection, setting_name: &str, std_env_var: &str, default: T, lock: Arc>, transformer: fn(T) -> T, ) -> error::Result<()> { - let q = load_value_from_global_settings(db, setting_name).await?; + let q = load_value_from_global_settings_with_conn(conn, setting_name, true).await?; let mut value = std::env::var(std_env_var) .ok() @@ -1260,27 +1335,31 @@ pub async fn monitor_pool(db: &DB) { } pub async fn monitor_db( - db: &Pool, + conn: &Connection, base_internal_url: &str, server_mode: bool, _worker_mode: bool, initial_load: bool, - _killpill_tx: tokio::sync::broadcast::Sender<()>, + _killpill_tx: KillpillSender, ) { let zombie_jobs_f = async { - if server_mode && !initial_load { - handle_zombie_jobs(db, base_internal_url, "server").await; - match handle_zombie_flows(db).await { - Err(err) => { - tracing::error!("Error handling zombie flows: {:?}", err); + if server_mode && !initial_load && !*DISABLE_ZOMBIE_JOBS_MONITORING { + if let Some(db) = conn.as_sql() { + handle_zombie_jobs(db, base_internal_url, "server").await; + match handle_zombie_flows(db).await { + Err(err) => { + tracing::error!("Error handling zombie flows: {:?}", err); + } + _ => {} } - _ => {} } } }; let expired_items_f = async { if server_mode && !initial_load { - delete_expired_items(&db).await; + if let Some(db) = conn.as_sql() { + delete_expired_items(&db).await; + } } }; @@ -1293,35 +1372,60 @@ pub async fn monitor_db( let expose_queue_metrics_f = async { if !initial_load && server_mode { - expose_queue_metrics(&db).await; + if let Some(db) = conn.as_sql() { + expose_queue_metrics(&db).await; + } } }; let worker_groups_alerts_f = async { #[cfg(feature = "enterprise")] if server_mode && !initial_load { - worker_groups_alerts(&db).await; + if let Some(db) = conn.as_sql() { + worker_groups_alerts(&db).await; + } } }; let jobs_waiting_alerts_f = async { #[cfg(feature = "enterprise")] if server_mode { - jobs_waiting_alerts(&db).await; + if let Some(db) = conn.as_sql() { + jobs_waiting_alerts(&db).await; + } + } + }; + + let low_disk_alerts_f = async { + #[cfg(feature = "enterprise")] + if let Some(db) = conn.as_sql() { + low_disk_alerts( + &db, + server_mode, + _worker_mode, + WORKERS_NAMES.read().await.clone(), + ) + .await; + } + #[cfg(not(feature = "enterprise"))] + { + () } }; let apply_autoscaling_f = async { #[cfg(feature = "enterprise")] if server_mode && !initial_load { - if let Err(e) = windmill_autoscaling::apply_all_autoscaling(db).await { - tracing::error!("Error applying autoscaling: {:?}", e); + if let Some(db) = conn.as_sql() { + if let Err(e) = windmill_autoscaling::apply_all_autoscaling(db).await { + tracing::error!("Error applying autoscaling: {:?}", e); + } } } }; let update_min_worker_version_f = async { - update_min_version(db).await; + update_min_version(conn).await; }; join!( @@ -1331,6 +1435,7 @@ pub async fn monitor_db( verify_license_key_f, worker_groups_alerts_f, jobs_waiting_alerts_f, + low_disk_alerts_f, apply_autoscaling_f, update_min_worker_version_f, ); @@ -1439,12 +1544,8 @@ pub async fn reload_indexer_config(db: &Pool) { } } -pub async fn reload_worker_config( - db: &DB, - tx: tokio::sync::broadcast::Sender<()>, - kill_if_change: bool, -) { - let config = load_worker_config(&db, tx.clone()).await; +pub async fn reload_worker_config(db: &DB, tx: KillpillSender, kill_if_change: bool) { + let config = load_worker_config(db, tx.clone()).await; if let Err(e) = config { tracing::error!("Error reloading worker config: {:?}", e) } else { @@ -1456,17 +1557,17 @@ pub async fn reload_worker_config( || (*wc).dedicated_worker != config.dedicated_worker { tracing::info!("Dedicated worker config changed, sending killpill. Expecting to be restarted by supervisor."); - let _ = tx.send(()); + let _ = tx.send(); } if (*wc).init_bash != config.init_bash { tracing::info!("Init bash config changed, sending killpill. Expecting to be restarted by supervisor."); - let _ = tx.send(()); + let _ = tx.send(); } if (*wc).cache_clear != config.cache_clear { tracing::info!("Cache clear changed, sending killpill. Expecting to be restarted by supervisor."); - let _ = tx.send(()); + let _ = tx.send(); tracing::info!("Waiting 5 seconds to allow others workers to start potential jobs that depend on a potential shared cache volume"); tokio::time::sleep(Duration::from_secs(5)).await; if let Err(e) = windmill_worker::common::clean_cache().await { @@ -1478,15 +1579,16 @@ pub async fn reload_worker_config( let mut wc = WORKER_CONFIG.write().await; tracing::info!("Reloading worker config..."); - make_suspended_pull_query(&config).await; - make_pull_query(&config).await; + store_suspended_pull_query(&config).await; + store_pull_query(&config).await; *wc = config } } } -pub async fn load_base_url(db: &DB) -> error::Result { - let q_base_url = load_value_from_global_settings(db, BASE_URL_SETTING).await?; +pub async fn load_base_url(conn: &Connection) -> error::Result { + let q_base_url = + load_value_from_global_settings_with_conn(conn, BASE_URL_SETTING, false).await?; let std_base_url = std::env::var("BASE_URL") .ok() @@ -1516,34 +1618,38 @@ pub async fn load_base_url(db: &DB) -> error::Result { Ok(base_url) } -pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> { +pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> { #[cfg(feature = "oauth2")] - let q_oauth = load_value_from_global_settings(db, OAUTH_SETTING).await?; + let oauths = if let Some(db) = conn.as_sql() { + let q_oauth = load_value_from_global_settings(db, OAUTH_SETTING).await?; - #[cfg(feature = "oauth2")] - let oauths = if let Some(q) = q_oauth { - if let Ok(v) = serde_json::from_value::< - Option>, - >(q.clone()) - { - v + if let Some(q) = q_oauth { + if let Ok(v) = serde_json::from_value::< + Option>, + >(q.clone()) + { + v + } else { + tracing::error!("Could not parse oauth setting as a json, found: {:#?}", &q); + None + } } else { - tracing::error!("Could not parse oauth setting as a json, found: {:#?}", &q); None } } else { None }; - - let base_url = load_base_url(db).await?; + let base_url = load_base_url(conn).await?; let is_secure = base_url.starts_with("https://"); #[cfg(feature = "oauth2")] { - let mut l = windmill_api::OAUTH_CLIENTS.write().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(); + 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 + .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(); + } } { @@ -1590,7 +1696,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker increment_counter AS ( INSERT INTO zombie_job_counter (job_id, counter) SELECT id, 1 FROM to_update WHERE counter < $2 - ON CONFLICT (job_id) DO UPDATE + ON CONFLICT (job_id) DO UPDATE SET counter = zombie_job_counter.counter + 1 ), update_concurrency AS ( @@ -1682,7 +1788,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker let same_worker_timeout_jobs = { let long_same_worker_jobs = sqlx::query!( - "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval + "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", ) .fetch_all(db) @@ -1696,9 +1802,9 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker .collect::>(); let long_dead_workers: std::collections::HashSet = sqlx::query_scalar!( - "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) - SELECT worker_ids.worker FROM worker_ids - LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker + "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) + SELECT worker_ids.worker FROM worker_ids + LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", &worker_ids[..] ) @@ -1742,7 +1848,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker let non_restartable_jobs = if *RESTART_ZOMBIE_JOBS { vec![] } else { - sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval + sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false") .bind(ZOMBIE_JOB_TIMEOUT.as_str()) .fetch_all(db) @@ -1801,7 +1907,8 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker mpsc::channel::(1); let same_worker_tx_never_used = SameWorkerSender(same_worker_tx_never_used, Arc::new(AtomicU16::new(0))); - let (send_result_never_used, _send_result_rx_never_used) = mpsc::channel::(1); + let (send_result_never_used, _send_result_rx_never_used) = + JobCompletedSender::new_never_used(); let label = if job.permissioned_as != format!("u/{}", job.created_by) && job.permissioned_as != job.created_by @@ -1818,16 +1925,17 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker *SCRIPT_TOKEN_EXPIRY, &job.email, &job.id, + None, ) .await .expect("could not create job token"); - let client = AuthedClient { - base_internal_url: base_internal_url.to_string(), + let client = AuthedClient::new( + base_internal_url.to_string(), + job.workspace_id.to_string(), token, - workspace: job.workspace_id.to_string(), - force_client: None, - }; + None, + ); let last_ping = job.last_ping.clone(); let error_message = format!( @@ -1841,17 +1949,17 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker let _ = handle_job_error( db, &client, - &job, + &MiniPulledJob::from(&job), 0, None, error::Error::ExecutionErr(error_message), true, - same_worker_tx_never_used, + Some(&same_worker_tx_never_used), "", worker_name, send_result_never_used, #[cfg(feature = "benchmark")] - &mut windmill_worker::bench::BenchmarkIter::new(), + &mut windmill_common::bench::BenchmarkIter::new(), ) .await; } @@ -1901,13 +2009,17 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> { .await?; if let Some(key) = concurrency_key { - sqlx::query!( - "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1", - key, - flow.id.hyphenated().to_string() - ) - .execute(&mut *tx) - .await?; + if *DISABLE_CONCURRENCY_LIMIT { + tracing::warn!("Concurrency limit is disabled, skipping"); + } else { + sqlx::query!( + "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1", + key, + flow.id.hyphenated().to_string() + ) + .execute(&mut *tx) + .await?; + } } sqlx::query!( @@ -1934,7 +2046,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> { } ); report_critical_error(reason.clone(), db.clone(), Some(&flow.workspace_id), None).await; - cancel_zombie_flow_job(db, flow.id, &flow.workspace_id, + cancel_zombie_flow_job(db, flow.id, &flow.workspace_id, format!(r#"{reason} This would happen if a worker was interrupted, killed or crashed while doing a state transition at the end of a job which is always an unexpected behavior that should never happen. Please check your worker logs for more details and feel free to report it to the Windmill team on our Discord or support@windmill.dev (response for non EE customers will be best effort) with as much context as possible, ideally: @@ -1951,7 +2063,7 @@ Please check your worker logs for more details and feel free to report it to the r#" DELETE FROM parallel_monitor_lock - WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval + WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL ) AS workspace_id @@ -2006,8 +2118,12 @@ async fn cancel_zombie_flow_job( Ok(()) } -pub async fn reload_hub_base_url_setting(db: &DB, server_mode: bool) -> error::Result<()> { - let hub_base_url = load_value_from_global_settings(db, HUB_BASE_URL_SETTING).await?; +pub async fn reload_hub_base_url_setting( + conn: &Connection, + server_mode: bool, +) -> error::Result<()> { + let hub_base_url = + load_value_from_global_settings_with_conn(conn, HUB_BASE_URL_SETTING, true).await?; let base_url = if let Some(q) = hub_base_url { if let Ok(v) = serde_json::from_value::(q.clone()) { @@ -2030,16 +2146,18 @@ pub async fn reload_hub_base_url_setting(db: &DB, server_mode: bool) -> error::R let mut l = HUB_BASE_URL.write().await; if server_mode { #[cfg(feature = "embedding")] - if *l != base_url { - let disable_embedding = std::env::var("DISABLE_EMBEDDING") - .ok() - .map(|x| x.parse::().unwrap_or(false)) - .unwrap_or(false); - if !disable_embedding { - let db_clone = db.clone(); - tokio::spawn(async move { - update_embeddings_db(&db_clone).await; - }); + if let Some(db) = conn.as_sql() { + if *l != base_url { + let disable_embedding = std::env::var("DISABLE_EMBEDDING") + .ok() + .map(|x| x.parse::().unwrap_or(false)) + .unwrap_or(false); + if !disable_embedding { + let db_clone = db.clone(); + tokio::spawn(async move { + update_embeddings_db(&db_clone).await; + }); + } } } } @@ -2048,16 +2166,16 @@ pub async fn reload_hub_base_url_setting(db: &DB, server_mode: bool) -> error::R Ok(()) } -pub async fn reload_critical_error_channels_setting(db: &DB) -> error::Result<()> { +pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result<()> { let critical_error_channels = - load_value_from_global_settings(db, CRITICAL_ERROR_CHANNELS_SETTING).await?; + load_value_from_global_settings(conn, CRITICAL_ERROR_CHANNELS_SETTING).await?; let critical_error_channels = if let Some(q) = critical_error_channels { if let Ok(v) = serde_json::from_value::>(q.clone()) { v } else { tracing::error!( - "Could not parse critical_error_emails setting as an array of channels, found: {:#?}", + "Could not parse critical_error_channels setting as an array of channels, found: {:#?}", &q ); vec![] @@ -2072,6 +2190,39 @@ pub async fn reload_critical_error_channels_setting(db: &DB) -> error::Result<() Ok(()) } +pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<()> { + #[derive(Deserialize)] + struct DBOversize { + #[serde(default)] + enabled: bool, + #[serde(default)] + value: f32, + } + let db_oversize_value = + load_value_from_global_settings(conn, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING).await?; + + let db_oversize = if let Some(q) = db_oversize_value { + match serde_json::from_value::(q.clone()) { + Ok(DBOversize { enabled: true, value }) => Some(value), + Ok(_) => None, + Err(q) => { + tracing::error!( + "Could not parse critical_alerts_on_db_oversize setting, found: {:#?}", + &q + ); + None + } + } + } else { + None + }; + + let mut l = CRITICAL_ALERTS_ON_DB_OVERSIZE.write().await; + *l = db_oversize; + + Ok(()) +} + async fn generate_and_save_jwt_secret(db: &DB) -> error::Result { let secret = rd_string(32); sqlx::query!( diff --git a/backend/substitute_ee_code.sh b/backend/substitute_ee_code.sh index 28b890990b..a1819c75b2 100755 --- a/backend/substitute_ee_code.sh +++ b/backend/substitute_ee_code.sh @@ -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,13 +16,7 @@ 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" - 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" + MOVE_NEW_FILES="YES" shift ;; -c|--copy) @@ -33,6 +27,11 @@ 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. @@ -70,29 +69,34 @@ 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}" - 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 + rm ${ce_file} || true done -else +elif [ "$MOVE_NEW_FILES" == "NO" ]; then # 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 [[ -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 + 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}'" else - echo "File ${ce_file} is not a file, ignoring" + ln -s "${ee_file}" "${ce_file}" || true + echo "Symlink created '${ee_file}' -->> '${ce_file}'" 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 \ No newline at end of file diff --git a/backend/summarize_schema.py b/backend/summarize_schema.py new file mode 100644 index 0000000000..a6d929ba9a --- /dev/null +++ b/backend/summarize_schema.py @@ -0,0 +1,154 @@ +# This script is used to summarize the database schema. +# You can use pg_dump to dump the schema to a file. +# pg_dump --file "schema.sql" --host "localhost" --port "5432" --username "postgres" --no-password --format=c --large-objects --schema-only --no-owner --no-privileges --no-tablespaces --no-unlogged-table-data --no-comments --no-publications --no-subscriptions --no-security-labels --no-toast-compression --no-table-access-method --verbose --schema "public" "windmill" +# Then you can run python summarize_schema.py schema.sql to get the summarized schema. + +import re +import sys +from collections import defaultdict + +def summarize_schema(file_path): + """ + Parses a PostgreSQL dump file and extracts a summarized schema. + """ + tables = defaultdict(lambda: {'columns': [], 'pks': set(), 'fks': [], 'indexes': []}) + enums = defaultdict(list) + + # Use state variables to parse multi-line definitions + current_table = None + current_enum = None + + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + + # --- State Resets --- + if line.startswith(');'): + current_table = None + current_enum = None + continue + + # --- Parse ENUM definitions --- + match_enum = re.match(r"CREATE TYPE public\.(\w+) AS ENUM \($", line) + if match_enum: + current_enum = match_enum.group(1) + continue + + if current_enum: + # Extract enum values, which are typically like 'value', + value = line.strip("',") + if value and not value.startswith('--'): + enums[current_enum].append(value) + continue + + # --- Parse TABLE definitions --- + match_table = re.match(r"CREATE TABLE public\.(\w+) \($", line) + if match_table: + current_table = match_table.group(1) + continue + + if current_table: + # Parse columns within a CREATE TABLE block + # e.g., "column_name type NOT NULL," + # e.g., "id bigint NOT NULL," + match_column = re.match(r'^"?(\w+)"?\s+([\w\d\.\[\]\(\)]+)', line) + if match_column: + col_name = match_column.group(1) + col_type = match_column.group(2) + tables[current_table]['columns'].append(f"{col_name} ({col_type})") + + # Parse PRIMARY KEY defined inside the table + match_pk = re.search(r"CONSTRAINT \w+ PRIMARY KEY \((.+)\)", line) + if match_pk: + # Handle multiple PK columns: "col1, col2, col3" + pk_cols = [p.strip().strip('"') for p in match_pk.group(1).split(',')] + tables[current_table]['pks'].update(pk_cols) + continue + + # --- Parse Foreign Keys (defined outside CREATE TABLE) --- + match_fk = re.match(r"ALTER TABLE ONLY public\.(\w+)\s+ADD CONSTRAINT \w+ FOREIGN KEY \(([\w,\s\"]+)\) REFERENCES public\.(\w+)\(([\w,\s\"]+)\);", line) + if match_fk: + from_table, from_cols, to_table, to_cols = match_fk.groups() + # Clean up column names + from_cols_clean = ', '.join([c.strip().strip('"') for c in from_cols.split(',')]) + to_cols_clean = ', '.join([c.strip().strip('"') for c in to_cols.split(',')]) + + fk_string = f"({from_cols_clean}) -> {to_table}({to_cols_clean})" + tables[from_table]['fks'].append(fk_string) + + # --- Parse Index definitions --- + match_index = re.match(r"CREATE (UNIQUE )?INDEX (\w+) ON public\.(\w+) USING (\w+) \((.+)\);", line) + if match_index: + is_unique = match_index.group(1) is not None + index_name = match_index.group(2) + table_name = match_index.group(3) + index_type = match_index.group(4) + columns = match_index.group(5) + + # Clean up column expressions + columns_clean = columns.replace('"', '') + + unique_str = "UNIQUE " if is_unique else "" + index_string = f"{unique_str}INDEX {index_name} ({index_type}) ON ({columns_clean})" + tables[table_name]['indexes'].append(index_string) + + return enums, tables + +def format_output(enums, tables): + """ + Formats the parsed schema data into a clean, readable string. + """ + output = [] + + output.append("### Simplified Database Schema ###") + output.append("\n--- Custom Data Types (ENUMs) ---\n") + if not enums: + output.append("No custom ENUM types found.") + else: + for name, values in sorted(enums.items()): + output.append(f"{name}:") + for v in values: + output.append(f" - {v}") + output.append("") + + output.append("\n--- Tables and Relationships ---\n") + if not tables: + output.append("No tables found.") + else: + for name, data in sorted(tables.items()): + output.append(f"TABLE: {name}") + for col in data['columns']: + col_name = col.split(' ')[0] + marker = " (PK)" if col_name in data['pks'] else "" + output.append(f" - {col}{marker}") + + if data['fks']: + output.append(" Relationships:") + for fk in data['fks']: + output.append(f" - {fk}") + + if data['indexes']: + output.append(" Indexes:") + for idx in data['indexes']: + output.append(f" - {idx}") + output.append("-" * 20) + + return "\n".join(output) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} ") + sys.exit(1) + + input_file = sys.argv[1] + + try: + enums_data, tables_data = summarize_schema(input_file) + formatted_summary = format_output(enums_data, tables_data) + print(formatted_summary) + except FileNotFoundError: + print(f"Error: The file '{input_file}' was not found.") + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}") + sys.exit(1) \ No newline at end of file diff --git a/backend/summarized_schema.txt b/backend/summarized_schema.txt new file mode 100644 index 0000000000..a1ae0c1847 --- /dev/null +++ b/backend/summarized_schema.txt @@ -0,0 +1,1085 @@ +### Simplified Database Schema ### + +--- Custom Data Types (ENUMs) --- + +action_kind: + - create + - update + - delete + - execute + +authentication_method: + - none + - windmill + - api_key + - basic_http + - custom_script + - signature + +autoscaling_event_type: + - full_scaleout + - scalein + - scaleout + +aws_auth_resource_type: + - oidc + - credentials + +delivery_mode: + - push + - pull + +draft_type: + - script + - flow + - app + +favorite_kind: + - app + - script + - flow + - raw_app + +gcp_subscription_mode: + - create_update + - existing + +http_method: + - get + - post + - put + - delete + - patch + +importer_kind: + - script + - flow + - app + +job_kind: + - script + - preview + - flow + - dependencies + - flowpreview + - script_hub + - identity + - flowdependencies + - http + - graphql + - postgresql + - noop + - appdependencies + - deploymentcallback + - singlescriptflow + - flowscript + - flownode + - appscript + +job_status: + - success + - failure + - canceled + - skipped + +job_trigger_kind: + - webhook + - http + - websocket + - kafka + - email + - nats + - schedule + - app + - ui + - postgres + - sqs + - gcp + +log_mode: + - standalone + - server + - worker + - agent + - indexer + - mcp + +login_type: + - password + - github + +metric_kind: + - scalar_int + - scalar_float + - timeseries_int + - timeseries_float + +mqtt_client_version: + - v3 + - v5 + +runnable_type: + - ScriptHash + - ScriptPath + - FlowPath + +script_kind: + - script + - trigger + - failure + - command + - approval + - preprocessor + +script_lang: + - python3 + - deno + - go + - bash + - postgresql + - nativets + - bun + - mysql + - bigquery + - snowflake + - graphql + - powershell + - mssql + - php + - bunnative + - rust + - ansible + - csharp + - oracledb + - nu + - java + - duckdb + +trigger_kind: + - webhook + - http + - websocket + - kafka + - email + - nats + - postgres + - sqs + - mqtt + - gcp + +workspace_key_kind: + - cloud + + +--- Tables and Relationships --- + +TABLE: _sqlx_migrations + - version (bigint) + - description (text) + - installed_on (timestamp) + - success (boolean) + - checksum (bytea) + - execution_time (bigint) +-------------------- +TABLE: account + - workspace_id (character) + - id (integer) + - expires_at (timestamp) + - refresh_token (character) + - client (character) + - refresh_error (text) +-------------------- +TABLE: alerts + - id (integer) + - alert_type (character) + - message (text) + - created_at (timestamp) + - acknowledged (boolean) + - workspace_id (text) + - acknowledged_workspace (boolean) + - resource (text) + Indexes: + - INDEX alerts_by_workspace (btree) ON (workspace_id) +-------------------- +TABLE: app + - id (bigint) + - workspace_id (character) + - path (character) + - summary (character) + - policy (jsonb) + - versions (bigint[]) + - extra_perms (jsonb) + - draft_only (boolean) + - custom_path (text) + - CONSTRAINT (app_custom_path_check) +-------------------- +TABLE: app_script + - id (bigint) + - app (bigint) + - hash (character(64)) + - lock (text) + - code (text) + - code_sha256 (character(64)) +-------------------- +TABLE: app_version + - id (bigint) + - app_id (bigint) + - value (json) + - created_by (character) + - created_at (timestamp) + - raw_app (boolean) +-------------------- +TABLE: app_version_lite + - id (bigint) + - value (jsonb) +-------------------- +TABLE: audit + - workspace_id (character) + - id (integer) + - timestamp (timestamp) + - username (character) + - operation (character) + - action_kind (public.action_kind) + - resource (character) + - parameters (jsonb) + Indexes: + - INDEX ix_audit_timestamps (btree) ON (timestamp DESC) +-------------------- +TABLE: autoscaling_event + - id (integer) + - worker_group (text) + - event_type (public.autoscaling_event_type) + - desired_workers (integer) + - applied_at (timestamp) + - reason (text) + Indexes: + - INDEX autoscaling_event_worker_group_idx (btree) ON (worker_group, applied_at) +-------------------- +TABLE: capture + - workspace_id (character) + - path (character) + - created_at (timestamp) + - created_by (character) + - main_args (jsonb) + - is_flow (boolean) + - trigger_kind (public.trigger_kind) + - preprocessor_args (jsonb) + - id (bigint) + - CONSTRAINT (capture_payload_too_big) +-------------------- +TABLE: capture_config + - workspace_id (character) + - path (character) + - is_flow (boolean) + - trigger_kind (public.trigger_kind) + - trigger_config (jsonb) + - owner (character) + - email (character) + - server_id (character) + - last_client_ping (timestamp) + - last_server_ping (timestamp) + - error (text) +-------------------- +TABLE: cloud_workspace_settings + - workspace_id (character) + - threshold_alert_amount (integer) + - last_alert_sent (timestamp) + - last_warning_sent (timestamp) +-------------------- +TABLE: concurrency_counter + - concurrency_id (character) + - job_uuids (jsonb) +-------------------- +TABLE: concurrency_key + - key (character) + - ended_at (timestamp) + - job_id (uuid) + Indexes: + - INDEX concurrency_key_ended_at_idx (btree) ON (key, ended_at DESC) +-------------------- +TABLE: concurrency_locks + - id (character) + - last_locked_at (timestamp) + - owner (character) +-------------------- +TABLE: config + - name (character) + - config (jsonb) +-------------------- +TABLE: custom_concurrency_key_ended + - key (character) + - ended_at (timestamp) +-------------------- +TABLE: dependency_map + - workspace_id (character) + - importer_path (character) + - importer_kind (public.importer_kind) + - imported_path (character) + - importer_node_id (character) + Indexes: + - INDEX dependency_map_imported_path_idx (btree) ON (workspace_id, imported_path) +-------------------- +TABLE: deployment_metadata + - workspace_id (character) + - path (character) + - script_hash (bigint) + - app_version (bigint) + - callback_job_ids (uuid[]) + - deployment_msg (text) + - flow_version (bigint) + Indexes: + - UNIQUE INDEX deployment_metadata_app (btree) ON (workspace_id, path, app_version) WHERE (app_version IS NOT NULL) + - UNIQUE INDEX deployment_metadata_flow (btree) ON (workspace_id, path, flow_version) WHERE (flow_version IS NOT NULL) + - UNIQUE INDEX deployment_metadata_script (btree) ON (workspace_id, script_hash) WHERE (script_hash IS NOT NULL) +-------------------- +TABLE: draft + - workspace_id (character) + - path (character) + - typ (public.draft_type) + - value (json) + - created_at (timestamp) +-------------------- +TABLE: email_to_igroup + - email (character) + - igroup (character) +-------------------- +TABLE: favorite + - usr (character) + - workspace_id (character) + - path (character) + - favorite_kind (public.favorite_kind) +-------------------- +TABLE: flow + - workspace_id (character) + - path (character) + - summary (text) + - description (text) + - value (jsonb) + - edited_by (character) + - edited_at (timestamp) + - archived (boolean) + - schema (json) + - extra_perms (jsonb) + - dependency_job (uuid) + - draft_only (boolean) + - tag (character) + - ws_error_handler_muted (boolean) + - dedicated_worker (boolean) + - timeout (integer) + - visible_to_runner_only (boolean) + - concurrency_key (character) + - versions (bigint[]) + - on_behalf_of_email (text) + - lock_error_logs (text) + - CONSTRAINT (proper_id) + Indexes: + - INDEX flow_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: flow_node + - id (bigint) + - workspace_id (character) + - hash (bigint) + - path (character) + - lock (text) + - code (text) + - flow (jsonb) + - hash_v2 (character(64)) + Indexes: + - INDEX flow_node_hash (btree) ON (hash) +-------------------- +TABLE: flow_version + - id (bigint) + - workspace_id (character) + - path (character) + - value (jsonb) + - schema (json) + - created_by (character) + - created_at (timestamp) + Indexes: + - INDEX index_flow_version_path_created_at (btree) ON (path, created_at) +-------------------- +TABLE: flow_version_lite + - id (bigint) + - value (jsonb) +-------------------- +TABLE: folder + - name (character) + - workspace_id (character) + - display_name (character) + - owners (character) + - extra_perms (jsonb) + - summary (text) + - edited_at (timestamp) + - created_by (character) + Indexes: + - INDEX folder_extra_perms (gin) ON (extra_perms) + - INDEX folder_owners (gin) ON (owners) +-------------------- +TABLE: gcp_trigger + - gcp_resource_path (character) + - topic_id (character) + - subscription_id (character) + - delivery_type (public.delivery_mode) + - delivery_config (jsonb) + - path (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - server_id (character) + - last_server_ping (timestamp) + - error (text) + - enabled (boolean) + - subscription_mode (public.gcp_subscription_mode) + - CONSTRAINT (gcp_trigger_check) + - CONSTRAINT (gcp_trigger_subscription_id_check) + - CONSTRAINT (gcp_trigger_topic_id_check) + Indexes: + - UNIQUE INDEX unique_subscription_per_gcp_resource (btree) ON (subscription_id, gcp_resource_path, workspace_id) +-------------------- +TABLE: global_settings + - name (character) + - value (jsonb) + - updated_at (timestamp) +-------------------- +TABLE: group_ + - workspace_id (character) + - name (character) + - summary (text) + - extra_perms (jsonb) + - CONSTRAINT (proper_name) + Indexes: + - INDEX group_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: healthchecks + - id (bigint) + - check_type (character) + - healthy (boolean) + - created_at (timestamp) + Indexes: + - INDEX healthchecks_check_type_created_at (btree) ON (check_type, created_at) +-------------------- +TABLE: http_trigger + - path (character) + - route_path (character) + - route_path_key (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - is_async (boolean) + - authentication_method (public.authentication_method) + - http_method (public.http_method) + - static_asset_config (jsonb) + - is_static_website (boolean) + - workspaced_route (boolean) + - wrap_body (boolean) + - raw_string (boolean) + - authentication_resource_path (character) +-------------------- +TABLE: input + - id (uuid) + - workspace_id (character) + - runnable_id (character) + - runnable_type (public.runnable_type) + - name (text) + - args (jsonb) + - created_at (timestamp) + - created_by (character) + - is_public (boolean) +-------------------- +TABLE: instance_group + - name (character) + - summary (character) + - id (character) + - scim_display_name (character) + - external_id (character) +-------------------- +TABLE: job_logs + - job_id (uuid) + - workspace_id (character) + - created_at (timestamp) + - logs (text) + - log_offset (integer) + - log_file_index (text[]) +-------------------- +TABLE: job_perms + - job_id (uuid) + - email (character) + - username (character) + - is_admin (boolean) + - is_operator (boolean) + - created_at (timestamp) + - workspace_id (character) + - groups (text[]) + - folders (jsonb[]) +-------------------- +TABLE: job_stats + - workspace_id (character) + - job_id (uuid) + - metric_id (character) + - metric_name (character) + - metric_kind (public.metric_kind) + - scalar_int (integer) + - scalar_float (real) + - timestamps (timestamp) + - timeseries_int (integer[]) + - timeseries_float (real[]) + Indexes: + - INDEX job_stats_id (btree) ON (job_id) +-------------------- +TABLE: kafka_trigger + - path (character) + - kafka_resource_path (character) + - topics (character) + - group_id (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - server_id (character) + - last_server_ping (timestamp) + - error (text) + - enabled (boolean) +-------------------- +TABLE: log_file + - hostname (character) + - log_ts (timestamp) + - ok_lines (bigint) + - err_lines (bigint) + - mode (public.log_mode) + - worker_group (character) + - file_path (character) + - json_fmt (boolean) + Indexes: + - INDEX log_file_log_ts_idx (btree) ON (log_ts) +-------------------- +TABLE: magic_link + - email (character) + - token (character) + - expiration (timestamp) + Indexes: + - INDEX index_magic_link_exp (btree) ON (expiration) +-------------------- +TABLE: metrics + - id (character) + - value (jsonb) + - created_at (timestamp) + Indexes: + - INDEX idx_metrics_id_created_at (btree) ON (id, created_at DESC) WHERE ((id)::text ~~ 'queue_%'::text) + - INDEX metrics_key_idx (btree) ON (id) + - INDEX metrics_sort_idx (btree) ON (created_at DESC) +-------------------- +TABLE: mqtt_trigger + - mqtt_resource_path (character) + - subscribe_topics (jsonb[]) + - client_version (public.mqtt_client_version) + - v5_config (jsonb) + - v3_config (jsonb) + - client_id (character) + - path (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - server_id (character) + - last_server_ping (timestamp) + - error (text) + - enabled (boolean) +-------------------- +TABLE: nats_trigger + - path (character) + - nats_resource_path (character) + - subjects (character) + - stream_name (character) + - consumer_name (character) + - use_jetstream (boolean) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - server_id (character) + - last_server_ping (timestamp) + - error (text) + - enabled (boolean) +-------------------- +TABLE: outstanding_wait_time + - job_id (uuid) + - self_wait_time_ms (bigint) + - aggregate_wait_time_ms (bigint) +-------------------- +TABLE: parallel_monitor_lock + - parent_flow_id (uuid) + - job_id (uuid) + - last_ping (timestamp) +-------------------- +TABLE: password + - email (character) + - password_hash (character) + - login_type (character) + - super_admin (boolean) + - verified (boolean) + - name (character) + - company (character) + - first_time_user (boolean) + - username (character) + - devops (boolean) +-------------------- +TABLE: pending_user + - email (character) + - created_at (timestamp) + - username (character) +-------------------- +TABLE: pip_resolution_cache + - hash (character) + - expiration (timestamp) + - lockfile (text) +-------------------- +TABLE: postgres_trigger + - path (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - postgres_resource_path (character) + - error (text) + - server_id (character) + - last_server_ping (timestamp) + - replication_slot_name (character) + - publication_name (character) + - enabled (boolean) +-------------------- +TABLE: raw_app + - path (character) + - version (integer) + - workspace_id (character) + - summary (character) + - edited_at (timestamp) + - data (text) + - extra_perms (jsonb) +-------------------- +TABLE: resource + - workspace_id (character) + - path (character) + - value (jsonb) + - description (text) + - resource_type (character) + - extra_perms (jsonb) + - edited_at (timestamp) + - created_by (character) + - CONSTRAINT (proper_id) + Indexes: + - INDEX resource_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: resource_type + - workspace_id (character) + - name (character) + - schema (jsonb) + - description (text) + - edited_at (timestamp) + - created_by (character) + - format_extension (character) + - CONSTRAINT (proper_name) +-------------------- +TABLE: resume_job + - id (uuid) + - job (uuid) + - flow (uuid) + - created_at (timestamp) + - value (jsonb) + - approver (character) + - resume_id (integer) + - approved (boolean) +-------------------- +TABLE: schedule + - workspace_id (character) + - path (character) + - edited_by (character) + - edited_at (timestamp) + - schedule (character) + - enabled (boolean) + - script_path (character) + - args (jsonb) + - extra_perms (jsonb) + - is_flow (boolean) + - email (character) + - error (text) + - timezone (character) + - on_failure (character) + - on_recovery (character) + - on_failure_times (integer) + - on_failure_exact (boolean) + - on_failure_extra_args (jsonb) + - on_recovery_times (integer) + - on_recovery_extra_args (jsonb) + - ws_error_handler_muted (boolean) + - retry (jsonb) + - summary (character) + - no_flow_overlap (boolean) + - tag (character) + - paused_until (timestamp) + - on_success (character) + - on_success_extra_args (jsonb) + - cron_version (text) + - description (text) + - CONSTRAINT (proper_id) + Indexes: + - INDEX schedule_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: script + - workspace_id (character) + - hash (bigint) + - path (character) + - parent_hashes (bigint[]) + - summary (text) + - description (text) + - content (text) + - created_by (character) + - created_at (timestamp) + - archived (boolean) + - schema (json) + - deleted (boolean) + - is_template (boolean) + - extra_perms (jsonb) + - lock (text) + - lock_error_logs (text) + - language (public.script_lang) + - kind (public.script_kind) + - tag (character) + - draft_only (boolean) + - envs (character) + - concurrent_limit (integer) + - concurrency_time_window_s (integer) + - cache_ttl (integer) + - dedicated_worker (boolean) + - ws_error_handler_muted (boolean) + - priority (smallint) + - timeout (integer) + - delete_after_use (boolean) + - restart_unless_cancelled (boolean) + - concurrency_key (character) + - visible_to_runner_only (boolean) + - no_main_func (boolean) + - codebase (character) + - has_preprocessor (boolean) + - on_behalf_of_email (text) + - schema_validation (boolean) + - CONSTRAINT (proper_id) + Indexes: + - INDEX index_script_on_path_created_at (btree) ON (workspace_id, path, created_at DESC) + - INDEX script_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: sqs_trigger + - path (character) + - queue_url (character) + - aws_resource_path (character) + - message_attributes (text[]) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - error (text) + - server_id (character) + - last_server_ping (timestamp) + - enabled (boolean) + - aws_auth_resource_type (public.aws_auth_resource_type) +-------------------- +TABLE: token + - token (character) + - label (character) + - expiration (timestamp) + - workspace_id (character) + - owner (character) + - email (character) + - super_admin (boolean) + - created_at (timestamp) + - last_used_at (timestamp) + - scopes (text[]) + - job (uuid) + Indexes: + - INDEX index_token_exp (btree) ON (expiration) +-------------------- +TABLE: tutorial_progress + - email (character) + - progress (bit(64)) +-------------------- +TABLE: usage + - id (character) + - is_workspace (boolean) + - month_ (integer) + - usage (integer) +-------------------- +TABLE: usr + - workspace_id (character) + - username (character) + - email (character) + - is_admin (boolean) + - created_at (timestamp) + - operator (boolean) + - disabled (boolean) + - role (character) + - CONSTRAINT (proper_email) + - CONSTRAINT (proper_username) + Indexes: + - INDEX index_usr_email (btree) ON (email) +-------------------- +TABLE: usr_to_group + - workspace_id (character) + - group_ (character) + - usr (character) +-------------------- +TABLE: v2_job + - id (uuid) + - raw_code (text) + - raw_lock (text) + - raw_flow (jsonb) + - tag (character) + - workspace_id (character) + - created_at (timestamp) + - created_by (character) + - permissioned_as (character) + - permissioned_as_email (character) + - kind (public.job_kind) + - runnable_id (bigint) + - runnable_path (character) + - parent_job (uuid) + - root_job (uuid) + - script_lang (public.script_lang) + - script_entrypoint_override (character) + - flow_step (integer) + - flow_step_id (character) + - flow_innermost_root_job (uuid) + - trigger (character) + - trigger_kind (public.job_trigger_kind) + - same_worker (boolean) + - visible_to_owner (boolean) + - concurrent_limit (integer) + - concurrency_time_window_s (integer) + - cache_ttl (integer) + - timeout (integer) + - priority (smallint) + - preprocessed (boolean) + - args (jsonb) + - labels (text[]) + - pre_run_error (text) + Indexes: + - INDEX ix_job_created_at (btree) ON (created_at DESC) + - INDEX ix_job_root_job_index_by_path_2 (btree) ON (workspace_id, runnable_path, created_at DESC) WHERE (parent_job IS NULL) + - INDEX ix_job_workspace_id_created_at_new_3 (btree) ON (workspace_id, created_at DESC) + - INDEX ix_job_workspace_id_created_at_new_5 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['preview'::public.job_kind, 'flowpreview'::public.job_kind])) AND (parent_job IS NULL)) + - INDEX ix_job_workspace_id_created_at_new_8 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = 'deploymentcallback'::public.job_kind) AND (parent_job IS NULL)) + - INDEX ix_job_workspace_id_created_at_new_9 (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['dependencies'::public.job_kind, 'flowdependencies'::public.job_kind, 'appdependencies'::public.job_kind])) AND (parent_job IS NULL)) + - INDEX ix_v2_job_labels (gin) ON (labels) WHERE (labels IS NOT NULL) + - INDEX ix_v2_job_workspace_id_created_at (btree) ON (workspace_id, created_at DESC) WHERE ((kind = ANY (ARRAY['script'::public.job_kind, 'flow'::public.job_kind, 'singlescriptflow'::public.job_kind])) AND (parent_job IS NULL)) +-------------------- +TABLE: v2_job_completed + - id (uuid) + - workspace_id (character) + - duration_ms (bigint) + - result (jsonb) + - deleted (boolean) + - canceled_by (character) + - canceled_reason (text) + - flow_status (jsonb) + - started_at (timestamp) + - memory_peak (integer) + - status (public.job_status) + - completed_at (timestamp) + - worker (character) + - workflow_as_code_status (jsonb) + - result_columns (text[]) + - retries (uuid[]) + - extras (jsonb) + Indexes: + - INDEX ix_completed_job_workspace_id_started_at_new_2 (btree) ON (workspace_id, started_at DESC) + - INDEX ix_job_completed_completed_at (btree) ON (completed_at DESC) + - INDEX labeled_jobs_on_jobs (gin) ON (((result -> 'wm_labels'::text))) WHERE (result ? 'wm_labels'::text) +-------------------- +TABLE: v2_job_queue + - id (uuid) + - workspace_id (character) + - created_at (timestamp) + - started_at (timestamp) + - scheduled_for (timestamp) + - running (boolean) + - canceled_by (character) + - canceled_reason (text) + - suspend (integer) + - suspend_until (timestamp) + - tag (character) + - priority (smallint) + - worker (character) + - extras (jsonb) + Indexes: + - INDEX queue_sort_v2 (btree) ON (priority DESC NULLS LAST, scheduled_for, tag) WHERE (running = false) + - INDEX queue_suspended (btree) ON (priority DESC NULLS LAST, created_at, suspend_until, suspend, tag) WHERE (suspend_until IS NOT NULL) + - INDEX root_queue_index_by_path (btree) ON (workspace_id, created_at) + - INDEX v2_job_queue_suspend (btree) ON (workspace_id, suspend) WHERE (suspend > 0) +-------------------- +TABLE: v2_job_runtime + - id (uuid) + - ping (timestamp) + - memory_peak (integer) +-------------------- +TABLE: v2_job_status + - id (uuid) + - flow_status (jsonb) + - flow_leaf_jobs (jsonb) + - workflow_as_code_status (jsonb) +-------------------- +TABLE: variable + - workspace_id (character) + - path (character) + - value (character) + - is_secret (boolean) + - description (character) + - extra_perms (jsonb) + - account (integer) + - is_oauth (boolean) + - expires_at (timestamp) + - CONSTRAINT (proper_id) + Indexes: + - INDEX variable_extra_perms (gin) ON (extra_perms) +-------------------- +TABLE: websocket_trigger + - path (character) + - url (character) + - script_path (character) + - is_flow (boolean) + - workspace_id (character) + - edited_by (character) + - email (character) + - edited_at (timestamp) + - extra_perms (jsonb) + - server_id (character) + - last_server_ping (timestamp) + - error (text) + - enabled (boolean) + - filters (jsonb[]) + - initial_messages (jsonb[]) + - url_runnable_args (jsonb) + - can_return_message (boolean) +-------------------- +TABLE: windmill_migrations + - name (text) + - created_at (timestamp) +-------------------- +TABLE: worker_ping + - worker (character) + - worker_instance (character) + - ping_at (timestamp) + - started_at (timestamp) + - ip (character) + - jobs_executed (integer) + - custom_tags (text[]) + - worker_group (character) + - dedicated_worker (character) + - wm_version (character) + - current_job_id (uuid) + - current_job_workspace_id (character) + - vcpus (bigint) + - memory (bigint) + - occupancy_rate (real) + - memory_usage (bigint) + - wm_memory_usage (bigint) + - occupancy_rate_15s (real) + - occupancy_rate_5m (real) + - occupancy_rate_30m (real) + Indexes: + - INDEX worker_ping_on_ping_at (btree) ON (ping_at) +-------------------- +TABLE: workspace + - id (character) + - name (character) + - owner (character) + - deleted (boolean) + - premium (boolean) + - CONSTRAINT (proper_id) +-------------------- +TABLE: workspace_env + - workspace_id (character) + - name (character) + - value (character) +-------------------- +TABLE: workspace_invite + - workspace_id (character) + - email (character) + - is_admin (boolean) + - operator (boolean) + - CONSTRAINT (proper_email) +-------------------- +TABLE: workspace_key + - workspace_id (character) + - kind (public.workspace_key_kind) + - key (character) +-------------------- +TABLE: workspace_runnable_dependencies + - flow_path (character) + - runnable_path (character) + - script_hash (bigint) + - runnable_is_flow (boolean) + - workspace_id (character) + - app_path (character) + - CONSTRAINT (workspace_runnable_dependencies_path_exclusive) + Indexes: + - UNIQUE INDEX app_workspace_with_hash_unique_idx (btree) ON (app_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE (script_hash IS NOT NULL) + - UNIQUE INDEX app_workspace_without_hash_unique_idx (btree) ON (app_path, runnable_path, runnable_is_flow, workspace_id) WHERE (script_hash IS NULL) + - INDEX flow_workspace_runnable_path_is_flow_idx (btree) ON (runnable_path, runnable_is_flow, workspace_id) + - UNIQUE INDEX flow_workspace_with_hash_unique_idx (btree) ON (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE (script_hash IS NOT NULL) + - UNIQUE INDEX flow_workspace_without_hash_unique_idx (btree) ON (flow_path, runnable_path, runnable_is_flow, workspace_id) WHERE (script_hash IS NULL) +-------------------- +TABLE: workspace_settings + - workspace_id (character) + - slack_team_id (character) + - slack_name (character) + - slack_command_script (character) + - slack_email (character) + - auto_invite_domain (character) + - auto_invite_operator (boolean) + - customer_id (character) + - plan (character) + - webhook (text) + - deploy_to (character) + - error_handler (character) + - ai_config (jsonb) + - error_handler_extra_args (json) + - error_handler_muted_on_cancel (boolean) + - large_file_storage (jsonb) + - git_sync (jsonb) + - default_app (character) + - auto_add (boolean) + - default_scripts (jsonb) + - deploy_ui (jsonb) + - mute_critical_alerts (boolean) + - color (character) + - operator_settings (jsonb) + - teams_command_script (text) + - teams_team_id (text) + - teams_team_name (text) + - git_app_installations (jsonb) +-------------------- +TABLE: zombie_job_counter + - job_id (uuid) + - counter (integer) +-------------------- diff --git a/backend/tests/fixtures/lockfile_python.sql b/backend/tests/fixtures/lockfile_python.sql new file mode 100644 index 0000000000..27f7b103f0 --- /dev/null +++ b/backend/tests/fixtures/lockfile_python.sql @@ -0,0 +1,51 @@ +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +# requirements: +# microdot==2.2.0 + +import pandas +import requests +import tiny # pin: tiny==0.1.2 + +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/system/requirements', 12346, 'python3', ''); + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +# extra_requirements: +# bottle==0.13.2 + +import tiny + +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/system/extra_requirements', 12347, 'python3', ''); + + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +import tiny # pin: bottle==0.13.2 +import simplejson # pin: simplejson==3.19.3 + +def main(): + return [test1(), test2(), test3(), test4()] +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/system/pins', 12348, 'python3', ''); diff --git a/backend/tests/fixtures/multipython.sql b/backend/tests/fixtures/multipython.sql new file mode 100644 index 0000000000..fa7d9c8c8d --- /dev/null +++ b/backend/tests/fixtures/multipython.sql @@ -0,0 +1,20 @@ +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', ''); + diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 2f7abde33d..8792b2eb1d 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -1,7 +1,7 @@ use serde::de::DeserializeOwned; use std::future::Future; use std::{str::FromStr, sync::Arc}; -use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage}; +use windmill_common::KillpillSender; #[cfg(feature = "enterprise")] use chrono::Timelike; @@ -16,16 +16,20 @@ use tokio::sync::RwLock; #[cfg(feature = "enterprise")] use tokio::time::{timeout, Duration}; +#[cfg(feature = "python")] use windmill_api_client::types::{CreateFlowBody, RawScript}; - #[cfg(feature = "enterprise")] use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs}; +use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage}; use serde::Serialize; +#[cfg(feature = "deno_core")] +use windmill_common::flows::InputTransform; use windmill_common::worker::WORKER_CONFIG; + use windmill_common::{ flow_status::{FlowStatus, FlowStatusModule, RestartedFrom}, - flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform}, + flows::{FlowModule, FlowModuleValue, FlowValue}, jobs::{JobKind, JobPayload, RawCode}, jwt::JWT_SECRET, scripts::{ScriptHash, ScriptLang}, @@ -137,11 +141,11 @@ impl ApiServer { rx, port_tx, false, - #[cfg(feature = "smtp")] + false, format!("http://localhost:{}", addr.port()), )); - _port_rx.await.unwrap(); + _port_rx.await.expect("failed to receive port"); // clear the cache between tests windmill_common::cache::clear(); @@ -167,6 +171,7 @@ impl ApiServer { // Ok(()) // } +#[cfg(feature = "python")] fn get_module(cjob: &CompletedJob, id: &str) -> Option { cjob.flow_status.clone().and_then(|fs| { find_module_in_vec( @@ -176,6 +181,7 @@ fn get_module(cjob: &CompletedJob, id: &str) -> Option { }) } +#[cfg(feature = "python")] fn find_module_in_vec(modules: Vec, id: &str) -> Option { modules.into_iter().find(|s| s.id() == id) } @@ -284,6 +290,7 @@ mod suspend_resume { .unwrap() } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test(db: Pool) { initialize_tracing().await; @@ -315,7 +322,7 @@ mod suspend_resume { let second = completed.next().await.unwrap(); // print_job(second, &db).await; - let token = windmill_worker::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "", &Uuid::nil()).await.unwrap(); + let token = windmill_common::auth::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "", &Uuid::nil(), None).await.unwrap(); let secret = reqwest::get(format!( "http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}&approver=ruben" )) @@ -366,6 +373,7 @@ mod suspend_resume { ); } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn cancel_from_job(db: Pool) { initialize_tracing().await; @@ -391,6 +399,7 @@ mod suspend_resume { ); } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn cancel_after_suspend(db: Pool) { initialize_tracing().await; @@ -418,7 +427,7 @@ mod suspend_resume { /* ... and send a request resume it. */ let second = completed.next().await.unwrap(); - let token = windmill_worker::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "", &Uuid::nil()).await.unwrap(); + let token = windmill_common::auth::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "", &Uuid::nil(), None).await.unwrap(); let secret = reqwest::get(format!( "http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}" )) @@ -564,6 +573,7 @@ def main(last, port): .unwrap() } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_pass(db: Pool) { initialize_tracing().await; @@ -609,6 +619,7 @@ def main(last, port): assert_eq!(json!([3, 5, 7, 9]), result); } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_fail_step_zero(db: Pool) { initialize_tracing().await; @@ -652,6 +663,7 @@ def main(last, port): ); } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_fail_step_one(db: Pool) { initialize_tracing().await; @@ -693,6 +705,7 @@ def main(last, port): .contains("index out of range")); } + #[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_with_failure_module(db: Pool) { initialize_tracing().await; @@ -769,6 +782,7 @@ def main(error, port): } } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_iteration(db: Pool) { initialize_tracing().await; @@ -827,6 +841,7 @@ async fn test_iteration(db: Pool) { .contains("2")); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_iteration_parallel(db: Pool) { initialize_tracing().await; @@ -999,7 +1014,7 @@ async fn in_test_worker( }; /* ensure the worker quits before we return */ - quit.send(()).expect("send"); + quit.send(); let _: () = worker .await @@ -1011,16 +1026,13 @@ async fn in_test_worker( fn spawn_test_worker( db: &Pool, port: u16, -) -> ( - tokio::sync::broadcast::Sender<()>, - tokio::task::JoinHandle<()>, -) { +) -> (KillpillSender, tokio::task::JoinHandle<()>) { std::fs::DirBuilder::new() .recursive(true) .create(windmill_worker::GO_BIN_CACHE_DIR) .expect("could not create initial worker dir"); - let (tx, rx) = tokio::sync::broadcast::channel(1); + let (tx, rx) = KillpillSender::new(1); let db = db.to_owned(); let worker_instance: &str = "test worker instance"; let worker_name: String = next_worker_name(); @@ -1036,11 +1048,11 @@ fn spawn_test_worker( priority: 0, tags: (*wc).worker_tags.clone(), }]; - windmill_common::worker::make_suspended_pull_query(&wc).await; - windmill_common::worker::make_pull_query(&wc).await; + windmill_common::worker::store_suspended_pull_query(&wc).await; + windmill_common::worker::store_pull_query(&wc).await; } windmill_worker::run_worker( - &db, + &db.into(), worker_instance, worker_name, 1, @@ -1049,7 +1061,6 @@ fn spawn_test_worker( rx, tx2, &base_internal_url, - false, ) .await }; @@ -1109,6 +1120,7 @@ trait StreamFind: futures::Stream + Unpin + Sized { impl StreamFind for T {} +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_deno_flow(db: Pool) { initialize_tracing().await; @@ -1227,6 +1239,7 @@ async fn test_deno_flow(db: Pool) { } } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_identity(db: Pool) { initialize_tracing().await; @@ -1264,6 +1277,7 @@ async fn test_identity(db: Pool) { assert_eq!(result, serde_json::json!(42)); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_deno_flow_same_worker(db: Pool) { initialize_tracing().await; @@ -1539,6 +1553,7 @@ async fn test_flow_result_by_id(db: Pool) { assert_eq!(result, serde_json::json!([[42]])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_stop_after_if(db: Pool) { initialize_tracing().await; @@ -1592,6 +1607,7 @@ async fn test_stop_after_if(db: Pool) { assert_eq!(json!(-123), result); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_stop_after_if_nested(db: Pool) { initialize_tracing().await; @@ -1650,6 +1666,7 @@ async fn test_stop_after_if_nested(db: Pool) { assert_eq!(json!([-123]), result); } +#[cfg(all(feature = "deno_core", feature = "python"))] #[sqlx::test(fixtures("base"))] async fn test_python_flow(db: Pool) { initialize_tracing().await; @@ -1707,6 +1724,7 @@ async fn test_python_flow(db: Pool) { } } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_flow_2(db: Pool) { initialize_tracing().await; @@ -1781,6 +1799,7 @@ func main(derp string) (string, error) { assert_eq!(result, serde_json::json!("hello world")); } +#[cfg(feature = "rust")] #[sqlx::test(fixtures("base"))] async fn test_rust_job(db: Pool) { initialize_tracing().await; @@ -1888,6 +1907,155 @@ echo "hello $msg" assert_eq!(job.json_result(), Some(json!("hello world"))); } +#[cfg(feature = "nu")] +#[sqlx::test(fixtures("base"))] +async fn test_nu_job(db: Pool) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + + let content = r#" +def main [ msg: string ] { + "hello " + $msg +} +"# + .to_owned(); + + let job = RunJob::from(JobPayload::Code(RawCode { + hash: None, + content, + path: None, + lock: None, + language: ScriptLang::Nu, + custom_concurrency_key: None, + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + })) + .arg("msg", json!("world")) + .run_until_complete(&db, port) + .await; + assert_eq!(job.json_result(), Some(json!("hello world"))); +} + +#[cfg(feature = "nu")] +#[sqlx::test(fixtures("base"))] +async fn test_nu_job_full(db: Pool) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + + let content = r#" +def main [ + # Required + ## Primitive + a + b: any + c: bool + d: float + e: datetime + f: string + j: nothing + ## Nesting + g: record + h: list + i: table + # Optional + m? + n = "foo" + o: any = "foo" + p?: any + # TODO: ...x + ] { + 0 +} + "# + .to_owned(); + + let result = RunJob::from(JobPayload::Code(RawCode { + hash: None, + content, + path: None, + lock: None, + language: ScriptLang::Nu, + custom_concurrency_key: None, + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + })) + .arg("a", json!("3")) + .arg("b", json!("null")) + .arg("c", json!(true)) + .arg("d", json!(3.0)) + .arg("e", json!("2024-09-24T10:00:00.000Z")) + .arg("f", json!("str")) + .arg("j", json!(null)) + .arg("g", json!({"a": 32})) + .arg("h", json!(["foo"])) + .arg( + "i", + json!([ + {"a": 1, "b": "foo", "c": true}, + {"a": 2, "b": "baz", "c": false} + ]), + ) + .arg("n", json!("baz")) + .run_until_complete(&db, port) + .await + .json_result() + .unwrap(); + + assert_eq!(result, serde_json::json!(0)); +} + +#[cfg(feature = "java")] +#[sqlx::test(fixtures("base"))] +async fn test_java_job(db: Pool) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + + let content = r#" +public class Main { + public static Object main( + // Primitive + int a, + float b, + // Objects + Integer age, + Float d + ){ + return "hello world"; + } +} + +"# + .to_owned(); + + let job = RunJob::from(JobPayload::Code(RawCode { + hash: None, + content, + path: None, + lock: None, + language: ScriptLang::Java, + custom_concurrency_key: None, + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + })) + .arg("a", json!(3)) + .arg("b", json!(3.0)) + .arg("age", json!(30)) + .arg("d", json!(3.0)) + .run_until_complete(&db, port) + .await; + assert_eq!(job.json_result(), Some(json!("hello world"))); +} + +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_job(db: Pool) { initialize_tracing().await; @@ -1921,6 +2089,7 @@ def main(): assert_eq!(result, serde_json::json!("hello world")); } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_job_heavy_dep(db: Pool) { initialize_tracing().await; @@ -1957,6 +2126,7 @@ def main(): assert_eq!(result, serde_json::json!(3)); } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_job_with_imports(db: Pool) { initialize_tracing().await; @@ -2060,6 +2230,7 @@ export async function main(a: Date) { assert_eq!(result, serde_json::json!("object")); } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_job_datetime_and_bytes(db: Pool) { initialize_tracing().await; @@ -2095,6 +2266,7 @@ def main(a: datetime, b: bytes): assert_eq!(result, serde_json::json!([true, true])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_empty_loop_1(db: Pool) { initialize_tracing().await; @@ -2151,6 +2323,7 @@ async fn test_empty_loop_1(db: Pool) { assert_eq!(result, serde_json::json!(0)); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_invalid_first_step(db: Pool) { initialize_tracing().await; @@ -2231,6 +2404,7 @@ async fn test_empty_loop_2(db: Pool) { assert_eq!(result, serde_json::json!([])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_step_after_loop(db: Pool) { initialize_tracing().await; @@ -2354,6 +2528,7 @@ async fn test_branchone_simple(db: Pool) { assert_eq!(result, serde_json::json!([1, 2])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_branchone_with_cond(db: Pool) { initialize_tracing().await; @@ -2390,6 +2565,7 @@ async fn test_branchone_with_cond(db: Pool) { assert_eq!(result, serde_json::json!([1, 3])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_branchall_sequential(db: Pool) { initialize_tracing().await; @@ -2428,6 +2604,7 @@ async fn test_branchall_sequential(db: Pool) { assert_eq!(result, serde_json::json!([[1, 2], [1, 3]])); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_branchall_simple(db: Pool) { initialize_tracing().await; @@ -2555,6 +2732,7 @@ async fn test_branchall_skip_failure(db: Pool) { ); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_branchone_nested(db: Pool) { initialize_tracing().await; @@ -2674,6 +2852,7 @@ async fn test_branchall_nested(db: Pool) { ); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_failure_module(db: Pool) { initialize_tracing().await; @@ -2786,6 +2965,7 @@ async fn test_failure_module(db: Pool) { assert_eq!(json!({ "l": [0, 1, 2] }), result); } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_flow_lock_all(db: Pool) { use futures::StreamExt; @@ -2924,6 +3104,7 @@ async fn test_flow_lock_all(db: Pool) { }); } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_complex_flow_restart(db: Pool) { @@ -3608,6 +3789,7 @@ export async function main() { run_preview_relative_imports(&db, content, ScriptLang::Bun).await; } +#[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "relative_bun"))] async fn test_nested_imports_bun(db: Pool) { let content = r#" @@ -3656,6 +3838,7 @@ export async function main() { run_preview_relative_imports(&db, content, ScriptLang::Deno).await; } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base", "relative_python"))] async fn test_relative_imports_python(db: Pool) { let content = r#" @@ -3673,6 +3856,7 @@ def main(): run_preview_relative_imports(&db, content, ScriptLang::Python3).await; } +#[cfg(feature = "python")] #[sqlx::test(fixtures("base", "relative_python"))] async fn test_nested_imports_python(db: Pool) { let content = r#" @@ -3688,6 +3872,241 @@ def main(): run_preview_relative_imports(&db, content, ScriptLang::Python3).await; } +#[cfg(feature = "python")] +async fn assert_lockfile( + db: &Pool, + script_content: String, + language: ScriptLang, + expected_lines: Vec<&str>, +) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + let client = windmill_api_client::create_client( + &format!("http://localhost:{port}"), + "SECRET_TOKEN".to_string(), + ); + + client + .create_script( + "test-workspace", + &NewScript { + language: NewScriptLanguage::from_str(language.as_str()).unwrap(), + content: script_content, + path: "f/system/test_import".to_string(), + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + description: "".to_string(), + draft_only: None, + envs: vec![], + is_template: None, + kind: None, + parent_hash: None, + lock: None, + summary: "".to_string(), + tag: None, + schema: std::collections::HashMap::new(), + ws_error_handler_muted: Some(false), + priority: None, + delete_after_use: None, + timeout: None, + restart_unless_cancelled: None, + deployment_message: None, + concurrency_key: None, + visible_to_runner_only: None, + no_main_func: None, + codebase: None, + has_preprocessor: None, + on_behalf_of_email: None, + }, + ) + .await + .unwrap(); + + let mut completed = listen_for_completed_jobs(&db).await; + let db2 = db.clone(); + in_test_worker( + &db, + async move { + completed.next().await; // deployed script + + let script = sqlx::query!( + "SELECT hash FROM script WHERE path = $1", + "f/system/test_import".to_string() + ) + .fetch_one(&db2) + .await + .unwrap(); + + let job = RunJob::from(JobPayload::Dependencies { + path: "f/system/test_import".to_string(), + hash: ScriptHash(script.hash), + dedicated_worker: None, + language, + }) + .push(&db2) + .await; + + completed.next().await; // completed job + + let result = completed_job(job, &db2).await.json_result().unwrap(); + + assert_eq!( + result, + json!({ + "lock": expected_lines.join("\n"), + "status": "Successful lock file generation" + }) + ); + }, + port, + ) + .await; +} + +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "lockfile_python"))] +async fn test_requirements_python(db: Pool) { + let content = r#"# py: ==3.11.11 +# requirements: +# tiny==0.1.3 + +import bar +import baz # pin: foo +import baz # repin: fee +import bug # repin: free + +def main(): + pass +"# + .to_string(); + + assert_lockfile( + &db, + content, + ScriptLang::Python3, + vec!["# py: 3.11.11", "tiny==0.1.3"], + ) + .await; +} + +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "lockfile_python"))] +async fn test_extra_requirements_python(db: Pool) { + { + let content = r#"# py: ==3.11.11 +# extra_requirements: +# tiny + +import f.system.extra_requirements +import tiny # pin: tiny==0.1.0 +import tiny # pin: tiny==0.1.1 +import tiny # repin: tiny==0.1.2 + +def main(): + pass + "# + .to_string(); + + assert_lockfile( + &db, + content, + ScriptLang::Python3, + vec!["# py: 3.11.11", "bottle==0.13.2", "tiny==0.1.2"], + ) + .await; + } +} + +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "lockfile_python"))] +async fn test_extra_requirements_python2(db: Pool) { + let content = r#"# py: ==3.11.11 +# extra_requirements: +# tiny==0.1.3 + +import simplejson # pin: simplejson==3.20.1 +def main(): + pass +"# + .to_string(); + + assert_lockfile( + &db, + content, + ScriptLang::Python3, + vec!["# py: 3.11.11", "simplejson==3.20.1", "tiny==0.1.3"], + ) + .await; +} + +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "lockfile_python"))] +async fn test_pins_python(db: Pool) { + let content = r#"# py: ==3.11.11 +# extra_requirements: +# tiny==0.1.3 +# bottle==0.13.2 + +import f.system.requirements +import f.system.pins +import tiny # repin: tiny==0.1.3 +import simplejson + +def main(): + pass +"# + .to_string(); + + assert_lockfile( + &db, + content, + ScriptLang::Python3, + vec![ + "# py: 3.11.11", + "bottle==0.13.2", + "microdot==2.2.0", + "simplejson==3.19.3", + "tiny==0.1.3", + ], + ) + .await; +} +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "multipython"))] +async fn test_multipython_python(db: Pool) { + 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) { + 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) { let ordered_result_job_id = "1eecb96a-c8b0-4a3d-b1b6-087878c55e41"; @@ -3698,7 +4117,7 @@ async fn test_result_format(db: Pool) { let port = server.addr.port(); - let token = windmill_worker::create_token_for_owner( + let token = windmill_common::auth::create_token_for_owner( &db, "test-workspace", "u/test-user", @@ -3706,6 +4125,7 @@ async fn test_result_format(db: Pool) { 100, "", &Uuid::nil(), + None, ) .await .unwrap(); @@ -3738,7 +4158,7 @@ async fn test_result_format(db: Pool) { assert_eq!(job_result.get(), correct_result); let response = windmill_api::jobs::run_wait_result( - &db, + &db.into(), Uuid::parse_str(ordered_result_job_id).unwrap(), "test-workspace".to_string(), None, @@ -3938,6 +4358,7 @@ mod job_payload { ]; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_script_hash_payload(db: Pool) { initialize_tracing().await; @@ -4098,6 +4519,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_flow_node_payload(db: Pool) { initialize_tracing().await; @@ -4282,6 +4704,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_flow_payload(db: Pool) { initialize_tracing().await; @@ -4293,6 +4716,7 @@ mod job_payload { path: "f/system/hello_with_nodes_flow".to_string(), dedicated_worker: None, apply_preprocessor: false, + version: 1443253234253454, }) .run_until_complete(&db, port) .await @@ -4324,6 +4748,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_flow_payload_with_preprocessor(db: Pool) { initialize_tracing().await; @@ -4336,6 +4761,7 @@ mod job_payload { path: "f/system/hello_with_preprocessor".to_string(), dedicated_worker: None, apply_preprocessor: true, + version: 1443253234253456, }) .run_until_complete_with(db, port, |id| async move { let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", id) @@ -4391,6 +4817,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_restarted_flow_payload(db: Pool) { initialize_tracing().await; @@ -4402,6 +4829,7 @@ mod job_payload { path: "f/system/hello_with_nodes_flow".to_string(), dedicated_worker: None, apply_preprocessor: true, + version: 1443253234253454, }) .run_until_complete(&db, port) .await @@ -4443,6 +4871,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_raw_flow_payload(db: Pool) { initialize_tracing().await; @@ -4489,6 +4918,7 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base", "hello"))] async fn test_raw_flow_payload_with_restarted_from(db: Pool) { initialize_tracing().await; diff --git a/backend/update_sqlx.sh b/backend/update_sqlx.sh index 11b5196ad8..6e64115ce0 100755 --- a/backend/update_sqlx.sh +++ b/backend/update_sqlx.sh @@ -1,4 +1,18 @@ -./substitute_ee_code.sh --dir ../windmill-ee-private +#!/bin/bash + +# Default directory +EE_DIR="../windmill-ee-private" + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --dir) EE_DIR="$2"; shift ;; + *) echo "Unknown parameter: $1"; exit 1 ;; + esac + shift +done + +./substitute_ee_code.sh --dir "$EE_DIR" # Check if running on macOS if [[ "$(uname)" == "Darwin" ]]; then @@ -10,7 +24,6 @@ if [[ "$(uname)" == "Darwin" ]]; then fi cargo sqlx prepare --workspace -- --all-targets --all-features -./substitute_ee_code.sh -r --dir ../windmill-ee-private # Undo the samael changes on macOS if [[ "$(uname)" == "Darwin" ]]; then @@ -19,4 +32,4 @@ if [[ "$(uname)" == "Darwin" ]]; then sed -i '' 's/^#samael = { version="0.0.14", features = \["xmlsec"\] }/samael = { version="0.0.14", features = ["xmlsec"] }/' Cargo.toml # Comment out the git-based samael dependency sed -i '' 's/^\(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/# \1/' Cargo.toml -fi +fi \ No newline at end of file diff --git a/backend/windmill-api-client/Cargo.toml b/backend/windmill-api-client/Cargo.toml index ab764e6670..a2d9a64baf 100644 --- a/backend/windmill-api-client/Cargo.toml +++ b/backend/windmill-api-client/Cargo.toml @@ -3,7 +3,6 @@ name = "windmill-api-client" version.workspace = true authors.workspace = true edition.workspace = true -build = "build.rs" [lib] name = "windmill_api_client" @@ -21,9 +20,3 @@ rand.workspace = true base64.workspace = true openapiv3 = "=1.0.2" -[build-dependencies] -prettyplease = "0.1.25" -progenitor = { git = "https://github.com/oxidecomputer/progenitor", rev = "3d96016ae8d422e90513b2d34fb5b63eeab30b01" } -serde_json = "1.0" -syn = "1.0" -openapiv3 = "=1.0.2" \ No newline at end of file diff --git a/backend/windmill-api-client/build.rs b/backend/windmill-api-client/build.rs deleted file mode 100644 index 202952e547..0000000000 --- a/backend/windmill-api-client/build.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::{ - env, - fs::{self, File}, - path::Path, - process::Command, -}; - -fn main() { - let src = "../windmill-api/openapi.yaml"; - println!("cargo:rerun-if-changed={}", src); - Command::new("sh").args(&["bundle.sh"]).status().unwrap(); - let file = File::open("./bundled.json").unwrap(); - let spec = serde_json::from_reader(file).unwrap(); - let mut generator = progenitor::Generator::default(); - - let tokens = generator.generate_tokens(&spec).unwrap(); - let ast = syn::parse2(tokens).unwrap(); - let content = prettyplease::unparse(&ast); - - let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); - out_file.push("codegen.rs"); - - fs::write(out_file, content).unwrap(); -} diff --git a/backend/windmill-api-client/build.sh b/backend/windmill-api-client/build.sh new file mode 100755 index 0000000000..a3afecda3d --- /dev/null +++ b/backend/windmill-api-client/build.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd build_cargo +cargo run --bin windmill_api_client_build diff --git a/backend/windmill-api-client/build_cargo/Cargo.lock b/backend/windmill-api-client/build_cargo/Cargo.lock new file mode 100644 index 0000000000..ddf1947942 --- /dev/null +++ b/backend/windmill-api-client/build_cargo/Cargo.lock @@ -0,0 +1,2059 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "built" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99c4cdc7b2c2364182331055623bdf45254fcb679fea565c40c3c11c101889a" +dependencies = [ + "cargo-lock", + "git2", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cargo-lock" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11c675378efb449ed3ce8de78d75d0d80542fc98487c26aba28eb3b82feac72" +dependencies = [ + "semver", + "serde", + "toml 0.7.8", + "url", +] + +[[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "clap" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "git2" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.8.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libgit2-sys" +version = "0.15.2+1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "openapiv3" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1a9f106eb0a780abd17ba9fca8e0843e3461630bcbe2af0ad4d5d3ba4e9aa4" +dependencies = [ + "indexmap 1.9.3", + "serde", + "serde_json", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "progenitor" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" +dependencies = [ + "anyhow", + "built", + "clap", + "openapiv3", + "progenitor-client", + "progenitor-impl", + "progenitor-macro", + "project-root", + "rustfmt-wrapper", + "serde", + "serde_json", + "serde_yaml", +] + +[[package]] +name = "progenitor-client" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" +dependencies = [ + "bytes", + "futures-core", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "progenitor-impl" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" +dependencies = [ + "getopts", + "heck 0.4.1", + "http", + "indexmap 1.9.3", + "openapiv3", + "proc-macro2", + "quote", + "regex", + "schemars", + "serde", + "serde_json", + "syn 2.0.100", + "thiserror", + "typify", + "unicode-ident", +] + +[[package]] +name = "progenitor-macro" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/progenitor?rev=3d96016ae8d422e90513b2d34fb5b63eeab30b01#3d96016ae8d422e90513b2d34fb5b63eeab30b01" +dependencies = [ + "openapiv3", + "proc-macro2", + "progenitor-impl", + "quote", + "schemars", + "serde", + "serde_json", + "serde_tokenstream", + "serde_yaml", + "syn 2.0.100", +] + +[[package]] +name = "project-root" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "regress" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a9ecfa0cb04d0b04dddb99b8ccf4f66bc8dfd23df694b398570bd8ae3a50fb" +dependencies = [ + "hashbrown 0.13.2", + "memchr", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustfmt-wrapper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" +dependencies = [ + "serde", + "tempfile", + "thiserror", + "toml 0.8.20", + "toolchain_find", +] + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "chrono", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.100", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.100", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.8.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.24", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.8.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap 2.8.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.7.4", +] + +[[package]] +name = "toolchain_find" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" +dependencies = [ + "home", + "once_cell", + "regex", + "semver", + "walkdir", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typify" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6658d09e71bfe59e7987dc95ee7f71809fdb5793ab0cdc1503cc0073990484d" +dependencies = [ + "typify-impl", + "typify-macro", +] + +[[package]] +name = "typify-impl" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d3bb47587b13edf526d6ed02bf360ecefe083ab47a4ef29fc43112828b2bef" +dependencies = [ + "heck 0.4.1", + "log", + "proc-macro2", + "quote", + "regress", + "schemars", + "serde_json", + "syn 2.0.100", + "thiserror", + "unicode-ident", +] + +[[package]] +name = "typify-macro" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3f7e627c18be12d53bc1f261830b9c2763437b6a86ac57293b9085af2d32ffe" +dependencies = [ + "proc-macro2", + "quote", + "schemars", + "serde", + "serde_json", + "serde_tokenstream", + "syn 2.0.100", + "typify-impl", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windmill-api-client-build" +version = "0.1.0" +dependencies = [ + "openapiv3", + "prettyplease", + "progenitor", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/backend/windmill-api-client/build_cargo/Cargo.toml b/backend/windmill-api-client/build_cargo/Cargo.toml new file mode 100644 index 0000000000..1e6a9ce192 --- /dev/null +++ b/backend/windmill-api-client/build_cargo/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "windmill-api-client-build" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "windmill_api_client_build" +path = "./main.rs" + + +[dependencies] +prettyplease = "0.1.25" +progenitor = { git = "https://github.com/oxidecomputer/progenitor", rev = "3d96016ae8d422e90513b2d34fb5b63eeab30b01" } +serde_json = "1.0" +syn = "1.0" +openapiv3 = "=1.0.2" + +[workspace] diff --git a/backend/windmill-api-client/build_cargo/bundle.sh b/backend/windmill-api-client/build_cargo/bundle.sh new file mode 100755 index 0000000000..665fae59fb --- /dev/null +++ b/backend/windmill-api-client/build_cargo/bundle.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +npx swagger-cli bundle ../../windmill-api/openapi.yaml > bundled.json \ No newline at end of file diff --git a/backend/windmill-api-client/build_cargo/main.rs b/backend/windmill-api-client/build_cargo/main.rs new file mode 100644 index 0000000000..0a6f1b447e --- /dev/null +++ b/backend/windmill-api-client/build_cargo/main.rs @@ -0,0 +1,32 @@ +use std::{ + fs::{self, File}, + path::Path, + process::Command, +}; + +fn main() { + Command::new("sh").args(&["./bundle.sh"]).status().unwrap(); + let file = File::open("./bundled.json").unwrap(); + let mut spec: openapiv3::OpenAPI = serde_json::from_reader(file).unwrap(); + spec.paths.paths.retain(|key, _| { + [ + "/w/{workspace}/flows/create", + "/w/{workspace}/flows/get/{path}", + "/w/{workspace}/scripts/create", + "/workspaces/list", + "/w/{workspace}/schedules/create", + "/w/{workspace}/schedules/update/{path}", + ] + .contains(&key.as_str()) + }); + + let mut generator = progenitor::Generator::default(); + let tokens = generator.generate_tokens(&spec).unwrap(); + let ast = syn::parse2(tokens).unwrap(); + let content = prettyplease::unparse(&ast); + + let mut out_file = Path::new("../src").to_path_buf(); + out_file.push("codegen.rs"); + + fs::write(out_file, content).unwrap(); +} diff --git a/backend/windmill-api-client/bundle.sh b/backend/windmill-api-client/bundle.sh deleted file mode 100755 index 0fe4b172ca..0000000000 --- a/backend/windmill-api-client/bundle.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -npx swagger-cli bundle ../windmill-api/openapi.yaml > bundled.json \ No newline at end of file diff --git a/backend/windmill-api-client/codegen.rs b/backend/windmill-api-client/codegen.rs new file mode 100644 index 0000000000..202145159e --- /dev/null +++ b/backend/windmill-api-client/codegen.rs @@ -0,0 +1,24391 @@ +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +#[allow(unused_imports)] +use reqwest::header::{HeaderMap, HeaderValue}; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AcceptInviteBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + pub workspace_id: String, + } + impl From<&AcceptInviteBody> for AcceptInviteBody { + fn from(value: &AcceptInviteBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AddGranularAclsBody { + pub owner: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub write: Option, + } + impl From<&AddGranularAclsBody> for AddGranularAclsBody { + fn from(value: &AddGranularAclsBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AddGranularAclsKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "group_")] + Group, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "app")] + App, + #[serde(rename = "raw_app")] + RawApp, + #[serde(rename = "http_trigger")] + HttpTrigger, + #[serde(rename = "websocket_trigger")] + WebsocketTrigger, + #[serde(rename = "kafka_trigger")] + KafkaTrigger, + #[serde(rename = "nats_trigger")] + NatsTrigger, + #[serde(rename = "postgres_trigger")] + PostgresTrigger, + #[serde(rename = "mqtt_trigger")] + MqttTrigger, + #[serde(rename = "sqs_trigger")] + SqsTrigger, + } + impl From<&AddGranularAclsKind> for AddGranularAclsKind { + fn from(value: &AddGranularAclsKind) -> Self { + value.clone() + } + } + impl ToString for AddGranularAclsKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Group => "group_".to_string(), + Self::Resource => "resource".to_string(), + Self::Schedule => "schedule".to_string(), + Self::Variable => "variable".to_string(), + Self::Flow => "flow".to_string(), + Self::Folder => "folder".to_string(), + Self::App => "app".to_string(), + Self::RawApp => "raw_app".to_string(), + Self::HttpTrigger => "http_trigger".to_string(), + Self::WebsocketTrigger => "websocket_trigger".to_string(), + Self::KafkaTrigger => "kafka_trigger".to_string(), + Self::NatsTrigger => "nats_trigger".to_string(), + Self::PostgresTrigger => "postgres_trigger".to_string(), + Self::MqttTrigger => "mqtt_trigger".to_string(), + Self::SqsTrigger => "sqs_trigger".to_string(), + } + } + } + impl std::str::FromStr for AddGranularAclsKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "group_" => Ok(Self::Group), + "resource" => Ok(Self::Resource), + "schedule" => Ok(Self::Schedule), + "variable" => Ok(Self::Variable), + "flow" => Ok(Self::Flow), + "folder" => Ok(Self::Folder), + "app" => Ok(Self::App), + "raw_app" => Ok(Self::RawApp), + "http_trigger" => Ok(Self::HttpTrigger), + "websocket_trigger" => Ok(Self::WebsocketTrigger), + "kafka_trigger" => Ok(Self::KafkaTrigger), + "nats_trigger" => Ok(Self::NatsTrigger), + "postgres_trigger" => Ok(Self::PostgresTrigger), + "mqtt_trigger" => Ok(Self::MqttTrigger), + "sqs_trigger" => Ok(Self::SqsTrigger), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AddGranularAclsKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AddGranularAclsKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AddGranularAclsKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AddOwnerToFolderBody { + pub owner: String, + } + impl From<&AddOwnerToFolderBody> for AddOwnerToFolderBody { + fn from(value: &AddOwnerToFolderBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AddUserBody { + pub email: String, + pub is_admin: bool, + pub operator: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + } + impl From<&AddUserBody> for AddUserBody { + fn from(value: &AddUserBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AddUserToGroupBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + } + impl From<&AddUserToGroupBody> for AddUserToGroupBody { + fn from(value: &AddUserToGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AddUserToInstanceGroupBody { + pub email: String, + } + impl From<&AddUserToInstanceGroupBody> for AddUserToInstanceGroupBody { + fn from(value: &AddUserToInstanceGroupBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AiProvider { + #[serde(rename = "openai")] + Openai, + #[serde(rename = "anthropic")] + Anthropic, + #[serde(rename = "mistral")] + Mistral, + #[serde(rename = "deepseek")] + Deepseek, + #[serde(rename = "googleai")] + Googleai, + #[serde(rename = "groq")] + Groq, + #[serde(rename = "openrouter")] + Openrouter, + #[serde(rename = "customai")] + Customai, + } + impl From<&AiProvider> for AiProvider { + fn from(value: &AiProvider) -> Self { + value.clone() + } + } + impl ToString for AiProvider { + fn to_string(&self) -> String { + match *self { + Self::Openai => "openai".to_string(), + Self::Anthropic => "anthropic".to_string(), + Self::Mistral => "mistral".to_string(), + Self::Deepseek => "deepseek".to_string(), + Self::Googleai => "googleai".to_string(), + Self::Groq => "groq".to_string(), + Self::Openrouter => "openrouter".to_string(), + Self::Customai => "customai".to_string(), + } + } + } + impl std::str::FromStr for AiProvider { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "openai" => Ok(Self::Openai), + "anthropic" => Ok(Self::Anthropic), + "mistral" => Ok(Self::Mistral), + "deepseek" => Ok(Self::Deepseek), + "googleai" => Ok(Self::Googleai), + "groq" => Ok(Self::Groq), + "openrouter" => Ok(Self::Openrouter), + "customai" => Ok(Self::Customai), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AiProvider { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AiProvider { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AiProvider { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AiResource { + pub path: String, + pub provider: AiProvider, + } + impl From<&AiResource> for AiResource { + fn from(value: &AiResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppHistory { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub version: i64, + } + impl From<&AppHistory> for AppHistory { + fn from(value: &AppHistory) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppWithLastVersion { + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_path: Option, + pub execution_mode: AppWithLastVersionExecutionMode, + pub extra_perms: std::collections::HashMap, + pub id: i64, + pub path: String, + pub policy: Policy, + pub summary: String, + pub value: std::collections::HashMap, + pub versions: Vec, + pub workspace_id: String, + } + impl From<&AppWithLastVersion> for AppWithLastVersion { + fn from(value: &AppWithLastVersion) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AppWithLastVersionExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&AppWithLastVersionExecutionMode> for AppWithLastVersionExecutionMode { + fn from(value: &AppWithLastVersionExecutionMode) -> Self { + value.clone() + } + } + impl ToString for AppWithLastVersionExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for AppWithLastVersionExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppWithLastVersionWDraft { + #[serde(flatten)] + pub app_with_last_version: AppWithLastVersion, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + } + impl From<&AppWithLastVersionWDraft> for AppWithLastVersionWDraft { + fn from(value: &AppWithLastVersionWDraft) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ArchiveFlowByPathBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub archived: Option, + } + impl From<&ArchiveFlowByPathBody> for ArchiveFlowByPathBody { + fn from(value: &ArchiveFlowByPathBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AuditLog { + pub action_kind: AuditLogActionKind, + pub id: i64, + pub operation: AuditLogOperation, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub parameters: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, + pub timestamp: chrono::DateTime, + pub username: String, + } + impl From<&AuditLog> for AuditLog { + fn from(value: &AuditLog) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AuditLogActionKind { + Created, + Updated, + Delete, + Execute, + } + impl From<&AuditLogActionKind> for AuditLogActionKind { + fn from(value: &AuditLogActionKind) -> Self { + value.clone() + } + } + impl ToString for AuditLogActionKind { + fn to_string(&self) -> String { + match *self { + Self::Created => "Created".to_string(), + Self::Updated => "Updated".to_string(), + Self::Delete => "Delete".to_string(), + Self::Execute => "Execute".to_string(), + } + } + } + impl std::str::FromStr for AuditLogActionKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Created" => Ok(Self::Created), + "Updated" => Ok(Self::Updated), + "Delete" => Ok(Self::Delete), + "Execute" => Ok(Self::Execute), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AuditLogOperation { + #[serde(rename = "jobs.run")] + JobsRun, + #[serde(rename = "jobs.run.script")] + JobsRunScript, + #[serde(rename = "jobs.run.preview")] + JobsRunPreview, + #[serde(rename = "jobs.run.flow")] + JobsRunFlow, + #[serde(rename = "jobs.run.flow_preview")] + JobsRunFlowPreview, + #[serde(rename = "jobs.run.script_hub")] + JobsRunScriptHub, + #[serde(rename = "jobs.run.dependencies")] + JobsRunDependencies, + #[serde(rename = "jobs.run.identity")] + JobsRunIdentity, + #[serde(rename = "jobs.run.noop")] + JobsRunNoop, + #[serde(rename = "jobs.flow_dependencies")] + JobsFlowDependencies, + #[serde(rename = "jobs")] + Jobs, + #[serde(rename = "jobs.cancel")] + JobsCancel, + #[serde(rename = "jobs.force_cancel")] + JobsForceCancel, + #[serde(rename = "jobs.disapproval")] + JobsDisapproval, + #[serde(rename = "jobs.delete")] + JobsDelete, + #[serde(rename = "account.delete")] + AccountDelete, + #[serde(rename = "ai.request")] + AiRequest, + #[serde(rename = "resources.create")] + ResourcesCreate, + #[serde(rename = "resources.update")] + ResourcesUpdate, + #[serde(rename = "resources.delete")] + ResourcesDelete, + #[serde(rename = "resource_types.create")] + ResourceTypesCreate, + #[serde(rename = "resource_types.update")] + ResourceTypesUpdate, + #[serde(rename = "resource_types.delete")] + ResourceTypesDelete, + #[serde(rename = "schedule.create")] + ScheduleCreate, + #[serde(rename = "schedule.setenabled")] + ScheduleSetenabled, + #[serde(rename = "schedule.edit")] + ScheduleEdit, + #[serde(rename = "schedule.delete")] + ScheduleDelete, + #[serde(rename = "scripts.create")] + ScriptsCreate, + #[serde(rename = "scripts.update")] + ScriptsUpdate, + #[serde(rename = "scripts.archive")] + ScriptsArchive, + #[serde(rename = "scripts.delete")] + ScriptsDelete, + #[serde(rename = "users.create")] + UsersCreate, + #[serde(rename = "users.delete")] + UsersDelete, + #[serde(rename = "users.update")] + UsersUpdate, + #[serde(rename = "users.login")] + UsersLogin, + #[serde(rename = "users.login_failure")] + UsersLoginFailure, + #[serde(rename = "users.logout")] + UsersLogout, + #[serde(rename = "users.accept_invite")] + UsersAcceptInvite, + #[serde(rename = "users.decline_invite")] + UsersDeclineInvite, + #[serde(rename = "users.token.create")] + UsersTokenCreate, + #[serde(rename = "users.token.delete")] + UsersTokenDelete, + #[serde(rename = "users.add_to_workspace")] + UsersAddToWorkspace, + #[serde(rename = "users.add_global")] + UsersAddGlobal, + #[serde(rename = "users.setpassword")] + UsersSetpassword, + #[serde(rename = "users.impersonate")] + UsersImpersonate, + #[serde(rename = "users.leave_workspace")] + UsersLeaveWorkspace, + #[serde(rename = "oauth.login")] + OauthLogin, + #[serde(rename = "oauth.login_failure")] + OauthLoginFailure, + #[serde(rename = "oauth.signup")] + OauthSignup, + #[serde(rename = "variables.create")] + VariablesCreate, + #[serde(rename = "variables.delete")] + VariablesDelete, + #[serde(rename = "variables.update")] + VariablesUpdate, + #[serde(rename = "flows.create")] + FlowsCreate, + #[serde(rename = "flows.update")] + FlowsUpdate, + #[serde(rename = "flows.delete")] + FlowsDelete, + #[serde(rename = "flows.archive")] + FlowsArchive, + #[serde(rename = "apps.create")] + AppsCreate, + #[serde(rename = "apps.update")] + AppsUpdate, + #[serde(rename = "apps.delete")] + AppsDelete, + #[serde(rename = "folder.create")] + FolderCreate, + #[serde(rename = "folder.update")] + FolderUpdate, + #[serde(rename = "folder.delete")] + FolderDelete, + #[serde(rename = "folder.add_owner")] + FolderAddOwner, + #[serde(rename = "folder.remove_owner")] + FolderRemoveOwner, + #[serde(rename = "group.create")] + GroupCreate, + #[serde(rename = "group.delete")] + GroupDelete, + #[serde(rename = "group.edit")] + GroupEdit, + #[serde(rename = "group.adduser")] + GroupAdduser, + #[serde(rename = "group.removeuser")] + GroupRemoveuser, + #[serde(rename = "igroup.create")] + IgroupCreate, + #[serde(rename = "igroup.delete")] + IgroupDelete, + #[serde(rename = "igroup.adduser")] + IgroupAdduser, + #[serde(rename = "igroup.removeuser")] + IgroupRemoveuser, + #[serde(rename = "variables.decrypt_secret")] + VariablesDecryptSecret, + #[serde(rename = "workspaces.edit_command_script")] + WorkspacesEditCommandScript, + #[serde(rename = "workspaces.edit_deploy_to")] + WorkspacesEditDeployTo, + #[serde(rename = "workspaces.edit_auto_invite_domain")] + WorkspacesEditAutoInviteDomain, + #[serde(rename = "workspaces.edit_webhook")] + WorkspacesEditWebhook, + #[serde(rename = "workspaces.edit_copilot_config")] + WorkspacesEditCopilotConfig, + #[serde(rename = "workspaces.edit_error_handler")] + WorkspacesEditErrorHandler, + #[serde(rename = "workspaces.create")] + WorkspacesCreate, + #[serde(rename = "workspaces.update")] + WorkspacesUpdate, + #[serde(rename = "workspaces.archive")] + WorkspacesArchive, + #[serde(rename = "workspaces.unarchive")] + WorkspacesUnarchive, + #[serde(rename = "workspaces.delete")] + WorkspacesDelete, + } + impl From<&AuditLogOperation> for AuditLogOperation { + fn from(value: &AuditLogOperation) -> Self { + value.clone() + } + } + impl ToString for AuditLogOperation { + fn to_string(&self) -> String { + match *self { + Self::JobsRun => "jobs.run".to_string(), + Self::JobsRunScript => "jobs.run.script".to_string(), + Self::JobsRunPreview => "jobs.run.preview".to_string(), + Self::JobsRunFlow => "jobs.run.flow".to_string(), + Self::JobsRunFlowPreview => "jobs.run.flow_preview".to_string(), + Self::JobsRunScriptHub => "jobs.run.script_hub".to_string(), + Self::JobsRunDependencies => "jobs.run.dependencies".to_string(), + Self::JobsRunIdentity => "jobs.run.identity".to_string(), + Self::JobsRunNoop => "jobs.run.noop".to_string(), + Self::JobsFlowDependencies => "jobs.flow_dependencies".to_string(), + Self::Jobs => "jobs".to_string(), + Self::JobsCancel => "jobs.cancel".to_string(), + Self::JobsForceCancel => "jobs.force_cancel".to_string(), + Self::JobsDisapproval => "jobs.disapproval".to_string(), + Self::JobsDelete => "jobs.delete".to_string(), + Self::AccountDelete => "account.delete".to_string(), + Self::AiRequest => "ai.request".to_string(), + Self::ResourcesCreate => "resources.create".to_string(), + Self::ResourcesUpdate => "resources.update".to_string(), + Self::ResourcesDelete => "resources.delete".to_string(), + Self::ResourceTypesCreate => "resource_types.create".to_string(), + Self::ResourceTypesUpdate => "resource_types.update".to_string(), + Self::ResourceTypesDelete => "resource_types.delete".to_string(), + Self::ScheduleCreate => "schedule.create".to_string(), + Self::ScheduleSetenabled => "schedule.setenabled".to_string(), + Self::ScheduleEdit => "schedule.edit".to_string(), + Self::ScheduleDelete => "schedule.delete".to_string(), + Self::ScriptsCreate => "scripts.create".to_string(), + Self::ScriptsUpdate => "scripts.update".to_string(), + Self::ScriptsArchive => "scripts.archive".to_string(), + Self::ScriptsDelete => "scripts.delete".to_string(), + Self::UsersCreate => "users.create".to_string(), + Self::UsersDelete => "users.delete".to_string(), + Self::UsersUpdate => "users.update".to_string(), + Self::UsersLogin => "users.login".to_string(), + Self::UsersLoginFailure => "users.login_failure".to_string(), + Self::UsersLogout => "users.logout".to_string(), + Self::UsersAcceptInvite => "users.accept_invite".to_string(), + Self::UsersDeclineInvite => "users.decline_invite".to_string(), + Self::UsersTokenCreate => "users.token.create".to_string(), + Self::UsersTokenDelete => "users.token.delete".to_string(), + Self::UsersAddToWorkspace => "users.add_to_workspace".to_string(), + Self::UsersAddGlobal => "users.add_global".to_string(), + Self::UsersSetpassword => "users.setpassword".to_string(), + Self::UsersImpersonate => "users.impersonate".to_string(), + Self::UsersLeaveWorkspace => "users.leave_workspace".to_string(), + Self::OauthLogin => "oauth.login".to_string(), + Self::OauthLoginFailure => "oauth.login_failure".to_string(), + Self::OauthSignup => "oauth.signup".to_string(), + Self::VariablesCreate => "variables.create".to_string(), + Self::VariablesDelete => "variables.delete".to_string(), + Self::VariablesUpdate => "variables.update".to_string(), + Self::FlowsCreate => "flows.create".to_string(), + Self::FlowsUpdate => "flows.update".to_string(), + Self::FlowsDelete => "flows.delete".to_string(), + Self::FlowsArchive => "flows.archive".to_string(), + Self::AppsCreate => "apps.create".to_string(), + Self::AppsUpdate => "apps.update".to_string(), + Self::AppsDelete => "apps.delete".to_string(), + Self::FolderCreate => "folder.create".to_string(), + Self::FolderUpdate => "folder.update".to_string(), + Self::FolderDelete => "folder.delete".to_string(), + Self::FolderAddOwner => "folder.add_owner".to_string(), + Self::FolderRemoveOwner => "folder.remove_owner".to_string(), + Self::GroupCreate => "group.create".to_string(), + Self::GroupDelete => "group.delete".to_string(), + Self::GroupEdit => "group.edit".to_string(), + Self::GroupAdduser => "group.adduser".to_string(), + Self::GroupRemoveuser => "group.removeuser".to_string(), + Self::IgroupCreate => "igroup.create".to_string(), + Self::IgroupDelete => "igroup.delete".to_string(), + Self::IgroupAdduser => "igroup.adduser".to_string(), + Self::IgroupRemoveuser => "igroup.removeuser".to_string(), + Self::VariablesDecryptSecret => "variables.decrypt_secret".to_string(), + Self::WorkspacesEditCommandScript => { + "workspaces.edit_command_script".to_string() + } + Self::WorkspacesEditDeployTo => "workspaces.edit_deploy_to".to_string(), + Self::WorkspacesEditAutoInviteDomain => { + "workspaces.edit_auto_invite_domain".to_string() + } + Self::WorkspacesEditWebhook => "workspaces.edit_webhook".to_string(), + Self::WorkspacesEditCopilotConfig => { + "workspaces.edit_copilot_config".to_string() + } + Self::WorkspacesEditErrorHandler => { + "workspaces.edit_error_handler".to_string() + } + Self::WorkspacesCreate => "workspaces.create".to_string(), + Self::WorkspacesUpdate => "workspaces.update".to_string(), + Self::WorkspacesArchive => "workspaces.archive".to_string(), + Self::WorkspacesUnarchive => "workspaces.unarchive".to_string(), + Self::WorkspacesDelete => "workspaces.delete".to_string(), + } + } + } + impl std::str::FromStr for AuditLogOperation { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "jobs.run" => Ok(Self::JobsRun), + "jobs.run.script" => Ok(Self::JobsRunScript), + "jobs.run.preview" => Ok(Self::JobsRunPreview), + "jobs.run.flow" => Ok(Self::JobsRunFlow), + "jobs.run.flow_preview" => Ok(Self::JobsRunFlowPreview), + "jobs.run.script_hub" => Ok(Self::JobsRunScriptHub), + "jobs.run.dependencies" => Ok(Self::JobsRunDependencies), + "jobs.run.identity" => Ok(Self::JobsRunIdentity), + "jobs.run.noop" => Ok(Self::JobsRunNoop), + "jobs.flow_dependencies" => Ok(Self::JobsFlowDependencies), + "jobs" => Ok(Self::Jobs), + "jobs.cancel" => Ok(Self::JobsCancel), + "jobs.force_cancel" => Ok(Self::JobsForceCancel), + "jobs.disapproval" => Ok(Self::JobsDisapproval), + "jobs.delete" => Ok(Self::JobsDelete), + "account.delete" => Ok(Self::AccountDelete), + "ai.request" => Ok(Self::AiRequest), + "resources.create" => Ok(Self::ResourcesCreate), + "resources.update" => Ok(Self::ResourcesUpdate), + "resources.delete" => Ok(Self::ResourcesDelete), + "resource_types.create" => Ok(Self::ResourceTypesCreate), + "resource_types.update" => Ok(Self::ResourceTypesUpdate), + "resource_types.delete" => Ok(Self::ResourceTypesDelete), + "schedule.create" => Ok(Self::ScheduleCreate), + "schedule.setenabled" => Ok(Self::ScheduleSetenabled), + "schedule.edit" => Ok(Self::ScheduleEdit), + "schedule.delete" => Ok(Self::ScheduleDelete), + "scripts.create" => Ok(Self::ScriptsCreate), + "scripts.update" => Ok(Self::ScriptsUpdate), + "scripts.archive" => Ok(Self::ScriptsArchive), + "scripts.delete" => Ok(Self::ScriptsDelete), + "users.create" => Ok(Self::UsersCreate), + "users.delete" => Ok(Self::UsersDelete), + "users.update" => Ok(Self::UsersUpdate), + "users.login" => Ok(Self::UsersLogin), + "users.login_failure" => Ok(Self::UsersLoginFailure), + "users.logout" => Ok(Self::UsersLogout), + "users.accept_invite" => Ok(Self::UsersAcceptInvite), + "users.decline_invite" => Ok(Self::UsersDeclineInvite), + "users.token.create" => Ok(Self::UsersTokenCreate), + "users.token.delete" => Ok(Self::UsersTokenDelete), + "users.add_to_workspace" => Ok(Self::UsersAddToWorkspace), + "users.add_global" => Ok(Self::UsersAddGlobal), + "users.setpassword" => Ok(Self::UsersSetpassword), + "users.impersonate" => Ok(Self::UsersImpersonate), + "users.leave_workspace" => Ok(Self::UsersLeaveWorkspace), + "oauth.login" => Ok(Self::OauthLogin), + "oauth.login_failure" => Ok(Self::OauthLoginFailure), + "oauth.signup" => Ok(Self::OauthSignup), + "variables.create" => Ok(Self::VariablesCreate), + "variables.delete" => Ok(Self::VariablesDelete), + "variables.update" => Ok(Self::VariablesUpdate), + "flows.create" => Ok(Self::FlowsCreate), + "flows.update" => Ok(Self::FlowsUpdate), + "flows.delete" => Ok(Self::FlowsDelete), + "flows.archive" => Ok(Self::FlowsArchive), + "apps.create" => Ok(Self::AppsCreate), + "apps.update" => Ok(Self::AppsUpdate), + "apps.delete" => Ok(Self::AppsDelete), + "folder.create" => Ok(Self::FolderCreate), + "folder.update" => Ok(Self::FolderUpdate), + "folder.delete" => Ok(Self::FolderDelete), + "folder.add_owner" => Ok(Self::FolderAddOwner), + "folder.remove_owner" => Ok(Self::FolderRemoveOwner), + "group.create" => Ok(Self::GroupCreate), + "group.delete" => Ok(Self::GroupDelete), + "group.edit" => Ok(Self::GroupEdit), + "group.adduser" => Ok(Self::GroupAdduser), + "group.removeuser" => Ok(Self::GroupRemoveuser), + "igroup.create" => Ok(Self::IgroupCreate), + "igroup.delete" => Ok(Self::IgroupDelete), + "igroup.adduser" => Ok(Self::IgroupAdduser), + "igroup.removeuser" => Ok(Self::IgroupRemoveuser), + "variables.decrypt_secret" => Ok(Self::VariablesDecryptSecret), + "workspaces.edit_command_script" => Ok(Self::WorkspacesEditCommandScript), + "workspaces.edit_deploy_to" => Ok(Self::WorkspacesEditDeployTo), + "workspaces.edit_auto_invite_domain" => { + Ok(Self::WorkspacesEditAutoInviteDomain) + } + "workspaces.edit_webhook" => Ok(Self::WorkspacesEditWebhook), + "workspaces.edit_copilot_config" => Ok(Self::WorkspacesEditCopilotConfig), + "workspaces.edit_error_handler" => Ok(Self::WorkspacesEditErrorHandler), + "workspaces.create" => Ok(Self::WorkspacesCreate), + "workspaces.update" => Ok(Self::WorkspacesUpdate), + "workspaces.archive" => Ok(Self::WorkspacesArchive), + "workspaces.unarchive" => Ok(Self::WorkspacesUnarchive), + "workspaces.delete" => Ok(Self::WorkspacesDelete), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AuditLogOperation { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AuditLogOperation { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AuditLogOperation { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AutoscalingEvent { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub applied_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub desired_workers: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub event_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker_group: Option, + } + impl From<&AutoscalingEvent> for AutoscalingEvent { + fn from(value: &AutoscalingEvent) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchAll { + pub branches: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(rename = "type")] + pub type_: BranchAllType, + } + impl From<&BranchAll> for BranchAll { + fn from(value: &BranchAll) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchAllBranchesItem { + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&BranchAllBranchesItem> for BranchAllBranchesItem { + fn from(value: &BranchAllBranchesItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum BranchAllType { + #[serde(rename = "branchall")] + Branchall, + } + impl From<&BranchAllType> for BranchAllType { + fn from(value: &BranchAllType) -> Self { + value.clone() + } + } + impl ToString for BranchAllType { + fn to_string(&self) -> String { + match *self { + Self::Branchall => "branchall".to_string(), + } + } + } + impl std::str::FromStr for BranchAllType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branchall" => Ok(Self::Branchall), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for BranchAllType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for BranchAllType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for BranchAllType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchOne { + pub branches: Vec, + pub default: Vec, + #[serde(rename = "type")] + pub type_: BranchOneType, + } + impl From<&BranchOne> for BranchOne { + fn from(value: &BranchOne) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchOneBranchesItem { + pub expr: String, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&BranchOneBranchesItem> for BranchOneBranchesItem { + fn from(value: &BranchOneBranchesItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum BranchOneType { + #[serde(rename = "branchone")] + Branchone, + } + impl From<&BranchOneType> for BranchOneType { + fn from(value: &BranchOneType) -> Self { + value.clone() + } + } + impl ToString for BranchOneType { + fn to_string(&self) -> String { + match *self { + Self::Branchone => "branchone".to_string(), + } + } + } + impl std::str::FromStr for BranchOneType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branchone" => Ok(Self::Branchone), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for BranchOneType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for BranchOneType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for BranchOneType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CancelPersistentQueuedJobsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + } + impl From<&CancelPersistentQueuedJobsBody> for CancelPersistentQueuedJobsBody { + fn from(value: &CancelPersistentQueuedJobsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CancelQueuedJobBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + } + impl From<&CancelQueuedJobBody> for CancelQueuedJobBody { + fn from(value: &CancelQueuedJobBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Capture { + pub created_at: chrono::DateTime, + pub id: i64, + pub payload: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trigger_extra: Option, + pub trigger_kind: CaptureTriggerKind, + } + impl From<&Capture> for Capture { + fn from(value: &Capture) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CaptureConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trigger_config: Option, + pub trigger_kind: CaptureTriggerKind, + } + impl From<&CaptureConfig> for CaptureConfig { + fn from(value: &CaptureConfig) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum CaptureTriggerKind { + #[serde(rename = "webhook")] + Webhook, + #[serde(rename = "http")] + Http, + #[serde(rename = "websocket")] + Websocket, + #[serde(rename = "kafka")] + Kafka, + #[serde(rename = "email")] + Email, + #[serde(rename = "nats")] + Nats, + #[serde(rename = "postgres")] + Postgres, + #[serde(rename = "sqs")] + Sqs, + #[serde(rename = "mqtt")] + Mqtt, + } + impl From<&CaptureTriggerKind> for CaptureTriggerKind { + fn from(value: &CaptureTriggerKind) -> Self { + value.clone() + } + } + impl ToString for CaptureTriggerKind { + fn to_string(&self) -> String { + match *self { + Self::Webhook => "webhook".to_string(), + Self::Http => "http".to_string(), + Self::Websocket => "websocket".to_string(), + Self::Kafka => "kafka".to_string(), + Self::Email => "email".to_string(), + Self::Nats => "nats".to_string(), + Self::Postgres => "postgres".to_string(), + Self::Sqs => "sqs".to_string(), + Self::Mqtt => "mqtt".to_string(), + } + } + } + impl std::str::FromStr for CaptureTriggerKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "webhook" => Ok(Self::Webhook), + "http" => Ok(Self::Http), + "websocket" => Ok(Self::Websocket), + "kafka" => Ok(Self::Kafka), + "email" => Ok(Self::Email), + "nats" => Ok(Self::Nats), + "postgres" => Ok(Self::Postgres), + "sqs" => Ok(Self::Sqs), + "mqtt" => Ok(Self::Mqtt), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ChangeWorkspaceColorBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + } + impl From<&ChangeWorkspaceColorBody> for ChangeWorkspaceColorBody { + fn from(value: &ChangeWorkspaceColorBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ChangeWorkspaceIdBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub new_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub new_name: Option, + } + impl From<&ChangeWorkspaceIdBody> for ChangeWorkspaceIdBody { + fn from(value: &ChangeWorkspaceIdBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ChangeWorkspaceNameBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub new_name: Option, + } + impl From<&ChangeWorkspaceNameBody> for ChangeWorkspaceNameBody { + fn from(value: &ChangeWorkspaceNameBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ChannelInfo { + ///The unique identifier of the channel + pub channel_id: String, + ///The display name of the channel + pub channel_name: String, + ///The service URL for the channel + pub service_url: String, + ///The Microsoft Teams tenant identifier + pub tenant_id: String, + } + impl From<&ChannelInfo> for ChannelInfo { + fn from(value: &ChannelInfo) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ClearIndexIdxName { + JobIndex, + ServiceLogIndex, + } + impl From<&ClearIndexIdxName> for ClearIndexIdxName { + fn from(value: &ClearIndexIdxName) -> Self { + value.clone() + } + } + impl ToString for ClearIndexIdxName { + fn to_string(&self) -> String { + match *self { + Self::JobIndex => "JobIndex".to_string(), + Self::ServiceLogIndex => "ServiceLogIndex".to_string(), + } + } + } + impl std::str::FromStr for ClearIndexIdxName { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "JobIndex" => Ok(Self::JobIndex), + "ServiceLogIndex" => Ok(Self::ServiceLogIndex), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ClearIndexIdxName { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ClearIndexIdxName { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ClearIndexIdxName { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CompletedJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aggregate_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + pub canceled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_reason: Option, + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deleted: Option, + pub duration_ms: i64, + pub email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_status: Option, + pub id: uuid::Uuid, + pub is_flow_step: bool, + pub is_skipped: bool, + pub job_kind: CompletedJobJobKind, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub logs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mem_peak: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_job: Option, + /**The user (u/userfoo) or group (g/groupfoo) whom +the execution of this script will be permissioned_as and by extension its DT_TOKEN. +*/ + pub permissioned_as: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessed: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_flow: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub result: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_wait_time_ms: Option, + pub started_at: chrono::DateTime, + pub success: bool, + pub tag: String, + pub visible_to_owner: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&CompletedJob> for CompletedJob { + fn from(value: &CompletedJob) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum CompletedJobJobKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "preview")] + Preview, + #[serde(rename = "dependencies")] + Dependencies, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "flowdependencies")] + Flowdependencies, + #[serde(rename = "appdependencies")] + Appdependencies, + #[serde(rename = "flowpreview")] + Flowpreview, + #[serde(rename = "script_hub")] + ScriptHub, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "deploymentcallback")] + Deploymentcallback, + #[serde(rename = "singlescriptflow")] + Singlescriptflow, + #[serde(rename = "flowscript")] + Flowscript, + #[serde(rename = "flownode")] + Flownode, + #[serde(rename = "appscript")] + Appscript, + } + impl From<&CompletedJobJobKind> for CompletedJobJobKind { + fn from(value: &CompletedJobJobKind) -> Self { + value.clone() + } + } + impl ToString for CompletedJobJobKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Preview => "preview".to_string(), + Self::Dependencies => "dependencies".to_string(), + Self::Flow => "flow".to_string(), + Self::Flowdependencies => "flowdependencies".to_string(), + Self::Appdependencies => "appdependencies".to_string(), + Self::Flowpreview => "flowpreview".to_string(), + Self::ScriptHub => "script_hub".to_string(), + Self::Identity => "identity".to_string(), + Self::Deploymentcallback => "deploymentcallback".to_string(), + Self::Singlescriptflow => "singlescriptflow".to_string(), + Self::Flowscript => "flowscript".to_string(), + Self::Flownode => "flownode".to_string(), + Self::Appscript => "appscript".to_string(), + } + } + } + impl std::str::FromStr for CompletedJobJobKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "preview" => Ok(Self::Preview), + "dependencies" => Ok(Self::Dependencies), + "flow" => Ok(Self::Flow), + "flowdependencies" => Ok(Self::Flowdependencies), + "appdependencies" => Ok(Self::Appdependencies), + "flowpreview" => Ok(Self::Flowpreview), + "script_hub" => Ok(Self::ScriptHub), + "identity" => Ok(Self::Identity), + "deploymentcallback" => Ok(Self::Deploymentcallback), + "singlescriptflow" => Ok(Self::Singlescriptflow), + "flowscript" => Ok(Self::Flowscript), + "flownode" => Ok(Self::Flownode), + "appscript" => Ok(Self::Appscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConcurrencyGroup { + pub concurrency_key: String, + pub total_running: f64, + } + impl From<&ConcurrencyGroup> for ConcurrencyGroup { + fn from(value: &ConcurrencyGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Config { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub config: std::collections::HashMap, + pub name: String, + } + impl From<&Config> for Config { + fn from(value: &Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConnectCallbackBody { + pub code: String, + pub state: String, + } + impl From<&ConnectCallbackBody> for ConnectCallbackBody { + fn from(value: &ConnectCallbackBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConnectSlackCallbackBody { + pub code: String, + pub state: String, + } + impl From<&ConnectSlackCallbackBody> for ConnectSlackCallbackBody { + fn from(value: &ConnectSlackCallbackBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConnectSlackCallbackInstanceBody { + pub code: String, + pub state: String, + } + impl From<&ConnectSlackCallbackInstanceBody> for ConnectSlackCallbackInstanceBody { + fn from(value: &ConnectSlackCallbackInstanceBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConnectTeamsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub team_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub team_name: Option, + } + impl From<&ConnectTeamsBody> for ConnectTeamsBody { + fn from(value: &ConnectTeamsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ContextualVariable { + pub description: String, + pub is_custom: bool, + pub name: String, + pub value: String, + } + impl From<&ContextualVariable> for ContextualVariable { + fn from(value: &ContextualVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CountJobsByTagResponseItem { + pub count: i64, + pub tag: String, + } + impl From<&CountJobsByTagResponseItem> for CountJobsByTagResponseItem { + fn from(value: &CountJobsByTagResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CountSearchLogsIndexResponse { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub count_per_host: std::collections::HashMap, + ///a list of the terms that couldn't be parsed (and thus ignored) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub query_parse_errors: Vec, + } + impl From<&CountSearchLogsIndexResponse> for CountSearchLogsIndexResponse { + fn from(value: &CountSearchLogsIndexResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateAccountBody { + pub client: String, + pub expires_in: i64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + } + impl From<&CreateAccountBody> for CreateAccountBody { + fn from(value: &CreateAccountBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateAppBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + pub path: String, + pub policy: Policy, + pub summary: String, + pub value: serde_json::Value, + } + impl From<&CreateAppBody> for CreateAppBody { + fn from(value: &CreateAppBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateDraftBody { + pub path: String, + pub typ: CreateDraftBodyTyp, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&CreateDraftBody> for CreateDraftBody { + fn from(value: &CreateDraftBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum CreateDraftBodyTyp { + #[serde(rename = "flow")] + Flow, + #[serde(rename = "script")] + Script, + #[serde(rename = "app")] + App, + } + impl From<&CreateDraftBodyTyp> for CreateDraftBodyTyp { + fn from(value: &CreateDraftBodyTyp) -> Self { + value.clone() + } + } + impl ToString for CreateDraftBodyTyp { + fn to_string(&self) -> String { + match *self { + Self::Flow => "flow".to_string(), + Self::Script => "script".to_string(), + Self::App => "app".to_string(), + } + } + } + impl std::str::FromStr for CreateDraftBodyTyp { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "flow" => Ok(Self::Flow), + "script" => Ok(Self::Script), + "app" => Ok(Self::App), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for CreateDraftBodyTyp { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for CreateDraftBodyTyp { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for CreateDraftBodyTyp { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateFlowBody { + #[serde(flatten)] + pub open_flow_w_path: OpenFlowWPath, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + } + impl From<&CreateFlowBody> for CreateFlowBody { + fn from(value: &CreateFlowBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateFolderBody { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + pub name: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub owners: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&CreateFolderBody> for CreateFolderBody { + fn from(value: &CreateFolderBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateGroupBody { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&CreateGroupBody> for CreateGroupBody { + fn from(value: &CreateGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateInput { + pub args: std::collections::HashMap, + pub name: String, + } + impl From<&CreateInput> for CreateInput { + fn from(value: &CreateInput) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateInstanceGroupBody { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&CreateInstanceGroupBody> for CreateInstanceGroupBody { + fn from(value: &CreateInstanceGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateRawAppBody { + pub path: String, + pub summary: String, + pub value: String, + } + impl From<&CreateRawAppBody> for CreateRawAppBody { + fn from(value: &CreateRawAppBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + pub path: String, + pub resource_type: String, + pub value: serde_json::Value, + } + impl From<&CreateResource> for CreateResource { + fn from(value: &CreateResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateUserGloballyBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub company: Option, + pub email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + pub password: String, + pub super_admin: bool, + } + impl From<&CreateUserGloballyBody> for CreateUserGloballyBody { + fn from(value: &CreateUserGloballyBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_oauth: Option, + pub is_secret: bool, + pub path: String, + pub value: String, + } + impl From<&CreateVariable> for CreateVariable { + fn from(value: &CreateVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateWorkspace { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + pub id: String, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + } + impl From<&CreateWorkspace> for CreateWorkspace { + fn from(value: &CreateWorkspace) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CriticalAlert { + ///Acknowledgment status of the alert, can be true, false, or null if not set + #[serde(default, skip_serializing_if = "Option::is_none")] + pub acknowledged: Option, + ///Type of alert (e.g., critical_error) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub alert_type: Option, + ///Time when the alert was created + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option>, + ///Unique identifier for the alert + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + ///The message content of the alert + #[serde(default, skip_serializing_if = "Option::is_none")] + pub message: Option, + ///Workspace id if the alert is in the scope of a workspace + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&CriticalAlert> for CriticalAlert { + fn from(value: &CriticalAlert) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DeclineInviteBody { + pub workspace_id: String, + } + impl From<&DeclineInviteBody> for DeclineInviteBody { + fn from(value: &DeclineInviteBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum DeleteDraftKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + } + impl From<&DeleteDraftKind> for DeleteDraftKind { + fn from(value: &DeleteDraftKind) -> Self { + value.clone() + } + } + impl ToString for DeleteDraftKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + } + } + } + impl std::str::FromStr for DeleteDraftKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for DeleteDraftKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for DeleteDraftKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for DeleteDraftKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DeleteInviteBody { + pub email: String, + pub is_admin: bool, + pub operator: bool, + } + impl From<&DeleteInviteBody> for DeleteInviteBody { + fn from(value: &DeleteInviteBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DuckdbConnectionSettingsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource: Option, + } + impl From<&DuckdbConnectionSettingsBody> for DuckdbConnectionSettingsBody { + fn from(value: &DuckdbConnectionSettingsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DuckdbConnectionSettingsResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub connection_settings_str: Option, + } + impl From<&DuckdbConnectionSettingsResponse> for DuckdbConnectionSettingsResponse { + fn from(value: &DuckdbConnectionSettingsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DuckdbConnectionSettingsV2Body { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + } + impl From<&DuckdbConnectionSettingsV2Body> for DuckdbConnectionSettingsV2Body { + fn from(value: &DuckdbConnectionSettingsV2Body) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DuckdbConnectionSettingsV2Response { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub azure_container_path: Option, + pub connection_settings_str: String, + } + impl From<&DuckdbConnectionSettingsV2Response> + for DuckdbConnectionSettingsV2Response { + fn from(value: &DuckdbConnectionSettingsV2Response) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditAutoInviteBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_add: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub invite_all: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator: Option, + } + impl From<&EditAutoInviteBody> for EditAutoInviteBody { + fn from(value: &EditAutoInviteBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditCopilotConfigBody { + pub ai_models: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ai_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_completion_model: Option, + } + impl From<&EditCopilotConfigBody> for EditCopilotConfigBody { + fn from(value: &EditCopilotConfigBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditDeployToBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deploy_to: Option, + } + impl From<&EditDeployToBody> for EditDeployToBody { + fn from(value: &EditDeployToBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditErrorHandlerBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_muted_on_cancel: Option, + } + impl From<&EditErrorHandlerBody> for EditErrorHandlerBody { + fn from(value: &EditErrorHandlerBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditHttpTrigger { + pub http_method: EditHttpTriggerHttpMethod, + pub is_async: bool, + pub is_flow: bool, + pub is_static_website: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_string: Option, + pub requires_auth: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub route_path: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wrap_body: Option, + } + impl From<&EditHttpTrigger> for EditHttpTrigger { + fn from(value: &EditHttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum EditHttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&EditHttpTriggerHttpMethod> for EditHttpTriggerHttpMethod { + fn from(value: &EditHttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for EditHttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for EditHttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditHttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&EditHttpTriggerStaticAssetConfig> for EditHttpTriggerStaticAssetConfig { + fn from(value: &EditHttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditKafkaTrigger { + pub group_id: String, + pub is_flow: bool, + pub kafka_resource_path: String, + pub path: String, + pub script_path: String, + pub topics: Vec, + } + impl From<&EditKafkaTrigger> for EditKafkaTrigger { + fn from(value: &EditKafkaTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditLargeFileStorageConfigBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub large_file_storage: Option, + } + impl From<&EditLargeFileStorageConfigBody> for EditLargeFileStorageConfigBody { + fn from(value: &EditLargeFileStorageConfigBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditMqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + pub enabled: bool, + pub is_flow: bool, + pub mqtt_resource_path: String, + pub path: String, + pub script_path: String, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&EditMqttTrigger> for EditMqttTrigger { + fn from(value: &EditMqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditNatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + pub is_flow: bool, + pub nats_resource_path: String, + pub path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&EditNatsTrigger> for EditNatsTrigger { + fn from(value: &EditNatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditPostgresTrigger { + pub enabled: bool, + pub is_flow: bool, + pub path: String, + pub postgres_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication: Option, + pub publication_name: String, + pub replication_slot_name: String, + pub script_path: String, + } + impl From<&EditPostgresTrigger> for EditPostgresTrigger { + fn from(value: &EditPostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&EditResource> for EditResource { + fn from(value: &EditResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditResourceType { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + } + impl From<&EditResourceType> for EditResourceType { + fn from(value: &EditResourceType) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditSchedule { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&EditSchedule> for EditSchedule { + fn from(value: &EditSchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditSlackCommandBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_command_script: Option, + } + impl From<&EditSlackCommandBody> for EditSlackCommandBody { + fn from(value: &EditSlackCommandBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditSqsTrigger { + pub aws_resource_path: String, + pub enabled: bool, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub path: String, + pub queue_url: String, + pub script_path: String, + } + impl From<&EditSqsTrigger> for EditSqsTrigger { + fn from(value: &EditSqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditTeamsCommandBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_command_script: Option, + } + impl From<&EditTeamsCommandBody> for EditTeamsCommandBody { + fn from(value: &EditTeamsCommandBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_secret: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&EditVariable> for EditVariable { + fn from(value: &EditVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWebhookBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub webhook: Option, + } + impl From<&EditWebhookBody> for EditWebhookBody { + fn from(value: &EditWebhookBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWebsocketTrigger { + pub can_return_message: bool, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&EditWebsocketTrigger> for EditWebsocketTrigger { + fn from(value: &EditWebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&EditWebsocketTriggerFiltersItem> for EditWebsocketTriggerFiltersItem { + fn from(value: &EditWebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWorkspaceDefaultAppBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_app_path: Option, + } + impl From<&EditWorkspaceDefaultAppBody> for EditWorkspaceDefaultAppBody { + fn from(value: &EditWorkspaceDefaultAppBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWorkspaceDeployUiSettingsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deploy_ui_settings: Option, + } + impl From<&EditWorkspaceDeployUiSettingsBody> for EditWorkspaceDeployUiSettingsBody { + fn from(value: &EditWorkspaceDeployUiSettingsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWorkspaceGitSyncConfigBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub git_sync_settings: Option, + } + impl From<&EditWorkspaceGitSyncConfigBody> for EditWorkspaceGitSyncConfigBody { + fn from(value: &EditWorkspaceGitSyncConfigBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWorkspaceUser { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_admin: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator: Option, + } + impl From<&EditWorkspaceUser> for EditWorkspaceUser { + fn from(value: &EditWorkspaceUser) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExecuteComponentBody { + pub args: serde_json::Value, + pub component: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub force_viewer_allow_user_resources: Vec, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub force_viewer_one_of_fields: std::collections::HashMap< + String, + serde_json::Value, + >, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub force_viewer_static_fields: std::collections::HashMap< + String, + serde_json::Value, + >, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version: Option, + } + impl From<&ExecuteComponentBody> for ExecuteComponentBody { + fn from(value: &ExecuteComponentBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExecuteComponentBodyRawCode { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + pub content: String, + pub language: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + } + impl From<&ExecuteComponentBodyRawCode> for ExecuteComponentBodyRawCode { + fn from(value: &ExecuteComponentBodyRawCode) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExistsRouteBody { + pub http_method: ExistsRouteBodyHttpMethod, + pub route_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trigger_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + } + impl From<&ExistsRouteBody> for ExistsRouteBody { + fn from(value: &ExistsRouteBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ExistsRouteBodyHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&ExistsRouteBodyHttpMethod> for ExistsRouteBodyHttpMethod { + fn from(value: &ExistsRouteBodyHttpMethod) -> Self { + value.clone() + } + } + impl ToString for ExistsRouteBodyHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for ExistsRouteBodyHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ExistsRouteBodyHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ExistsRouteBodyHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ExistsRouteBodyHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExistsUsernameBody { + pub id: String, + pub username: String, + } + impl From<&ExistsUsernameBody> for ExistsUsernameBody { + fn from(value: &ExistsUsernameBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExistsWorkspaceBody { + pub id: String, + } + impl From<&ExistsWorkspaceBody> for ExistsWorkspaceBody { + fn from(value: &ExistsWorkspaceBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExportedInstanceGroup { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub emails: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scim_display_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&ExportedInstanceGroup> for ExportedInstanceGroup { + fn from(value: &ExportedInstanceGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExportedUser { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub company: Option, + pub email: String, + pub first_time_user: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub password_hash: Option, + pub super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + pub verified: bool, + } + impl From<&ExportedUser> for ExportedUser { + fn from(value: &ExportedUser) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExtendedJobs { + pub jobs: Vec, + pub obscured_jobs: Vec, + ///Obscured jobs omitted for security because of too specific filtering + #[serde(default, skip_serializing_if = "Option::is_none")] + pub omitted_obscured_jobs: Option, + } + impl From<&ExtendedJobs> for ExtendedJobs { + fn from(value: &ExtendedJobs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExtraPerms(pub std::collections::HashMap); + impl std::ops::Deref for ExtraPerms { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From for std::collections::HashMap { + fn from(value: ExtraPerms) -> Self { + value.0 + } + } + impl From<&ExtraPerms> for ExtraPerms { + fn from(value: &ExtraPerms) -> Self { + value.clone() + } + } + impl From> for ExtraPerms { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FileUploadResponse { + pub file_key: String, + } + impl From<&FileUploadResponse> for FileUploadResponse { + fn from(value: &FileUploadResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Flow { + #[serde(flatten)] + pub open_flow: OpenFlow, + #[serde(flatten)] + pub flow_metadata: FlowMetadata, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + } + impl From<&Flow> for Flow { + fn from(value: &Flow) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowMetadata { + pub archived: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub extra_perms: ExtraPerms, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&FlowMetadata> for FlowMetadata { + fn from(value: &FlowMetadata) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub continue_on_error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sleep: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop_after_all_iters_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop_after_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub suspend: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + pub value: FlowModuleValue, + } + impl From<&FlowModule> for FlowModule { + fn from(value: &FlowModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleMock { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub return_value: Option, + } + impl From<&FlowModuleMock> for FlowModuleMock { + fn from(value: &FlowModuleMock) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSkipIf { + pub expr: String, + } + impl From<&FlowModuleSkipIf> for FlowModuleSkipIf { + fn from(value: &FlowModuleSkipIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleStopAfterAllItersIf { + pub expr: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if_stopped: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_message: Option + } + impl From<&FlowModuleStopAfterAllItersIf> for FlowModuleStopAfterAllItersIf { + fn from(value: &FlowModuleStopAfterAllItersIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleStopAfterIf { + pub expr: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if_stopped: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_message: Option + } + impl From<&FlowModuleStopAfterIf> for FlowModuleStopAfterIf { + fn from(value: &FlowModuleStopAfterIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSuspend { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub continue_on_disapprove_timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hide_cancel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub required_events: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resume_form: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_approval_disabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_auth_required: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_groups_required: Option, + } + impl From<&FlowModuleSuspend> for FlowModuleSuspend { + fn from(value: &FlowModuleSuspend) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSuspendResumeForm { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + } + impl From<&FlowModuleSuspendResumeForm> for FlowModuleSuspendResumeForm { + fn from(value: &FlowModuleSuspendResumeForm) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum FlowModuleValue { + RawScript(RawScript), + PathScript(PathScript), + PathFlow(PathFlow), + ForloopFlow(ForloopFlow), + WhileloopFlow(WhileloopFlow), + BranchOne(BranchOne), + BranchAll(BranchAll), + Identity(Identity), + } + impl From<&FlowModuleValue> for FlowModuleValue { + fn from(value: &FlowModuleValue) -> Self { + value.clone() + } + } + impl From for FlowModuleValue { + fn from(value: RawScript) -> Self { + Self::RawScript(value) + } + } + impl From for FlowModuleValue { + fn from(value: PathScript) -> Self { + Self::PathScript(value) + } + } + impl From for FlowModuleValue { + fn from(value: PathFlow) -> Self { + Self::PathFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: ForloopFlow) -> Self { + Self::ForloopFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: WhileloopFlow) -> Self { + Self::WhileloopFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: BranchOne) -> Self { + Self::BranchOne(value) + } + } + impl From for FlowModuleValue { + fn from(value: BranchAll) -> Self { + Self::BranchAll(value) + } + } + impl From for FlowModuleValue { + fn from(value: Identity) -> Self { + Self::Identity(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowPreview { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restarted_from: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub value: FlowValue, + } + impl From<&FlowPreview> for FlowPreview { + fn from(value: &FlowPreview) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatus { + pub failure_module: FlowStatusFailureModule, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessor_module: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub step: i64, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub user_states: std::collections::HashMap, + } + impl From<&FlowStatus> for FlowStatus { + fn from(value: &FlowStatus) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusFailureModule { + #[serde(flatten)] + pub flow_status_module: FlowStatusModule, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_module: Option, + } + impl From<&FlowStatusFailureModule> for FlowStatusFailureModule { + fn from(value: &FlowStatusFailureModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModule { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub approvers: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch_chosen: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branchall: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub count: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub failed_retries: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub flow_jobs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub flow_jobs_success: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub iterator: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub job: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub progress: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skipped: Option, + #[serde(rename = "type")] + pub type_: FlowStatusModuleType, + } + impl From<&FlowStatusModule> for FlowStatusModule { + fn from(value: &FlowStatusModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleApproversItem { + pub approver: String, + pub resume_id: i64, + } + impl From<&FlowStatusModuleApproversItem> for FlowStatusModuleApproversItem { + fn from(value: &FlowStatusModuleApproversItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleBranchChosen { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde(rename = "type")] + pub type_: FlowStatusModuleBranchChosenType, + } + impl From<&FlowStatusModuleBranchChosen> for FlowStatusModuleBranchChosen { + fn from(value: &FlowStatusModuleBranchChosen) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum FlowStatusModuleBranchChosenType { + #[serde(rename = "branch")] + Branch, + #[serde(rename = "default")] + Default, + } + impl From<&FlowStatusModuleBranchChosenType> for FlowStatusModuleBranchChosenType { + fn from(value: &FlowStatusModuleBranchChosenType) -> Self { + value.clone() + } + } + impl ToString for FlowStatusModuleBranchChosenType { + fn to_string(&self) -> String { + match *self { + Self::Branch => "branch".to_string(), + Self::Default => "default".to_string(), + } + } + } + impl std::str::FromStr for FlowStatusModuleBranchChosenType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branch" => Ok(Self::Branch), + "default" => Ok(Self::Default), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleBranchall { + pub branch: i64, + pub len: i64, + } + impl From<&FlowStatusModuleBranchall> for FlowStatusModuleBranchall { + fn from(value: &FlowStatusModuleBranchall) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleIterator { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub index: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub itered: Vec, + } + impl From<&FlowStatusModuleIterator> for FlowStatusModuleIterator { + fn from(value: &FlowStatusModuleIterator) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum FlowStatusModuleType { + WaitingForPriorSteps, + WaitingForEvents, + WaitingForExecutor, + InProgress, + Success, + Failure, + } + impl From<&FlowStatusModuleType> for FlowStatusModuleType { + fn from(value: &FlowStatusModuleType) -> Self { + value.clone() + } + } + impl ToString for FlowStatusModuleType { + fn to_string(&self) -> String { + match *self { + Self::WaitingForPriorSteps => "WaitingForPriorSteps".to_string(), + Self::WaitingForEvents => "WaitingForEvents".to_string(), + Self::WaitingForExecutor => "WaitingForExecutor".to_string(), + Self::InProgress => "InProgress".to_string(), + Self::Success => "Success".to_string(), + Self::Failure => "Failure".to_string(), + } + } + } + impl std::str::FromStr for FlowStatusModuleType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "WaitingForPriorSteps" => Ok(Self::WaitingForPriorSteps), + "WaitingForEvents" => Ok(Self::WaitingForEvents), + "WaitingForExecutor" => Ok(Self::WaitingForExecutor), + "InProgress" => Ok(Self::InProgress), + "Success" => Ok(Self::Success), + "Failure" => Ok(Self::Failure), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusRetry { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fail_count: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub failed_jobs: Vec, + } + impl From<&FlowStatusRetry> for FlowStatusRetry { + fn from(value: &FlowStatusRetry) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowValue { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub early_return: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub failure_module: Option, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessor_module: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub same_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_expr: Option, + } + impl From<&FlowValue> for FlowValue { + fn from(value: &FlowValue) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowVersion { + pub created_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub id: i64, + } + impl From<&FlowVersion> for FlowVersion { + fn from(value: &FlowVersion) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Folder { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + pub extra_perms: std::collections::HashMap, + pub name: String, + pub owners: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&Folder> for Folder { + fn from(value: &Folder) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ForceCancelQueuedJobBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + } + impl From<&ForceCancelQueuedJobBody> for ForceCancelQueuedJobBody { + fn from(value: &ForceCancelQueuedJobBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ForloopFlow { + pub iterator: InputTransform, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + pub skip_failures: bool, + #[serde(rename = "type")] + pub type_: ForloopFlowType, + } + impl From<&ForloopFlow> for ForloopFlow { + fn from(value: &ForloopFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ForloopFlowType { + #[serde(rename = "forloopflow")] + Forloopflow, + } + impl From<&ForloopFlowType> for ForloopFlowType { + fn from(value: &ForloopFlowType) -> Self { + value.clone() + } + } + impl ToString for ForloopFlowType { + fn to_string(&self) -> String { + match *self { + Self::Forloopflow => "forloopflow".to_string(), + } + } + } + impl std::str::FromStr for ForloopFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "forloopflow" => Ok(Self::Forloopflow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ForloopFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ForloopFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ForloopFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GetCaptureConfigsRunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&GetCaptureConfigsRunnableKind> for GetCaptureConfigsRunnableKind { + fn from(value: &GetCaptureConfigsRunnableKind) -> Self { + value.clone() + } + } + impl ToString for GetCaptureConfigsRunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for GetCaptureConfigsRunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GetCaptureConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for GetCaptureConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for GetCaptureConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetCompletedCountResponse { + pub database_length: i64, + } + impl From<&GetCompletedCountResponse> for GetCompletedCountResponse { + fn from(value: &GetCompletedCountResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetCompletedJobResultMaybeResponse { + pub completed: bool, + pub result: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub success: Option, + } + impl From<&GetCompletedJobResultMaybeResponse> + for GetCompletedJobResultMaybeResponse { + fn from(value: &GetCompletedJobResultMaybeResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetCriticalAlertsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub alerts: Vec, + ///Total number of pages based on the page size. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub total_pages: Option, + ///Total number of rows matching the query. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub total_rows: Option, + } + impl From<&GetCriticalAlertsResponse> for GetCriticalAlertsResponse { + fn from(value: &GetCriticalAlertsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetDeployToResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deploy_to: Option, + } + impl From<&GetDeployToResponse> for GetDeployToResponse { + fn from(value: &GetDeployToResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetFlowByPathWithDraftResponse { + #[serde(flatten)] + pub flow: Flow, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft: Option, + } + impl From<&GetFlowByPathWithDraftResponse> for GetFlowByPathWithDraftResponse { + fn from(value: &GetFlowByPathWithDraftResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetFlowDeploymentStatusResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + } + impl From<&GetFlowDeploymentStatusResponse> for GetFlowDeploymentStatusResponse { + fn from(value: &GetFlowDeploymentStatusResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetFolderUsageResponse { + pub apps: f64, + pub flows: f64, + pub resources: f64, + pub schedules: f64, + pub scripts: f64, + pub variables: f64, + } + impl From<&GetFolderUsageResponse> for GetFolderUsageResponse { + fn from(value: &GetFolderUsageResponse) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GetGranularAclsKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "group_")] + Group, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "app")] + App, + #[serde(rename = "raw_app")] + RawApp, + #[serde(rename = "http_trigger")] + HttpTrigger, + #[serde(rename = "websocket_trigger")] + WebsocketTrigger, + #[serde(rename = "kafka_trigger")] + KafkaTrigger, + #[serde(rename = "nats_trigger")] + NatsTrigger, + #[serde(rename = "postgres_trigger")] + PostgresTrigger, + #[serde(rename = "mqtt_trigger")] + MqttTrigger, + #[serde(rename = "sqs_trigger")] + SqsTrigger, + } + impl From<&GetGranularAclsKind> for GetGranularAclsKind { + fn from(value: &GetGranularAclsKind) -> Self { + value.clone() + } + } + impl ToString for GetGranularAclsKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Group => "group_".to_string(), + Self::Resource => "resource".to_string(), + Self::Schedule => "schedule".to_string(), + Self::Variable => "variable".to_string(), + Self::Flow => "flow".to_string(), + Self::Folder => "folder".to_string(), + Self::App => "app".to_string(), + Self::RawApp => "raw_app".to_string(), + Self::HttpTrigger => "http_trigger".to_string(), + Self::WebsocketTrigger => "websocket_trigger".to_string(), + Self::KafkaTrigger => "kafka_trigger".to_string(), + Self::NatsTrigger => "nats_trigger".to_string(), + Self::PostgresTrigger => "postgres_trigger".to_string(), + Self::MqttTrigger => "mqtt_trigger".to_string(), + Self::SqsTrigger => "sqs_trigger".to_string(), + } + } + } + impl std::str::FromStr for GetGranularAclsKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "group_" => Ok(Self::Group), + "resource" => Ok(Self::Resource), + "schedule" => Ok(Self::Schedule), + "variable" => Ok(Self::Variable), + "flow" => Ok(Self::Flow), + "folder" => Ok(Self::Folder), + "app" => Ok(Self::App), + "raw_app" => Ok(Self::RawApp), + "http_trigger" => Ok(Self::HttpTrigger), + "websocket_trigger" => Ok(Self::WebsocketTrigger), + "kafka_trigger" => Ok(Self::KafkaTrigger), + "nats_trigger" => Ok(Self::NatsTrigger), + "postgres_trigger" => Ok(Self::PostgresTrigger), + "mqtt_trigger" => Ok(Self::MqttTrigger), + "sqs_trigger" => Ok(Self::SqsTrigger), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GetGranularAclsKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for GetGranularAclsKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for GetGranularAclsKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetHubAppByIdResponse { + pub app: GetHubAppByIdResponseApp, + } + impl From<&GetHubAppByIdResponse> for GetHubAppByIdResponse { + fn from(value: &GetHubAppByIdResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetHubAppByIdResponseApp { + pub summary: String, + pub value: serde_json::Value, + } + impl From<&GetHubAppByIdResponseApp> for GetHubAppByIdResponseApp { + fn from(value: &GetHubAppByIdResponseApp) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetHubFlowByIdResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow: Option, + } + impl From<&GetHubFlowByIdResponse> for GetHubFlowByIdResponse { + fn from(value: &GetHubFlowByIdResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetHubScriptByPathResponse { + pub content: String, + pub language: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lockfile: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&GetHubScriptByPathResponse> for GetHubScriptByPathResponse { + fn from(value: &GetHubScriptByPathResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetJobMetricsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub from_timestamp: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeseries_max_datapoints: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub to_timestamp: Option>, + } + impl From<&GetJobMetricsBody> for GetJobMetricsBody { + fn from(value: &GetJobMetricsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetJobMetricsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub metrics_metadata: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scalar_metrics: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub timeseries_metrics: Vec, + } + impl From<&GetJobMetricsResponse> for GetJobMetricsResponse { + fn from(value: &GetJobMetricsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetJobUpdatesResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub completed: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_status: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub log_offset: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mem_peak: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub new_logs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub progress: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub running: Option, + } + impl From<&GetJobUpdatesResponse> for GetJobUpdatesResponse { + fn from(value: &GetJobUpdatesResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetLatestKeyRenewalAttemptResponse { + pub attempted_at: chrono::DateTime, + pub result: String, + } + impl From<&GetLatestKeyRenewalAttemptResponse> + for GetLatestKeyRenewalAttemptResponse { + fn from(value: &GetLatestKeyRenewalAttemptResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetOAuthConnectResponse { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_params: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scopes: Vec, + } + impl From<&GetOAuthConnectResponse> for GetOAuthConnectResponse { + fn from(value: &GetOAuthConnectResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetPremiumInfoResponse { + pub automatic_billing: bool, + pub owner: String, + pub premium: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seats: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub usage: Option, + } + impl From<&GetPremiumInfoResponse> for GetPremiumInfoResponse { + fn from(value: &GetPremiumInfoResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetQueueCountResponse { + pub database_length: i64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub suspended: Option, + } + impl From<&GetQueueCountResponse> for GetQueueCountResponse { + fn from(value: &GetQueueCountResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetQueueMetricsResponseItem { + pub id: String, + pub values: Vec, + } + impl From<&GetQueueMetricsResponseItem> for GetQueueMetricsResponseItem { + fn from(value: &GetQueueMetricsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetQueueMetricsResponseItemValuesItem { + pub created_at: String, + pub value: f64, + } + impl From<&GetQueueMetricsResponseItemValuesItem> + for GetQueueMetricsResponseItemValuesItem { + fn from(value: &GetQueueMetricsResponseItemValuesItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetResumeUrlsResponse { + #[serde(rename = "approvalPage")] + pub approval_page: String, + pub cancel: String, + pub resume: String, + } + impl From<&GetResumeUrlsResponse> for GetResumeUrlsResponse { + fn from(value: &GetResumeUrlsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetRunnableResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + pub endpoint_async: String, + pub endpoint_openai_sync: String, + pub endpoint_sync: String, + pub kind: String, + pub summary: String, + pub workspace: String, + } + impl From<&GetRunnableResponse> for GetRunnableResponse { + fn from(value: &GetRunnableResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetScriptDeploymentStatusResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + } + impl From<&GetScriptDeploymentStatusResponse> for GetScriptDeploymentStatusResponse { + fn from(value: &GetScriptDeploymentStatusResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetSettingsResponse { + pub ai_models: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ai_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_add: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_invite_domain: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_invite_operator: Option, + pub automatic_billing: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_completion_model: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub customer_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_app: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_scripts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deploy_to: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deploy_ui: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_extra_args: Option, + pub error_handler_muted_on_cancel: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub git_sync: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub large_file_storage: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mute_critical_alerts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator_settings: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plan: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_command_script: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_team_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub teams_command_script: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub teams_team_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub teams_team_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub webhook: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&GetSettingsResponse> for GetSettingsResponse { + fn from(value: &GetSettingsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetSuspendedJobFlowResponse { + pub approvers: Vec, + pub job: Job, + } + impl From<&GetSuspendedJobFlowResponse> for GetSuspendedJobFlowResponse { + fn from(value: &GetSuspendedJobFlowResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetSuspendedJobFlowResponseApproversItem { + pub approver: String, + pub resume_id: i64, + } + impl From<&GetSuspendedJobFlowResponseApproversItem> + for GetSuspendedJobFlowResponseApproversItem { + fn from(value: &GetSuspendedJobFlowResponseApproversItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetThresholdAlertResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_alert_sent: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub threshold_alert_amount: Option, + } + impl From<&GetThresholdAlertResponse> for GetThresholdAlertResponse { + fn from(value: &GetThresholdAlertResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetTopHubScriptsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub asks: Vec, + } + impl From<&GetTopHubScriptsResponse> for GetTopHubScriptsResponse { + fn from(value: &GetTopHubScriptsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetTopHubScriptsResponseAsksItem { + pub app: String, + pub ask_id: f64, + pub id: f64, + pub kind: HubScriptKind, + pub summary: String, + pub version_id: f64, + pub views: f64, + pub votes: f64, + } + impl From<&GetTopHubScriptsResponseAsksItem> for GetTopHubScriptsResponseAsksItem { + fn from(value: &GetTopHubScriptsResponseAsksItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetTutorialProgressResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub progress: Option, + } + impl From<&GetTutorialProgressResponse> for GetTutorialProgressResponse { + fn from(value: &GetTutorialProgressResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetUsedTriggersResponse { + pub http_routes_used: bool, + pub kafka_used: bool, + pub mqtt_used: bool, + pub nats_used: bool, + pub postgres_used: bool, + pub sqs_used: bool, + pub websocket_used: bool, + } + impl From<&GetUsedTriggersResponse> for GetUsedTriggersResponse { + fn from(value: &GetUsedTriggersResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetWorkspaceDefaultAppResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_app_path: Option, + } + impl From<&GetWorkspaceDefaultAppResponse> for GetWorkspaceDefaultAppResponse { + fn from(value: &GetWorkspaceDefaultAppResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetWorkspaceEncryptionKeyResponse { + pub key: String, + } + impl From<&GetWorkspaceEncryptionKeyResponse> for GetWorkspaceEncryptionKeyResponse { + fn from(value: &GetWorkspaceEncryptionKeyResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GitRepositorySettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exclude_types_override: Vec, + pub git_repo_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group_by_folder: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub use_individual_branch: Option, + } + impl From<&GitRepositorySettings> for GitRepositorySettings { + fn from(value: &GitRepositorySettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GitRepositorySettingsExcludeTypesOverrideItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "resourcetype")] + Resourcetype, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "user")] + User, + #[serde(rename = "group")] + Group, + } + impl From<&GitRepositorySettingsExcludeTypesOverrideItem> + for GitRepositorySettingsExcludeTypesOverrideItem { + fn from(value: &GitRepositorySettingsExcludeTypesOverrideItem) -> Self { + value.clone() + } + } + impl ToString for GitRepositorySettingsExcludeTypesOverrideItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Folder => "folder".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Resourcetype => "resourcetype".to_string(), + Self::Schedule => "schedule".to_string(), + Self::User => "user".to_string(), + Self::Group => "group".to_string(), + } + } + } + impl std::str::FromStr for GitRepositorySettingsExcludeTypesOverrideItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "folder" => Ok(Self::Folder), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "resourcetype" => Ok(Self::Resourcetype), + "schedule" => Ok(Self::Schedule), + "user" => Ok(Self::User), + "group" => Ok(Self::Group), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> + for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom + for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalSetting { + pub name: String, + pub value: std::collections::HashMap, + } + impl From<&GlobalSetting> for GlobalSetting { + fn from(value: &GlobalSetting) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUserInfo { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub company: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub devops: Option, + pub email: String, + pub login_type: GlobalUserInfoLoginType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator_only: Option, + pub super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + pub verified: bool, + } + impl From<&GlobalUserInfo> for GlobalUserInfo { + fn from(value: &GlobalUserInfo) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GlobalUserInfoLoginType { + #[serde(rename = "password")] + Password, + #[serde(rename = "github")] + Github, + } + impl From<&GlobalUserInfoLoginType> for GlobalUserInfoLoginType { + fn from(value: &GlobalUserInfoLoginType) -> Self { + value.clone() + } + } + impl ToString for GlobalUserInfoLoginType { + fn to_string(&self) -> String { + match *self { + Self::Password => "password".to_string(), + Self::Github => "github".to_string(), + } + } + } + impl std::str::FromStr for GlobalUserInfoLoginType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "password" => Ok(Self::Password), + "github" => Ok(Self::Github), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUserRenameBody { + pub new_username: String, + } + impl From<&GlobalUserRenameBody> for GlobalUserRenameBody { + fn from(value: &GlobalUserRenameBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUserUpdateBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_devops: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_super_admin: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + impl From<&GlobalUserUpdateBody> for GlobalUserUpdateBody { + fn from(value: &GlobalUserUpdateBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUsernameInfoResponse { + pub username: String, + pub workspace_usernames: Vec, + } + impl From<&GlobalUsernameInfoResponse> for GlobalUsernameInfoResponse { + fn from(value: &GlobalUsernameInfoResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUsernameInfoResponseWorkspaceUsernamesItem { + pub username: String, + pub workspace_id: String, + } + impl From<&GlobalUsernameInfoResponseWorkspaceUsernamesItem> + for GlobalUsernameInfoResponseWorkspaceUsernamesItem { + fn from(value: &GlobalUsernameInfoResponseWorkspaceUsernamesItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Group { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub members: Vec, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&Group> for Group { + fn from(value: &Group) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HttpTrigger { + pub http_method: HttpTriggerHttpMethod, + pub is_async: bool, + pub is_static_website: bool, + pub raw_string: bool, + pub requires_auth: bool, + pub route_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + pub workspaced_route: bool, + pub wrap_body: bool, + } + impl From<&HttpTrigger> for HttpTrigger { + fn from(value: &HttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum HttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&HttpTriggerHttpMethod> for HttpTriggerHttpMethod { + fn from(value: &HttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for HttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for HttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&HttpTriggerStaticAssetConfig> for HttpTriggerStaticAssetConfig { + fn from(value: &HttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HubScriptKind(pub serde_json::Value); + impl std::ops::Deref for HubScriptKind { + type Target = serde_json::Value; + fn deref(&self) -> &serde_json::Value { + &self.0 + } + } + impl From for serde_json::Value { + fn from(value: HubScriptKind) -> Self { + value.0 + } + } + impl From<&HubScriptKind> for HubScriptKind { + fn from(value: &HubScriptKind) -> Self { + value.clone() + } + } + impl From for HubScriptKind { + fn from(value: serde_json::Value) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Identity { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow: Option, + #[serde(rename = "type")] + pub type_: IdentityType, + } + impl From<&Identity> for Identity { + fn from(value: &Identity) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum IdentityType { + #[serde(rename = "identity")] + Identity, + } + impl From<&IdentityType> for IdentityType { + fn from(value: &IdentityType) -> Self { + value.clone() + } + } + impl ToString for IdentityType { + fn to_string(&self) -> String { + match *self { + Self::Identity => "identity".to_string(), + } + } + } + impl std::str::FromStr for IdentityType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "identity" => Ok(Self::Identity), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for IdentityType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for IdentityType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for IdentityType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Input { + pub created_at: chrono::DateTime, + pub created_by: String, + pub id: String, + pub is_public: bool, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub success: Option, + } + impl From<&Input> for Input { + fn from(value: &Input) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum InputTransform { + StaticTransform(StaticTransform), + JavascriptTransform(JavascriptTransform), + } + impl From<&InputTransform> for InputTransform { + fn from(value: &InputTransform) -> Self { + value.clone() + } + } + impl From for InputTransform { + fn from(value: StaticTransform) -> Self { + Self::StaticTransform(value) + } + } + impl From for InputTransform { + fn from(value: JavascriptTransform) -> Self { + Self::JavascriptTransform(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceGroup { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub emails: Vec, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&InstanceGroup> for InstanceGroup { + fn from(value: &InstanceGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InviteUserBody { + pub email: String, + pub is_admin: bool, + pub operator: bool, + } + impl From<&InviteUserBody> for InviteUserBody { + fn from(value: &InviteUserBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JavascriptTransform { + pub expr: String, + #[serde(rename = "type")] + pub type_: JavascriptTransformType, + } + impl From<&JavascriptTransform> for JavascriptTransform { + fn from(value: &JavascriptTransform) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JavascriptTransformType { + #[serde(rename = "javascript")] + Javascript, + } + impl From<&JavascriptTransformType> for JavascriptTransformType { + fn from(value: &JavascriptTransformType) -> Self { + value.clone() + } + } + impl ToString for JavascriptTransformType { + fn to_string(&self) -> String { + match *self { + Self::Javascript => "javascript".to_string(), + } + } + } + impl std::str::FromStr for JavascriptTransformType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "javascript" => Ok(Self::Javascript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum Job { + Variant0(JobVariant0), + Variant1(JobVariant1), + } + impl From<&Job> for Job { + fn from(value: &Job) -> Self { + value.clone() + } + } + impl From for Job { + fn from(value: JobVariant0) -> Self { + Self::Variant0(value) + } + } + impl From for Job { + fn from(value: JobVariant1) -> Self { + Self::Variant1(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobSearchHit { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dancer: Option, + } + impl From<&JobSearchHit> for JobSearchHit { + fn from(value: &JobSearchHit) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobVariant0 { + #[serde(flatten)] + pub completed_job: CompletedJob, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&JobVariant0> for JobVariant0 { + fn from(value: &JobVariant0) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JobVariant0Type { + CompletedJob, + } + impl From<&JobVariant0Type> for JobVariant0Type { + fn from(value: &JobVariant0Type) -> Self { + value.clone() + } + } + impl ToString for JobVariant0Type { + fn to_string(&self) -> String { + match *self { + Self::CompletedJob => "CompletedJob".to_string(), + } + } + } + impl std::str::FromStr for JobVariant0Type { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "CompletedJob" => Ok(Self::CompletedJob), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JobVariant0Type { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JobVariant0Type { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JobVariant0Type { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobVariant1 { + #[serde(flatten)] + pub queued_job: QueuedJob, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&JobVariant1> for JobVariant1 { + fn from(value: &JobVariant1) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JobVariant1Type { + QueuedJob, + } + impl From<&JobVariant1Type> for JobVariant1Type { + fn from(value: &JobVariant1Type) -> Self { + value.clone() + } + } + impl ToString for JobVariant1Type { + fn to_string(&self) -> String { + match *self { + Self::QueuedJob => "QueuedJob".to_string(), + } + } + } + impl std::str::FromStr for JobVariant1Type { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "QueuedJob" => Ok(Self::QueuedJob), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JobVariant1Type { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JobVariant1Type { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JobVariant1Type { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct KafkaTrigger { + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub group_id: String, + pub kafka_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub topics: Vec, + } + impl From<&KafkaTrigger> for KafkaTrigger { + fn from(value: &KafkaTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum Language { + Typescript, + } + impl From<&Language> for Language { + fn from(value: &Language) -> Self { + value.clone() + } + } + impl ToString for Language { + fn to_string(&self) -> String { + match *self { + Self::Typescript => "Typescript".to_string(), + } + } + } + impl std::str::FromStr for Language { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Typescript" => Ok(Self::Typescript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for Language { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for Language { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for Language { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LargeFileStorage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub azure_blob_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub public_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub secondary_storage: std::collections::HashMap< + String, + LargeFileStorageSecondaryStorageValue, + >, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&LargeFileStorage> for LargeFileStorage { + fn from(value: &LargeFileStorage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LargeFileStorageSecondaryStorageValue { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub azure_blob_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub public_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&LargeFileStorageSecondaryStorageValue> + for LargeFileStorageSecondaryStorageValue { + fn from(value: &LargeFileStorageSecondaryStorageValue) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum LargeFileStorageSecondaryStorageValueType { + S3Storage, + AzureBlobStorage, + AzureWorkloadIdentity, + S3AwsOidc, + } + impl From<&LargeFileStorageSecondaryStorageValueType> + for LargeFileStorageSecondaryStorageValueType { + fn from(value: &LargeFileStorageSecondaryStorageValueType) -> Self { + value.clone() + } + } + impl ToString for LargeFileStorageSecondaryStorageValueType { + fn to_string(&self) -> String { + match *self { + Self::S3Storage => "S3Storage".to_string(), + Self::AzureBlobStorage => "AzureBlobStorage".to_string(), + Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), + Self::S3AwsOidc => "S3AwsOidc".to_string(), + } + } + } + impl std::str::FromStr for LargeFileStorageSecondaryStorageValueType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "S3Storage" => Ok(Self::S3Storage), + "AzureBlobStorage" => Ok(Self::AzureBlobStorage), + "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), + "S3AwsOidc" => Ok(Self::S3AwsOidc), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum LargeFileStorageType { + S3Storage, + AzureBlobStorage, + AzureWorkloadIdentity, + S3AwsOidc, + } + impl From<&LargeFileStorageType> for LargeFileStorageType { + fn from(value: &LargeFileStorageType) -> Self { + value.clone() + } + } + impl ToString for LargeFileStorageType { + fn to_string(&self) -> String { + match *self { + Self::S3Storage => "S3Storage".to_string(), + Self::AzureBlobStorage => "AzureBlobStorage".to_string(), + Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), + Self::S3AwsOidc => "S3AwsOidc".to_string(), + } + } + } + impl std::str::FromStr for LargeFileStorageType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "S3Storage" => Ok(Self::S3Storage), + "AzureBlobStorage" => Ok(Self::AzureBlobStorage), + "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), + "S3AwsOidc" => Ok(Self::S3AwsOidc), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ListAuditLogsActionKind { + Create, + Update, + Delete, + Execute, + } + impl From<&ListAuditLogsActionKind> for ListAuditLogsActionKind { + fn from(value: &ListAuditLogsActionKind) -> Self { + value.clone() + } + } + impl ToString for ListAuditLogsActionKind { + fn to_string(&self) -> String { + match *self { + Self::Create => "Create".to_string(), + Self::Update => "Update".to_string(), + Self::Delete => "Delete".to_string(), + Self::Execute => "Execute".to_string(), + } + } + } + impl std::str::FromStr for ListAuditLogsActionKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Create" => Ok(Self::Create), + "Update" => Ok(Self::Update), + "Delete" => Ok(Self::Delete), + "Execute" => Ok(Self::Execute), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ListAuditLogsActionKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ListAuditLogsActionKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ListAuditLogsActionKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListAvailableTeamsChannelsResponseItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub channel_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub channel_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub service_url: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tenant_id: Option, + } + impl From<&ListAvailableTeamsChannelsResponseItem> + for ListAvailableTeamsChannelsResponseItem { + fn from(value: &ListAvailableTeamsChannelsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListAvailableTeamsIdsResponseItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub team_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub team_name: Option, + } + impl From<&ListAvailableTeamsIdsResponseItem> for ListAvailableTeamsIdsResponseItem { + fn from(value: &ListAvailableTeamsIdsResponseItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ListCapturesRunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&ListCapturesRunnableKind> for ListCapturesRunnableKind { + fn from(value: &ListCapturesRunnableKind) -> Self { + value.clone() + } + } + impl ToString for ListCapturesRunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for ListCapturesRunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ListCapturesRunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ListCapturesRunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ListCapturesRunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ListFlowPathsFromWorkspaceRunnableRunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&ListFlowPathsFromWorkspaceRunnableRunnableKind> + for ListFlowPathsFromWorkspaceRunnableRunnableKind { + fn from(value: &ListFlowPathsFromWorkspaceRunnableRunnableKind) -> Self { + value.clone() + } + } + impl ToString for ListFlowPathsFromWorkspaceRunnableRunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for ListFlowPathsFromWorkspaceRunnableRunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ListFlowPathsFromWorkspaceRunnableRunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> + for ListFlowPathsFromWorkspaceRunnableRunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom + for ListFlowPathsFromWorkspaceRunnableRunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListFlowsResponseItem { + #[serde(flatten)] + pub flow: Flow, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_draft: Option, + } + impl From<&ListFlowsResponseItem> for ListFlowsResponseItem { + fn from(value: &ListFlowsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListHubAppsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub apps: Vec, + } + impl From<&ListHubAppsResponse> for ListHubAppsResponse { + fn from(value: &ListHubAppsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListHubAppsResponseAppsItem { + pub app_id: f64, + pub approved: bool, + pub apps: Vec, + pub id: f64, + pub summary: String, + pub votes: f64, + } + impl From<&ListHubAppsResponseAppsItem> for ListHubAppsResponseAppsItem { + fn from(value: &ListHubAppsResponseAppsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListHubFlowsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub flows: Vec, + } + impl From<&ListHubFlowsResponse> for ListHubFlowsResponse { + fn from(value: &ListHubFlowsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListHubFlowsResponseFlowsItem { + pub approved: bool, + pub apps: Vec, + pub flow_id: f64, + pub id: f64, + pub summary: String, + pub votes: f64, + } + impl From<&ListHubFlowsResponseFlowsItem> for ListHubFlowsResponseFlowsItem { + fn from(value: &ListHubFlowsResponseFlowsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListHubIntegrationsResponseItem { + pub name: String, + } + impl From<&ListHubIntegrationsResponseItem> for ListHubIntegrationsResponseItem { + fn from(value: &ListHubIntegrationsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListLogFilesResponseItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub err_lines: Option, + pub file_path: String, + pub hostname: String, + pub json_fmt: bool, + pub log_ts: chrono::DateTime, + pub mode: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ok_lines: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker_group: Option, + } + impl From<&ListLogFilesResponseItem> for ListLogFilesResponseItem { + fn from(value: &ListLogFilesResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListOAuthLoginsResponse { + pub oauth: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub saml: Option, + } + impl From<&ListOAuthLoginsResponse> for ListOAuthLoginsResponse { + fn from(value: &ListOAuthLoginsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListOAuthLoginsResponseOauthItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub display_name: Option, + #[serde(rename = "type")] + pub type_: String, + } + impl From<&ListOAuthLoginsResponseOauthItem> for ListOAuthLoginsResponseOauthItem { + fn from(value: &ListOAuthLoginsResponseOauthItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListResourceNamesResponseItem { + pub name: String, + pub path: String, + } + impl From<&ListResourceNamesResponseItem> for ListResourceNamesResponseItem { + fn from(value: &ListResourceNamesResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListSearchAppResponseItem { + pub path: String, + pub value: serde_json::Value, + } + impl From<&ListSearchAppResponseItem> for ListSearchAppResponseItem { + fn from(value: &ListSearchAppResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListSearchFlowResponseItem { + pub path: String, + pub value: serde_json::Value, + } + impl From<&ListSearchFlowResponseItem> for ListSearchFlowResponseItem { + fn from(value: &ListSearchFlowResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListSearchResourceResponseItem { + pub path: String, + pub value: serde_json::Value, + } + impl From<&ListSearchResourceResponseItem> for ListSearchResourceResponseItem { + fn from(value: &ListSearchResourceResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListSearchScriptResponseItem { + pub content: String, + pub path: String, + } + impl From<&ListSearchScriptResponseItem> for ListSearchScriptResponseItem { + fn from(value: &ListSearchScriptResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListStoredFilesResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub next_marker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restricted_access: Option, + pub windmill_large_files: Vec, + } + impl From<&ListStoredFilesResponse> for ListStoredFilesResponse { + fn from(value: &ListStoredFilesResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListWorkerGroupsResponseItem { + pub config: serde_json::Value, + pub name: String, + } + impl From<&ListWorkerGroupsResponseItem> for ListWorkerGroupsResponseItem { + fn from(value: &ListWorkerGroupsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableApp { + pub edited_at: chrono::DateTime, + pub execution_mode: ListableAppExecutionMode, + pub extra_perms: std::collections::HashMap, + pub id: i64, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + pub summary: String, + pub version: i64, + pub workspace_id: String, + } + impl From<&ListableApp> for ListableApp { + fn from(value: &ListableApp) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ListableAppExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&ListableAppExecutionMode> for ListableAppExecutionMode { + fn from(value: &ListableAppExecutionMode) -> Self { + value.clone() + } + } + impl ToString for ListableAppExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for ListableAppExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableRawApp { + pub edited_at: chrono::DateTime, + pub extra_perms: std::collections::HashMap, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + pub summary: String, + pub version: f64, + pub workspace_id: String, + } + impl From<&ListableRawApp> for ListableRawApp { + fn from(value: &ListableRawApp) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_expired: Option, + pub is_linked: bool, + pub is_oauth: bool, + pub is_refreshed: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_error: Option, + pub resource_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&ListableResource> for ListableResource { + fn from(value: &ListableResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option>, + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_expired: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_linked: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_oauth: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_refreshed: Option, + pub is_secret: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + pub workspace_id: String, + } + impl From<&ListableVariable> for ListableVariable { + fn from(value: &ListableVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LoadTableRowCountResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub count: Option, + } + impl From<&LoadTableRowCountResponse> for LoadTableRowCountResponse { + fn from(value: &LoadTableRowCountResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LogSearchHit { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dancer: Option, + } + impl From<&LogSearchHit> for LogSearchHit { + fn from(value: &LogSearchHit) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Login { + pub email: String, + pub password: String, + } + impl From<&Login> for Login { + fn from(value: &Login) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LoginWithOauthBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub state: Option, + } + impl From<&LoginWithOauthBody> for LoginWithOauthBody { + fn from(value: &LoginWithOauthBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignature { + pub args: Vec, + pub error: String, + pub has_preprocessor: Option, + pub no_main_func: Option, + pub star_args: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub star_kwargs: Option, + #[serde(rename = "type")] + pub type_: MainArgSignatureType, + } + impl From<&MainArgSignature> for MainArgSignature { + fn from(value: &MainArgSignature) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignatureArgsItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_default: Option, + pub name: String, + pub typ: MainArgSignatureArgsItemTyp, + } + impl From<&MainArgSignatureArgsItem> for MainArgSignatureArgsItem { + fn from(value: &MainArgSignatureArgsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTyp { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "resource")] + Resource(Option), + #[serde(rename = "str")] + Str(Option>), + #[serde(rename = "object")] + Object(Vec), + #[serde(rename = "list")] + List(MainArgSignatureArgsItemTypList), + } + impl From<&MainArgSignatureArgsItemTyp> for MainArgSignatureArgsItemTyp { + fn from(value: &MainArgSignatureArgsItemTyp) -> Self { + value.clone() + } + } + impl From> for MainArgSignatureArgsItemTyp { + fn from(value: Option) -> Self { + Self::Resource(value) + } + } + impl From>> for MainArgSignatureArgsItemTyp { + fn from(value: Option>) -> Self { + Self::Str(value) + } + } + impl From> + for MainArgSignatureArgsItemTyp { + fn from(value: Vec) -> Self { + Self::Object(value) + } + } + impl From for MainArgSignatureArgsItemTyp { + fn from(value: MainArgSignatureArgsItemTypList) -> Self { + Self::List(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTypList { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "str")] + Str(serde_json::Value), + } + impl From<&MainArgSignatureArgsItemTypList> for MainArgSignatureArgsItemTypList { + fn from(value: &MainArgSignatureArgsItemTypList) -> Self { + value.clone() + } + } + impl From for MainArgSignatureArgsItemTypList { + fn from(value: serde_json::Value) -> Self { + Self::Str(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignatureArgsItemTypObjectItem { + pub key: String, + pub typ: MainArgSignatureArgsItemTypObjectItemTyp, + } + impl From<&MainArgSignatureArgsItemTypObjectItem> + for MainArgSignatureArgsItemTypObjectItem { + fn from(value: &MainArgSignatureArgsItemTypObjectItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTypObjectItemTyp { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "str")] + Str(serde_json::Value), + } + impl From<&MainArgSignatureArgsItemTypObjectItemTyp> + for MainArgSignatureArgsItemTypObjectItemTyp { + fn from(value: &MainArgSignatureArgsItemTypObjectItemTyp) -> Self { + value.clone() + } + } + impl From for MainArgSignatureArgsItemTypObjectItemTyp { + fn from(value: serde_json::Value) -> Self { + Self::Str(value) + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MainArgSignatureType { + Valid, + Invalid, + } + impl From<&MainArgSignatureType> for MainArgSignatureType { + fn from(value: &MainArgSignatureType) -> Self { + value.clone() + } + } + impl ToString for MainArgSignatureType { + fn to_string(&self) -> String { + match *self { + Self::Valid => "Valid".to_string(), + Self::Invalid => "Invalid".to_string(), + } + } + } + impl std::str::FromStr for MainArgSignatureType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Valid" => Ok(Self::Valid), + "Invalid" => Ok(Self::Invalid), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MetricDataPoint { + pub timestamp: chrono::DateTime, + pub value: f64, + } + impl From<&MetricDataPoint> for MetricDataPoint { + fn from(value: &MetricDataPoint) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MetricMetadata { + pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + impl From<&MetricMetadata> for MetricMetadata { + fn from(value: &MetricMetadata) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MoveCapturesAndConfigsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub new_path: Option, + } + impl From<&MoveCapturesAndConfigsBody> for MoveCapturesAndConfigsBody { + fn from(value: &MoveCapturesAndConfigsBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MoveCapturesAndConfigsRunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&MoveCapturesAndConfigsRunnableKind> + for MoveCapturesAndConfigsRunnableKind { + fn from(value: &MoveCapturesAndConfigsRunnableKind) -> Self { + value.clone() + } + } + impl ToString for MoveCapturesAndConfigsRunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for MoveCapturesAndConfigsRunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MoveCapturesAndConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MoveCapturesAndConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MoveCapturesAndConfigsRunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MqttClientVersion { + #[serde(rename = "v3")] + V3, + #[serde(rename = "v5")] + V5, + } + impl From<&MqttClientVersion> for MqttClientVersion { + fn from(value: &MqttClientVersion) -> Self { + value.clone() + } + } + impl ToString for MqttClientVersion { + fn to_string(&self) -> String { + match *self { + Self::V3 => "v3".to_string(), + Self::V5 => "v5".to_string(), + } + } + } + impl std::str::FromStr for MqttClientVersion { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "v3" => Ok(Self::V3), + "v5" => Ok(Self::V5), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MqttClientVersion { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MqttClientVersion { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MqttClientVersion { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MqttQoS { + #[serde(rename = "qos0")] + Qos0, + #[serde(rename = "qos1")] + Qos1, + #[serde(rename = "qos2")] + Qos2, + } + impl From<&MqttQoS> for MqttQoS { + fn from(value: &MqttQoS) -> Self { + value.clone() + } + } + impl ToString for MqttQoS { + fn to_string(&self) -> String { + match *self { + Self::Qos0 => "qos0".to_string(), + Self::Qos1 => "qos1".to_string(), + Self::Qos2 => "qos2".to_string(), + } + } + } + impl std::str::FromStr for MqttQoS { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "qos0" => Ok(Self::Qos0), + "qos1" => Ok(Self::Qos1), + "qos2" => Ok(Self::Qos2), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MqttQoS { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MqttQoS { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MqttQoS { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttSubscribeTopic { + pub qos: MqttQoS, + pub topic: String, + } + impl From<&MqttSubscribeTopic> for MqttSubscribeTopic { + fn from(value: &MqttSubscribeTopic) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub mqtt_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&MqttTrigger> for MqttTrigger { + fn from(value: &MqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttV3Config { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub clean_session: Option, + } + impl From<&MqttV3Config> for MqttV3Config { + fn from(value: &MqttV3Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttV5Config { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub clean_start: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub session_expiry_interval: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub topic_alias: Option, + } + impl From<&MqttV5Config> for MqttV5Config { + fn from(value: &MqttV5Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub nats_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&NatsTrigger> for NatsTrigger { + fn from(value: &NatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewHttpTrigger { + pub http_method: NewHttpTriggerHttpMethod, + pub is_async: bool, + pub is_flow: bool, + pub is_static_website: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_string: Option, + pub requires_auth: bool, + pub route_path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wrap_body: Option, + } + impl From<&NewHttpTrigger> for NewHttpTrigger { + fn from(value: &NewHttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewHttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&NewHttpTriggerHttpMethod> for NewHttpTriggerHttpMethod { + fn from(value: &NewHttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for NewHttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for NewHttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewHttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&NewHttpTriggerStaticAssetConfig> for NewHttpTriggerStaticAssetConfig { + fn from(value: &NewHttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewKafkaTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub group_id: String, + pub is_flow: bool, + pub kafka_resource_path: String, + pub path: String, + pub script_path: String, + pub topics: Vec, + } + impl From<&NewKafkaTrigger> for NewKafkaTrigger { + fn from(value: &NewKafkaTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewMqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + pub mqtt_resource_path: String, + pub path: String, + pub script_path: String, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&NewMqttTrigger> for NewMqttTrigger { + fn from(value: &NewMqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewNatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + pub nats_resource_path: String, + pub path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&NewNatsTrigger> for NewNatsTrigger { + fn from(value: &NewNatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewPostgresTrigger { + pub enabled: bool, + pub is_flow: bool, + pub path: String, + pub postgres_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub replication_slot_name: Option, + pub script_path: String, + } + impl From<&NewPostgresTrigger> for NewPostgresTrigger { + fn from(value: &NewPostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewSchedule { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&NewSchedule> for NewSchedule { + fn from(value: &NewSchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub codebase: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub envs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_preprocessor: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_template: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + pub language: ScriptLang, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_hash: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restart_unless_cancelled: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub summary: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&NewScript> for NewScript { + fn from(value: &NewScript) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewScriptKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "failure")] + Failure, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "command")] + Command, + #[serde(rename = "approval")] + Approval, + #[serde(rename = "preprocessor")] + Preprocessor, + } + impl From<&NewScriptKind> for NewScriptKind { + fn from(value: &NewScriptKind) -> Self { + value.clone() + } + } + impl ToString for NewScriptKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Failure => "failure".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Command => "command".to_string(), + Self::Approval => "approval".to_string(), + Self::Preprocessor => "preprocessor".to_string(), + } + } + } + impl std::str::FromStr for NewScriptKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "failure" => Ok(Self::Failure), + "trigger" => Ok(Self::Trigger), + "command" => Ok(Self::Command), + "approval" => Ok(Self::Approval), + "preprocessor" => Ok(Self::Preprocessor), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewScriptKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewScriptKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewScriptKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewScriptWithDraft { + #[serde(flatten)] + pub new_script: NewScript, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft: Option, + pub hash: String, + } + impl From<&NewScriptWithDraft> for NewScriptWithDraft { + fn from(value: &NewScriptWithDraft) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewSqsTrigger { + pub aws_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub path: String, + pub queue_url: String, + pub script_path: String, + } + impl From<&NewSqsTrigger> for NewSqsTrigger { + fn from(value: &NewSqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewToken { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scopes: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&NewToken> for NewToken { + fn from(value: &NewToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewTokenImpersonate { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + pub impersonate_email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&NewTokenImpersonate> for NewTokenImpersonate { + fn from(value: &NewTokenImpersonate) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewWebsocketTrigger { + pub can_return_message: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&NewWebsocketTrigger> for NewWebsocketTrigger { + fn from(value: &NewWebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewWebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&NewWebsocketTriggerFiltersItem> for NewWebsocketTriggerFiltersItem { + fn from(value: &NewWebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ObscuredJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub duration_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub typ: Option, + } + impl From<&ObscuredJob> for ObscuredJob { + fn from(value: &ObscuredJob) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenFlow { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub summary: String, + pub value: FlowValue, + } + impl From<&OpenFlow> for OpenFlow { + fn from(value: &OpenFlow) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenFlowWPath { + #[serde(flatten)] + pub open_flow: OpenFlow, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&OpenFlowWPath> for OpenFlowWPath { + fn from(value: &OpenFlowWPath) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OperatorSettings(pub Option); + impl std::ops::Deref for OperatorSettings { + type Target = Option; + fn deref(&self) -> &Option { + &self.0 + } + } + impl From for Option { + fn from(value: OperatorSettings) -> Self { + value.0 + } + } + impl From<&OperatorSettings> for OperatorSettings { + fn from(value: &OperatorSettings) -> Self { + value.clone() + } + } + impl From> for OperatorSettings { + fn from(value: Option) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OperatorSettingsInner { + ///Whether operators can view audit logs + pub audit_logs: bool, + ///Whether operators can view folders page + pub folders: bool, + ///Whether operators can view groups page + pub groups: bool, + ///Whether operators can view resources + pub resources: bool, + ///Whether operators can view runs + pub runs: bool, + ///Whether operators can view schedules + pub schedules: bool, + ///Whether operators can view triggers + pub triggers: bool, + ///Whether operators can view variables + pub variables: bool, + ///Whether operators can view workers page + pub workers: bool, + } + impl From<&OperatorSettingsInner> for OperatorSettingsInner { + fn from(value: &OperatorSettingsInner) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PathFlow { + pub input_transforms: std::collections::HashMap, + pub path: String, + #[serde(rename = "type")] + pub type_: PathFlowType, + } + impl From<&PathFlow> for PathFlow { + fn from(value: &PathFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PathFlowType { + #[serde(rename = "flow")] + Flow, + } + impl From<&PathFlowType> for PathFlowType { + fn from(value: &PathFlowType) -> Self { + value.clone() + } + } + impl ToString for PathFlowType { + fn to_string(&self) -> String { + match *self { + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for PathFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PathFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PathFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PathFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PathScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + pub input_transforms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_trigger: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag_override: Option, + #[serde(rename = "type")] + pub type_: PathScriptType, + } + impl From<&PathScript> for PathScript { + fn from(value: &PathScript) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PathScriptType { + #[serde(rename = "script")] + Script, + } + impl From<&PathScriptType> for PathScriptType { + fn from(value: &PathScriptType) -> Self { + value.clone() + } + } + impl ToString for PathScriptType { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + } + } + } + impl std::str::FromStr for PathScriptType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PathScriptType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PathScriptType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PathScriptType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PingCaptureConfigRunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&PingCaptureConfigRunnableKind> for PingCaptureConfigRunnableKind { + fn from(value: &PingCaptureConfigRunnableKind) -> Self { + value.clone() + } + } + impl ToString for PingCaptureConfigRunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for PingCaptureConfigRunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PingCaptureConfigRunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PingCaptureConfigRunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PingCaptureConfigRunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsClientKwargs { + pub region_name: String, + } + impl From<&PolarsClientKwargs> for PolarsClientKwargs { + fn from(value: &PolarsClientKwargs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource: Option, + } + impl From<&PolarsConnectionSettingsBody> for PolarsConnectionSettingsBody { + fn from(value: &PolarsConnectionSettingsBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsResponse { + pub cache_regions: bool, + pub client_kwargs: PolarsClientKwargs, + pub endpoint_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub secret: Option, + pub use_ssl: bool, + } + impl From<&PolarsConnectionSettingsResponse> for PolarsConnectionSettingsResponse { + fn from(value: &PolarsConnectionSettingsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsV2Body { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + } + impl From<&PolarsConnectionSettingsV2Body> for PolarsConnectionSettingsV2Body { + fn from(value: &PolarsConnectionSettingsV2Body) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsV2Response { + pub s3fs_args: PolarsConnectionSettingsV2ResponseS3fsArgs, + pub storage_options: PolarsConnectionSettingsV2ResponseStorageOptions, + } + impl From<&PolarsConnectionSettingsV2Response> + for PolarsConnectionSettingsV2Response { + fn from(value: &PolarsConnectionSettingsV2Response) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsV2ResponseS3fsArgs { + pub cache_regions: bool, + pub client_kwargs: PolarsClientKwargs, + pub endpoint_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub secret: Option, + pub use_ssl: bool, + } + impl From<&PolarsConnectionSettingsV2ResponseS3fsArgs> + for PolarsConnectionSettingsV2ResponseS3fsArgs { + fn from(value: &PolarsConnectionSettingsV2ResponseS3fsArgs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsConnectionSettingsV2ResponseStorageOptions { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aws_access_key_id: Option, + pub aws_allow_http: String, + pub aws_endpoint_url: String, + pub aws_region: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aws_secret_access_key: Option, + } + impl From<&PolarsConnectionSettingsV2ResponseStorageOptions> + for PolarsConnectionSettingsV2ResponseStorageOptions { + fn from(value: &PolarsConnectionSettingsV2ResponseStorageOptions) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Policy { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub allowed_s3_keys: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub s3_inputs: Vec>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub triggerables: std::collections::HashMap< + String, + std::collections::HashMap, + >, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub triggerables_v2: std::collections::HashMap< + String, + std::collections::HashMap, + >, + } + impl From<&Policy> for Policy { + fn from(value: &Policy) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolicyAllowedS3KeysItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_path: Option, + } + impl From<&PolicyAllowedS3KeysItem> for PolicyAllowedS3KeysItem { + fn from(value: &PolicyAllowedS3KeysItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PolicyExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&PolicyExecutionMode> for PolicyExecutionMode { + fn from(value: &PolicyExecutionMode) -> Self { + value.clone() + } + } + impl ToString for PolicyExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for PolicyExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PostgresTrigger { + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub postgres_resource_path: String, + pub publication_name: String, + pub replication_slot_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + } + impl From<&PostgresTrigger> for PostgresTrigger { + fn from(value: &PostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Preview { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + } + impl From<&Preview> for Preview { + fn from(value: &Preview) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PreviewKind { + #[serde(rename = "code")] + Code, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "http")] + Http, + } + impl From<&PreviewKind> for PreviewKind { + fn from(value: &PreviewKind) -> Self { + value.clone() + } + } + impl ToString for PreviewKind { + fn to_string(&self) -> String { + match *self { + Self::Code => "code".to_string(), + Self::Identity => "identity".to_string(), + Self::Http => "http".to_string(), + } + } + } + impl std::str::FromStr for PreviewKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "code" => Ok(Self::Code), + "identity" => Ok(Self::Identity), + "http" => Ok(Self::Http), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PreviewKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PreviewKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PreviewKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PreviewScheduleBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + pub schedule: String, + pub timezone: String, + } + impl From<&PreviewScheduleBody> for PreviewScheduleBody { + fn from(value: &PreviewScheduleBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PublicationData { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub table_to_track: Vec, + pub transaction_to_track: Vec, + } + impl From<&PublicationData> for PublicationData { + fn from(value: &PublicationData) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct QueryHubScriptsResponseItem { + pub app: String, + pub ask_id: f64, + pub id: f64, + pub kind: HubScriptKind, + pub score: f64, + pub summary: String, + pub version_id: f64, + } + impl From<&QueryHubScriptsResponseItem> for QueryHubScriptsResponseItem { + fn from(value: &QueryHubScriptsResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct QueryResourceTypesResponseItem { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + pub score: f64, + } + impl From<&QueryResourceTypesResponseItem> for QueryResourceTypesResponseItem { + fn from(value: &QueryResourceTypesResponseItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct QueuedJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aggregate_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + pub canceled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + pub email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_status: Option, + pub id: uuid::Uuid, + pub is_flow_step: bool, + pub job_kind: QueuedJobJobKind, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub logs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mem_peak: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_job: Option, + /**The user (u/userfoo) or group (g/groupfoo) whom +the execution of this script will be permissioned_as and by extension its DT_TOKEN. +*/ + pub permissioned_as: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessed: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_flow: Option, + pub running: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scheduled_for: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub suspend: Option, + pub tag: String, + pub visible_to_owner: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&QueuedJob> for QueuedJob { + fn from(value: &QueuedJob) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum QueuedJobJobKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "preview")] + Preview, + #[serde(rename = "dependencies")] + Dependencies, + #[serde(rename = "flowdependencies")] + Flowdependencies, + #[serde(rename = "appdependencies")] + Appdependencies, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "flowpreview")] + Flowpreview, + #[serde(rename = "script_hub")] + ScriptHub, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "deploymentcallback")] + Deploymentcallback, + #[serde(rename = "singlescriptflow")] + Singlescriptflow, + #[serde(rename = "flowscript")] + Flowscript, + #[serde(rename = "flownode")] + Flownode, + #[serde(rename = "appscript")] + Appscript, + } + impl From<&QueuedJobJobKind> for QueuedJobJobKind { + fn from(value: &QueuedJobJobKind) -> Self { + value.clone() + } + } + impl ToString for QueuedJobJobKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Preview => "preview".to_string(), + Self::Dependencies => "dependencies".to_string(), + Self::Flowdependencies => "flowdependencies".to_string(), + Self::Appdependencies => "appdependencies".to_string(), + Self::Flow => "flow".to_string(), + Self::Flowpreview => "flowpreview".to_string(), + Self::ScriptHub => "script_hub".to_string(), + Self::Identity => "identity".to_string(), + Self::Deploymentcallback => "deploymentcallback".to_string(), + Self::Singlescriptflow => "singlescriptflow".to_string(), + Self::Flowscript => "flowscript".to_string(), + Self::Flownode => "flownode".to_string(), + Self::Appscript => "appscript".to_string(), + } + } + } + impl std::str::FromStr for QueuedJobJobKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "preview" => Ok(Self::Preview), + "dependencies" => Ok(Self::Dependencies), + "flowdependencies" => Ok(Self::Flowdependencies), + "appdependencies" => Ok(Self::Appdependencies), + "flow" => Ok(Self::Flow), + "flowpreview" => Ok(Self::Flowpreview), + "script_hub" => Ok(Self::ScriptHub), + "identity" => Ok(Self::Identity), + "deploymentcallback" => Ok(Self::Deploymentcallback), + "singlescriptflow" => Ok(Self::Singlescriptflow), + "flowscript" => Ok(Self::Flowscript), + "flownode" => Ok(Self::Flownode), + "appscript" => Ok(Self::Appscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RawScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_concurrency_key: Option, + pub input_transforms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_trigger: Option, + pub language: RawScriptLanguage, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(rename = "type")] + pub type_: RawScriptType, + } + impl From<&RawScript> for RawScript { + fn from(value: &RawScript) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RawScriptForDependencies { + pub language: ScriptLang, + pub path: String, + pub raw_code: String, + } + impl From<&RawScriptForDependencies> for RawScriptForDependencies { + fn from(value: &RawScriptForDependencies) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptLanguage { + #[serde(rename = "deno")] + Deno, + #[serde(rename = "bun")] + Bun, + #[serde(rename = "python3")] + Python3, + #[serde(rename = "go")] + Go, + #[serde(rename = "bash")] + Bash, + #[serde(rename = "powershell")] + Powershell, + #[serde(rename = "postgresql")] + Postgresql, + #[serde(rename = "mysql")] + Mysql, + #[serde(rename = "bigquery")] + Bigquery, + #[serde(rename = "snowflake")] + Snowflake, + #[serde(rename = "mssql")] + Mssql, + #[serde(rename = "oracledb")] + Oracledb, + #[serde(rename = "graphql")] + Graphql, + #[serde(rename = "nativets")] + Nativets, + #[serde(rename = "php")] + Php, + } + impl From<&RawScriptLanguage> for RawScriptLanguage { + fn from(value: &RawScriptLanguage) -> Self { + value.clone() + } + } + impl ToString for RawScriptLanguage { + fn to_string(&self) -> String { + match *self { + Self::Deno => "deno".to_string(), + Self::Bun => "bun".to_string(), + Self::Python3 => "python3".to_string(), + Self::Go => "go".to_string(), + Self::Bash => "bash".to_string(), + Self::Powershell => "powershell".to_string(), + Self::Postgresql => "postgresql".to_string(), + Self::Mysql => "mysql".to_string(), + Self::Bigquery => "bigquery".to_string(), + Self::Snowflake => "snowflake".to_string(), + Self::Mssql => "mssql".to_string(), + Self::Oracledb => "oracledb".to_string(), + Self::Graphql => "graphql".to_string(), + Self::Nativets => "nativets".to_string(), + Self::Php => "php".to_string(), + } + } + } + impl std::str::FromStr for RawScriptLanguage { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "deno" => Ok(Self::Deno), + "bun" => Ok(Self::Bun), + "python3" => Ok(Self::Python3), + "go" => Ok(Self::Go), + "bash" => Ok(Self::Bash), + "powershell" => Ok(Self::Powershell), + "postgresql" => Ok(Self::Postgresql), + "mysql" => Ok(Self::Mysql), + "bigquery" => Ok(Self::Bigquery), + "snowflake" => Ok(Self::Snowflake), + "mssql" => Ok(Self::Mssql), + "oracledb" => Ok(Self::Oracledb), + "graphql" => Ok(Self::Graphql), + "nativets" => Ok(Self::Nativets), + "php" => Ok(Self::Php), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptType { + #[serde(rename = "rawscript")] + Rawscript, + } + impl From<&RawScriptType> for RawScriptType { + fn from(value: &RawScriptType) -> Self { + value.clone() + } + } + impl ToString for RawScriptType { + fn to_string(&self) -> String { + match *self { + Self::Rawscript => "rawscript".to_string(), + } + } + } + impl std::str::FromStr for RawScriptType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "rawscript" => Ok(Self::Rawscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RefreshTokenBody { + pub path: String, + } + impl From<&RefreshTokenBody> for RefreshTokenBody { + fn from(value: &RefreshTokenBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Relations { + pub schema_name: String, + pub table_to_track: TableToTrack, + } + impl From<&Relations> for Relations { + fn from(value: &Relations) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RemoveGranularAclsBody { + pub owner: String, + } + impl From<&RemoveGranularAclsBody> for RemoveGranularAclsBody { + fn from(value: &RemoveGranularAclsBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RemoveGranularAclsKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "group_")] + Group, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "app")] + App, + #[serde(rename = "raw_app")] + RawApp, + #[serde(rename = "http_trigger")] + HttpTrigger, + #[serde(rename = "websocket_trigger")] + WebsocketTrigger, + #[serde(rename = "kafka_trigger")] + KafkaTrigger, + #[serde(rename = "nats_trigger")] + NatsTrigger, + #[serde(rename = "postgres_trigger")] + PostgresTrigger, + #[serde(rename = "mqtt_trigger")] + MqttTrigger, + #[serde(rename = "sqs_trigger")] + SqsTrigger, + } + impl From<&RemoveGranularAclsKind> for RemoveGranularAclsKind { + fn from(value: &RemoveGranularAclsKind) -> Self { + value.clone() + } + } + impl ToString for RemoveGranularAclsKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Group => "group_".to_string(), + Self::Resource => "resource".to_string(), + Self::Schedule => "schedule".to_string(), + Self::Variable => "variable".to_string(), + Self::Flow => "flow".to_string(), + Self::Folder => "folder".to_string(), + Self::App => "app".to_string(), + Self::RawApp => "raw_app".to_string(), + Self::HttpTrigger => "http_trigger".to_string(), + Self::WebsocketTrigger => "websocket_trigger".to_string(), + Self::KafkaTrigger => "kafka_trigger".to_string(), + Self::NatsTrigger => "nats_trigger".to_string(), + Self::PostgresTrigger => "postgres_trigger".to_string(), + Self::MqttTrigger => "mqtt_trigger".to_string(), + Self::SqsTrigger => "sqs_trigger".to_string(), + } + } + } + impl std::str::FromStr for RemoveGranularAclsKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "group_" => Ok(Self::Group), + "resource" => Ok(Self::Resource), + "schedule" => Ok(Self::Schedule), + "variable" => Ok(Self::Variable), + "flow" => Ok(Self::Flow), + "folder" => Ok(Self::Folder), + "app" => Ok(Self::App), + "raw_app" => Ok(Self::RawApp), + "http_trigger" => Ok(Self::HttpTrigger), + "websocket_trigger" => Ok(Self::WebsocketTrigger), + "kafka_trigger" => Ok(Self::KafkaTrigger), + "nats_trigger" => Ok(Self::NatsTrigger), + "postgres_trigger" => Ok(Self::PostgresTrigger), + "mqtt_trigger" => Ok(Self::MqttTrigger), + "sqs_trigger" => Ok(Self::SqsTrigger), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RemoveGranularAclsKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RemoveGranularAclsKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RemoveGranularAclsKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RemoveOwnerToFolderBody { + pub owner: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub write: Option, + } + impl From<&RemoveOwnerToFolderBody> for RemoveOwnerToFolderBody { + fn from(value: &RemoveOwnerToFolderBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RemoveUserFromInstanceGroupBody { + pub email: String, + } + impl From<&RemoveUserFromInstanceGroupBody> for RemoveUserFromInstanceGroupBody { + fn from(value: &RemoveUserFromInstanceGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RemoveUserToGroupBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + } + impl From<&RemoveUserToGroupBody> for RemoveUserToGroupBody { + fn from(value: &RemoveUserToGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Resource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + pub is_oauth: bool, + pub path: String, + pub resource_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&Resource> for Resource { + fn from(value: &Resource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ResourceType { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format_extension: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&ResourceType> for ResourceType { + fn from(value: &ResourceType) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RestartedFrom { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch_or_iteration_n: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub step_id: Option, + } + impl From<&RestartedFrom> for RestartedFrom { + fn from(value: &RestartedFrom) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Retry { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub constant: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exponential: Option, + } + impl From<&Retry> for Retry { + fn from(value: &Retry) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RetryConstant { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub attempts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seconds: Option, + } + impl From<&RetryConstant> for RetryConstant { + fn from(value: &RetryConstant) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RetryExponential { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub attempts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub multiplier: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub random_factor: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seconds: Option, + } + impl From<&RetryExponential> for RetryExponential { + fn from(value: &RetryExponential) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RunRawScriptDependenciesBody { + pub entrypoint: String, + pub raw_scripts: Vec, + } + impl From<&RunRawScriptDependenciesBody> for RunRawScriptDependenciesBody { + fn from(value: &RunRawScriptDependenciesBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RunRawScriptDependenciesResponse { + pub lock: String, + } + impl From<&RunRawScriptDependenciesResponse> for RunRawScriptDependenciesResponse { + fn from(value: &RunRawScriptDependenciesResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RunSlackMessageTestJobBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hub_script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub test_msg: Option, + } + impl From<&RunSlackMessageTestJobBody> for RunSlackMessageTestJobBody { + fn from(value: &RunSlackMessageTestJobBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RunTeamsMessageTestJobBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hub_script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub test_msg: Option, + } + impl From<&RunTeamsMessageTestJobBody> for RunTeamsMessageTestJobBody { + fn from(value: &RunTeamsMessageTestJobBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RunnableType { + ScriptHash, + ScriptPath, + FlowPath, + } + impl From<&RunnableType> for RunnableType { + fn from(value: &RunnableType) -> Self { + value.clone() + } + } + impl ToString for RunnableType { + fn to_string(&self) -> String { + match *self { + Self::ScriptHash => "ScriptHash".to_string(), + Self::ScriptPath => "ScriptPath".to_string(), + Self::FlowPath => "FlowPath".to_string(), + } + } + } + impl std::str::FromStr for RunnableType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "ScriptHash" => Ok(Self::ScriptHash), + "ScriptPath" => Ok(Self::ScriptPath), + "FlowPath" => Ok(Self::FlowPath), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RunnableType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RunnableType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RunnableType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct S3Resource { + #[serde(rename = "accessKey", default, skip_serializing_if = "Option::is_none")] + pub access_key: Option, + pub bucket: String, + #[serde(rename = "endPoint")] + pub end_point: String, + #[serde(rename = "pathStyle")] + pub path_style: bool, + pub region: String, + #[serde(rename = "secretKey", default, skip_serializing_if = "Option::is_none")] + pub secret_key: Option, + #[serde(rename = "useSSL")] + pub use_ssl: bool, + } + impl From<&S3Resource> for S3Resource { + fn from(value: &S3Resource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct S3ResourceInfoBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + } + impl From<&S3ResourceInfoBody> for S3ResourceInfoBody { + fn from(value: &S3ResourceInfoBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScalarMetric { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metric_id: Option, + pub value: f64, + } + impl From<&ScalarMetric> for ScalarMetric { + fn from(value: &ScalarMetric) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Schedule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub email: String, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub extra_perms: std::collections::HashMap, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&Schedule> for Schedule { + fn from(value: &Schedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScheduleWJobs { + #[serde(flatten)] + pub schedule: Schedule, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub jobs: Vec, + } + impl From<&ScheduleWJobs> for ScheduleWJobs { + fn from(value: &ScheduleWJobs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScheduleWJobsJobsItem { + pub duration_ms: f64, + pub id: String, + pub success: bool, + } + impl From<&ScheduleWJobsJobsItem> for ScheduleWJobsJobsItem { + fn from(value: &ScheduleWJobsJobsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Script { + pub archived: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub codebase: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + pub deleted: bool, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub envs: Vec, + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_draft: Option, + pub has_preprocessor: bool, + pub hash: String, + pub is_template: bool, + pub kind: ScriptKind, + pub language: ScriptLang, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + pub no_main_func: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + /**The first element is the direct parent of the script, the second is the parent of the first, etc +*/ + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub parent_hashes: Vec, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restart_unless_cancelled: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub starred: bool, + pub summary: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&Script> for Script { + fn from(value: &Script) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScriptArgs(pub std::collections::HashMap); + impl std::ops::Deref for ScriptArgs { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From for std::collections::HashMap { + fn from(value: ScriptArgs) -> Self { + value.0 + } + } + impl From<&ScriptArgs> for ScriptArgs { + fn from(value: &ScriptArgs) -> Self { + value.clone() + } + } + impl From> for ScriptArgs { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScriptHistory { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub script_hash: String, + } + impl From<&ScriptHistory> for ScriptHistory { + fn from(value: &ScriptHistory) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ScriptKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "failure")] + Failure, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "command")] + Command, + #[serde(rename = "approval")] + Approval, + #[serde(rename = "preprocessor")] + Preprocessor, + } + impl From<&ScriptKind> for ScriptKind { + fn from(value: &ScriptKind) -> Self { + value.clone() + } + } + impl ToString for ScriptKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Failure => "failure".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Command => "command".to_string(), + Self::Approval => "approval".to_string(), + Self::Preprocessor => "preprocessor".to_string(), + } + } + } + impl std::str::FromStr for ScriptKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "failure" => Ok(Self::Failure), + "trigger" => Ok(Self::Trigger), + "command" => Ok(Self::Command), + "approval" => Ok(Self::Approval), + "preprocessor" => Ok(Self::Preprocessor), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ScriptKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ScriptKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ScriptKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ScriptLang { + #[serde(rename = "python3")] + Python3, + #[serde(rename = "deno")] + Deno, + #[serde(rename = "go")] + Go, + #[serde(rename = "bash")] + Bash, + #[serde(rename = "powershell")] + Powershell, + #[serde(rename = "postgresql")] + Postgresql, + #[serde(rename = "mysql")] + Mysql, + #[serde(rename = "bigquery")] + Bigquery, + #[serde(rename = "snowflake")] + Snowflake, + #[serde(rename = "mssql")] + Mssql, + #[serde(rename = "oracledb")] + Oracledb, + #[serde(rename = "graphql")] + Graphql, + #[serde(rename = "nativets")] + Nativets, + #[serde(rename = "bun")] + Bun, + #[serde(rename = "php")] + Php, + #[serde(rename = "rust")] + Rust, + #[serde(rename = "ansible")] + Ansible, + #[serde(rename = "csharp")] + Csharp, + #[serde(rename = "nu")] + Nu, + } + impl From<&ScriptLang> for ScriptLang { + fn from(value: &ScriptLang) -> Self { + value.clone() + } + } + impl ToString for ScriptLang { + fn to_string(&self) -> String { + match *self { + Self::Python3 => "python3".to_string(), + Self::Deno => "deno".to_string(), + Self::Go => "go".to_string(), + Self::Bash => "bash".to_string(), + Self::Powershell => "powershell".to_string(), + Self::Postgresql => "postgresql".to_string(), + Self::Mysql => "mysql".to_string(), + Self::Bigquery => "bigquery".to_string(), + Self::Snowflake => "snowflake".to_string(), + Self::Mssql => "mssql".to_string(), + Self::Oracledb => "oracledb".to_string(), + Self::Graphql => "graphql".to_string(), + Self::Nativets => "nativets".to_string(), + Self::Bun => "bun".to_string(), + Self::Php => "php".to_string(), + Self::Rust => "rust".to_string(), + Self::Ansible => "ansible".to_string(), + Self::Csharp => "csharp".to_string(), + Self::Nu => "nu".to_string(), + } + } + } + impl std::str::FromStr for ScriptLang { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "python3" => Ok(Self::Python3), + "deno" => Ok(Self::Deno), + "go" => Ok(Self::Go), + "bash" => Ok(Self::Bash), + "powershell" => Ok(Self::Powershell), + "postgresql" => Ok(Self::Postgresql), + "mysql" => Ok(Self::Mysql), + "bigquery" => Ok(Self::Bigquery), + "snowflake" => Ok(Self::Snowflake), + "mssql" => Ok(Self::Mssql), + "oracledb" => Ok(Self::Oracledb), + "graphql" => Ok(Self::Graphql), + "nativets" => Ok(Self::Nativets), + "bun" => Ok(Self::Bun), + "php" => Ok(Self::Php), + "rust" => Ok(Self::Rust), + "ansible" => Ok(Self::Ansible), + "csharp" => Ok(Self::Csharp), + "nu" => Ok(Self::Nu), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ScriptLang { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ScriptLang { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ScriptLang { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SearchJobsIndexResponse { + ///the jobs that matched the query + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub hits: Vec, + ///a list of the terms that couldn't be parsed (and thus ignored) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub query_parse_errors: Vec, + } + impl From<&SearchJobsIndexResponse> for SearchJobsIndexResponse { + fn from(value: &SearchJobsIndexResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SearchJobsIndexResponseQueryParseErrorsItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dancer: Option, + } + impl From<&SearchJobsIndexResponseQueryParseErrorsItem> + for SearchJobsIndexResponseQueryParseErrorsItem { + fn from(value: &SearchJobsIndexResponseQueryParseErrorsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SearchLogsIndexResponse { + ///log files that matched the query + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub hits: Vec, + ///a list of the terms that couldn't be parsed (and thus ignored) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub query_parse_errors: Vec, + } + impl From<&SearchLogsIndexResponse> for SearchLogsIndexResponse { + fn from(value: &SearchLogsIndexResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SendMessageToConversationBody { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub card_block: std::collections::HashMap, + ///The ID of the Teams conversation/activity + pub conversation_id: String, + ///Used for styling the card conditionally + #[serde(default = "defaults::default_bool::")] + pub success: bool, + ///The message text to be sent in the Teams card + pub text: String, + } + impl From<&SendMessageToConversationBody> for SendMessageToConversationBody { + fn from(value: &SendMessageToConversationBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetAutomaticBillingBody { + pub automatic_billing: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seats: Option, + } + impl From<&SetAutomaticBillingBody> for SetAutomaticBillingBody { + fn from(value: &SetAutomaticBillingBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetCaptureConfigBody { + pub is_flow: bool, + pub path: String, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub trigger_config: std::collections::HashMap, + pub trigger_kind: CaptureTriggerKind, + } + impl From<&SetCaptureConfigBody> for SetCaptureConfigBody { + fn from(value: &SetCaptureConfigBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetDefaultErrorOrRecoveryHandlerBody { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_args: std::collections::HashMap, + pub handler_type: SetDefaultErrorOrRecoveryHandlerBodyHandlerType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub number_of_occurence: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub number_of_occurence_exact: Option, + pub override_existing: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_handler_muted: Option, + } + impl From<&SetDefaultErrorOrRecoveryHandlerBody> + for SetDefaultErrorOrRecoveryHandlerBody { + fn from(value: &SetDefaultErrorOrRecoveryHandlerBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + #[serde(rename = "error")] + Error, + #[serde(rename = "recovery")] + Recovery, + #[serde(rename = "success")] + Success, + } + impl From<&SetDefaultErrorOrRecoveryHandlerBodyHandlerType> + for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + fn from(value: &SetDefaultErrorOrRecoveryHandlerBodyHandlerType) -> Self { + value.clone() + } + } + impl ToString for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + fn to_string(&self) -> String { + match *self { + Self::Error => "error".to_string(), + Self::Recovery => "recovery".to_string(), + Self::Success => "success".to_string(), + } + } + } + impl std::str::FromStr for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "error" => Ok(Self::Error), + "recovery" => Ok(Self::Recovery), + "success" => Ok(Self::Success), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> + for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> + for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom + for SetDefaultErrorOrRecoveryHandlerBodyHandlerType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetEnvironmentVariableBody { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&SetEnvironmentVariableBody> for SetEnvironmentVariableBody { + fn from(value: &SetEnvironmentVariableBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetGlobalBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&SetGlobalBody> for SetGlobalBody { + fn from(value: &SetGlobalBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetJobProgressBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub percent: Option, + } + impl From<&SetJobProgressBody> for SetJobProgressBody { + fn from(value: &SetJobProgressBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetKafkaTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetKafkaTriggerEnabledBody> for SetKafkaTriggerEnabledBody { + fn from(value: &SetKafkaTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetLoginTypeForUserBody { + pub login_type: String, + } + impl From<&SetLoginTypeForUserBody> for SetLoginTypeForUserBody { + fn from(value: &SetLoginTypeForUserBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetMqttTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetMqttTriggerEnabledBody> for SetMqttTriggerEnabledBody { + fn from(value: &SetMqttTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetNatsTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetNatsTriggerEnabledBody> for SetNatsTriggerEnabledBody { + fn from(value: &SetNatsTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetPasswordBody { + pub password: String, + } + impl From<&SetPasswordBody> for SetPasswordBody { + fn from(value: &SetPasswordBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetPasswordForUserBody { + pub password: String, + } + impl From<&SetPasswordForUserBody> for SetPasswordForUserBody { + fn from(value: &SetPasswordForUserBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetPostgresTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetPostgresTriggerEnabledBody> for SetPostgresTriggerEnabledBody { + fn from(value: &SetPostgresTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetScheduleEnabledBody { + pub enabled: bool, + } + impl From<&SetScheduleEnabledBody> for SetScheduleEnabledBody { + fn from(value: &SetScheduleEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetSqsTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetSqsTriggerEnabledBody> for SetSqsTriggerEnabledBody { + fn from(value: &SetSqsTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetThresholdAlertBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub threshold_alert_amount: Option, + } + impl From<&SetThresholdAlertBody> for SetThresholdAlertBody { + fn from(value: &SetThresholdAlertBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetWebsocketTriggerEnabledBody { + pub enabled: bool, + } + impl From<&SetWebsocketTriggerEnabledBody> for SetWebsocketTriggerEnabledBody { + fn from(value: &SetWebsocketTriggerEnabledBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SetWorkspaceEncryptionKeyBody { + pub new_key: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_reencrypt: Option, + } + impl From<&SetWorkspaceEncryptionKeyBody> for SetWorkspaceEncryptionKeyBody { + fn from(value: &SetWorkspaceEncryptionKeyBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlackToken { + pub access_token: String, + pub bot: SlackTokenBot, + pub team_id: String, + pub team_name: String, + } + impl From<&SlackToken> for SlackToken { + fn from(value: &SlackToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlackTokenBot { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bot_access_token: Option, + } + impl From<&SlackTokenBot> for SlackTokenBot { + fn from(value: &SlackTokenBot) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Slot { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + impl From<&Slot> for Slot { + fn from(value: &Slot) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlotList { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub active: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slot_name: Option, + } + impl From<&SlotList> for SlotList { + fn from(value: &SlotList) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SqsTrigger { + pub aws_resource_path: String, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub queue_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + } + impl From<&SqsTrigger> for SqsTrigger { + fn from(value: &SqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct StarBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub favorite_kind: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + } + impl From<&StarBody> for StarBody { + fn from(value: &StarBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum StarBodyFavoriteKind { + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "script")] + Script, + #[serde(rename = "raw_app")] + RawApp, + } + impl From<&StarBodyFavoriteKind> for StarBodyFavoriteKind { + fn from(value: &StarBodyFavoriteKind) -> Self { + value.clone() + } + } + impl ToString for StarBodyFavoriteKind { + fn to_string(&self) -> String { + match *self { + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Script => "script".to_string(), + Self::RawApp => "raw_app".to_string(), + } + } + } + impl std::str::FromStr for StarBodyFavoriteKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "script" => Ok(Self::Script), + "raw_app" => Ok(Self::RawApp), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for StarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for StarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for StarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct StaticTransform { + #[serde(rename = "type")] + pub type_: StaticTransformType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&StaticTransform> for StaticTransform { + fn from(value: &StaticTransform) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum StaticTransformType { + #[serde(rename = "javascript")] + Javascript, + } + impl From<&StaticTransformType> for StaticTransformType { + fn from(value: &StaticTransformType) -> Self { + value.clone() + } + } + impl ToString for StaticTransformType { + fn to_string(&self) -> String { + match *self { + Self::Javascript => "javascript".to_string(), + } + } + } + impl std::str::FromStr for StaticTransformType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "javascript" => Ok(Self::Javascript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for StaticTransformType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for StaticTransformType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for StaticTransformType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TableToTrack(pub Vec); + impl std::ops::Deref for TableToTrack { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } + } + impl From for Vec { + fn from(value: TableToTrack) -> Self { + value.0 + } + } + impl From<&TableToTrack> for TableToTrack { + fn from(value: &TableToTrack) -> Self { + value.clone() + } + } + impl From> for TableToTrack { + fn from(value: Vec) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TableToTrackItem { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub columns_name: Vec, + pub table_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub where_clause: Option, + } + impl From<&TableToTrackItem> for TableToTrackItem { + fn from(value: &TableToTrackItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TeamInfo { + ///List of channels within the team + pub channels: Vec, + ///The unique identifier of the Microsoft Teams team + pub team_id: String, + ///The display name of the Microsoft Teams team + pub team_name: String, + } + impl From<&TeamInfo> for TeamInfo { + fn from(value: &TeamInfo) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TemplateScript { + pub language: Language, + pub postgres_resource_path: String, + pub relations: Vec, + } + impl From<&TemplateScript> for TemplateScript { + fn from(value: &TemplateScript) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestCriticalChannelsBodyItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slack_channel: Option, + } + impl From<&TestCriticalChannelsBodyItem> for TestCriticalChannelsBodyItem { + fn from(value: &TestCriticalChannelsBodyItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestKafkaConnectionBody { + pub connection: std::collections::HashMap, + } + impl From<&TestKafkaConnectionBody> for TestKafkaConnectionBody { + fn from(value: &TestKafkaConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestLicenseKeyBody { + pub license_key: String, + } + impl From<&TestLicenseKeyBody> for TestLicenseKeyBody { + fn from(value: &TestLicenseKeyBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestMqttConnectionBody { + pub connection: std::collections::HashMap, + } + impl From<&TestMqttConnectionBody> for TestMqttConnectionBody { + fn from(value: &TestMqttConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestNatsConnectionBody { + pub connection: std::collections::HashMap, + } + impl From<&TestNatsConnectionBody> for TestNatsConnectionBody { + fn from(value: &TestNatsConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestPostgresConnectionBody { + pub database: String, + } + impl From<&TestPostgresConnectionBody> for TestPostgresConnectionBody { + fn from(value: &TestPostgresConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestSmtpBody { + pub smtp: TestSmtpBodySmtp, + pub to: String, + } + impl From<&TestSmtpBody> for TestSmtpBody { + fn from(value: &TestSmtpBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestSmtpBodySmtp { + pub disable_tls: bool, + pub from: String, + pub host: String, + pub password: String, + pub port: i64, + pub tls_implicit: bool, + pub username: String, + } + impl From<&TestSmtpBodySmtp> for TestSmtpBodySmtp { + fn from(value: &TestSmtpBodySmtp) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestSqsConnectionBody { + pub connection: std::collections::HashMap, + } + impl From<&TestSqsConnectionBody> for TestSqsConnectionBody { + fn from(value: &TestSqsConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TestWebsocketConnectionBody { + pub can_return_message: bool, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&TestWebsocketConnectionBody> for TestWebsocketConnectionBody { + fn from(value: &TestWebsocketConnectionBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TimeseriesMetric { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metric_id: Option, + pub values: Vec, + } + impl From<&TimeseriesMetric> for TimeseriesMetric { + fn from(value: &TimeseriesMetric) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ToggleWorkspaceErrorHandlerForFlowBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub muted: Option, + } + impl From<&ToggleWorkspaceErrorHandlerForFlowBody> + for ToggleWorkspaceErrorHandlerForFlowBody { + fn from(value: &ToggleWorkspaceErrorHandlerForFlowBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ToggleWorkspaceErrorHandlerForScriptBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub muted: Option, + } + impl From<&ToggleWorkspaceErrorHandlerForScriptBody> + for ToggleWorkspaceErrorHandlerForScriptBody { + fn from(value: &ToggleWorkspaceErrorHandlerForScriptBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TokenResponse { + pub access_token: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_in: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scope: Vec, + } + impl From<&TokenResponse> for TokenResponse { + fn from(value: &TokenResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggerExtraProperty { + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub email: String, + pub extra_perms: std::collections::HashMap, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub workspace_id: String, + } + impl From<&TriggerExtraProperty> for TriggerExtraProperty { + fn from(value: &TriggerExtraProperty) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggersCount { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub http_routes_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kafka_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mqtt_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nats_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub postgres_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub primary_schedule: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sqs_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub webhook_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub websocket_count: Option, + } + impl From<&TriggersCount> for TriggersCount { + fn from(value: &TriggersCount) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggersCountPrimarySchedule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule: Option, + } + impl From<&TriggersCountPrimarySchedule> for TriggersCountPrimarySchedule { + fn from(value: &TriggersCountPrimarySchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TruncatedToken { + pub created_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + pub last_used_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scopes: Vec, + pub token_prefix: String, + } + impl From<&TruncatedToken> for TruncatedToken { + fn from(value: &TruncatedToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UnstarBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub favorite_kind: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + } + impl From<&UnstarBody> for UnstarBody { + fn from(value: &UnstarBody) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum UnstarBodyFavoriteKind { + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "script")] + Script, + #[serde(rename = "raw_app")] + RawApp, + } + impl From<&UnstarBodyFavoriteKind> for UnstarBodyFavoriteKind { + fn from(value: &UnstarBodyFavoriteKind) -> Self { + value.clone() + } + } + impl ToString for UnstarBodyFavoriteKind { + fn to_string(&self) -> String { + match *self { + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Script => "script".to_string(), + Self::RawApp => "raw_app".to_string(), + } + } + } + impl std::str::FromStr for UnstarBodyFavoriteKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "script" => Ok(Self::Script), + "raw_app" => Ok(Self::RawApp), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for UnstarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for UnstarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for UnstarBodyFavoriteKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateAppBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub policy: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&UpdateAppBody> for UpdateAppBody { + fn from(value: &UpdateAppBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateAppHistoryBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + } + impl From<&UpdateAppHistoryBody> for UpdateAppHistoryBody { + fn from(value: &UpdateAppHistoryBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateFlowBody { + #[serde(flatten)] + pub open_flow_w_path: OpenFlowWPath, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + } + impl From<&UpdateFlowBody> for UpdateFlowBody { + fn from(value: &UpdateFlowBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateFlowHistoryBody { + pub deployment_msg: String, + } + impl From<&UpdateFlowHistoryBody> for UpdateFlowHistoryBody { + fn from(value: &UpdateFlowHistoryBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateFolderBody { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub owners: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&UpdateFolderBody> for UpdateFolderBody { + fn from(value: &UpdateFolderBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateGroupBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&UpdateGroupBody> for UpdateGroupBody { + fn from(value: &UpdateGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateInput { + pub id: String, + pub is_public: bool, + pub name: String, + } + impl From<&UpdateInput> for UpdateInput { + fn from(value: &UpdateInput) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateInstanceGroupBody { + pub new_summary: String, + } + impl From<&UpdateInstanceGroupBody> for UpdateInstanceGroupBody { + fn from(value: &UpdateInstanceGroupBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateRawAppBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&UpdateRawAppBody> for UpdateRawAppBody { + fn from(value: &UpdateRawAppBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateResourceValueBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&UpdateResourceValueBody> for UpdateResourceValueBody { + fn from(value: &UpdateResourceValueBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateScriptHistoryBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + } + impl From<&UpdateScriptHistoryBody> for UpdateScriptHistoryBody { + fn from(value: &UpdateScriptHistoryBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateTutorialProgressBody { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub progress: Option, + } + impl From<&UpdateTutorialProgressBody> for UpdateTutorialProgressBody { + fn from(value: &UpdateTutorialProgressBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UploadFilePart { + pub part_number: i64, + pub tag: String, + } + impl From<&UploadFilePart> for UploadFilePart { + fn from(value: &UploadFilePart) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UploadS3FileFromAppResponse { + pub delete_token: String, + pub file_key: String, + } + impl From<&UploadS3FileFromAppResponse> for UploadS3FileFromAppResponse { + fn from(value: &UploadS3FileFromAppResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct User { + pub created_at: chrono::DateTime, + pub disabled: bool, + pub email: String, + pub folders: Vec, + pub folders_owners: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub groups: Vec, + pub is_admin: bool, + pub is_super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + pub operator: bool, + pub username: String, + } + impl From<&User> for User { + fn from(value: &User) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserUsage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub executions: Option, + } + impl From<&UserUsage> for UserUsage { + fn from(value: &UserUsage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserWorkspaceList { + pub email: String, + pub workspaces: Vec, + } + impl From<&UserWorkspaceList> for UserWorkspaceList { + fn from(value: &UserWorkspaceList) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserWorkspaceListWorkspacesItem { + pub color: String, + pub id: String, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator_settings: Option, + pub username: String, + } + impl From<&UserWorkspaceListWorkspacesItem> for UserWorkspaceListWorkspacesItem { + fn from(value: &UserWorkspaceListWorkspacesItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WebsocketTrigger { + pub can_return_message: bool, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&WebsocketTrigger> for WebsocketTrigger { + fn from(value: &WebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&WebsocketTriggerFiltersItem> for WebsocketTriggerFiltersItem { + fn from(value: &WebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum WebsocketTriggerInitialMessage { + #[serde(rename = "raw_message")] + RawMessage(String), + #[serde(rename = "runnable_result")] + RunnableResult { args: ScriptArgs, is_flow: bool, path: String }, + } + impl From<&WebsocketTriggerInitialMessage> for WebsocketTriggerInitialMessage { + fn from(value: &WebsocketTriggerInitialMessage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WhileloopFlow { + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + pub skip_failures: bool, + #[serde(rename = "type")] + pub type_: WhileloopFlowType, + } + impl From<&WhileloopFlow> for WhileloopFlow { + fn from(value: &WhileloopFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WhileloopFlowType { + #[serde(rename = "forloopflow")] + Forloopflow, + } + impl From<&WhileloopFlowType> for WhileloopFlowType { + fn from(value: &WhileloopFlowType) -> Self { + value.clone() + } + } + impl ToString for WhileloopFlowType { + fn to_string(&self) -> String { + match *self { + Self::Forloopflow => "forloopflow".to_string(), + } + } + } + impl std::str::FromStr for WhileloopFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "forloopflow" => Ok(Self::Forloopflow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillFileMetadata { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_modified: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub size_in_bytes: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version_id: Option, + } + impl From<&WindmillFileMetadata> for WindmillFileMetadata { + fn from(value: &WindmillFileMetadata) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillFilePreview { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content: Option, + pub content_type: WindmillFilePreviewContentType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub msg: Option, + } + impl From<&WindmillFilePreview> for WindmillFilePreview { + fn from(value: &WindmillFilePreview) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WindmillFilePreviewContentType { + RawText, + Csv, + Parquet, + Unknown, + } + impl From<&WindmillFilePreviewContentType> for WindmillFilePreviewContentType { + fn from(value: &WindmillFilePreviewContentType) -> Self { + value.clone() + } + } + impl ToString for WindmillFilePreviewContentType { + fn to_string(&self) -> String { + match *self { + Self::RawText => "RawText".to_string(), + Self::Csv => "Csv".to_string(), + Self::Parquet => "Parquet".to_string(), + Self::Unknown => "Unknown".to_string(), + } + } + } + impl std::str::FromStr for WindmillFilePreviewContentType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "RawText" => Ok(Self::RawText), + "Csv" => Ok(Self::Csv), + "Parquet" => Ok(Self::Parquet), + "Unknown" => Ok(Self::Unknown), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillLargeFile { + pub s3: String, + } + impl From<&WindmillLargeFile> for WindmillLargeFile { + fn from(value: &WindmillLargeFile) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkerPing { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub custom_tags: Vec, + pub ip: String, + pub jobs_executed: i64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_job_workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_ping: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory_usage: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_15s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_30m: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_5m: Option, + pub started_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub vcpus: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wm_memory_usage: Option, + pub wm_version: String, + pub worker: String, + pub worker_group: String, + pub worker_instance: String, + } + impl From<&WorkerPing> for WorkerPing { + fn from(value: &WorkerPing) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowStatus { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub duration_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scheduled_for: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + } + impl From<&WorkflowStatus> for WorkflowStatus { + fn from(value: &WorkflowStatus) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowStatusRecord( + pub std::collections::HashMap, + ); + impl std::ops::Deref for WorkflowStatusRecord { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From + for std::collections::HashMap { + fn from(value: WorkflowStatusRecord) -> Self { + value.0 + } + } + impl From<&WorkflowStatusRecord> for WorkflowStatusRecord { + fn from(value: &WorkflowStatusRecord) -> Self { + value.clone() + } + } + impl From> + for WorkflowStatusRecord { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowTask { + pub args: ScriptArgs, + } + impl From<&WorkflowTask> for WorkflowTask { + fn from(value: &WorkflowTask) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Workspace { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub domain: Option, + pub id: String, + pub name: String, + pub owner: String, + } + impl From<&Workspace> for Workspace { + fn from(value: &Workspace) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceDefaultScripts { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub default_script_content: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub hidden: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub order: Vec, + } + impl From<&WorkspaceDefaultScripts> for WorkspaceDefaultScripts { + fn from(value: &WorkspaceDefaultScripts) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceDeployUiSettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_type: Vec, + } + impl From<&WorkspaceDeployUiSettings> for WorkspaceDeployUiSettings { + fn from(value: &WorkspaceDeployUiSettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WorkspaceDeployUiSettingsIncludeTypeItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "trigger")] + Trigger, + } + impl From<&WorkspaceDeployUiSettingsIncludeTypeItem> + for WorkspaceDeployUiSettingsIncludeTypeItem { + fn from(value: &WorkspaceDeployUiSettingsIncludeTypeItem) -> Self { + value.clone() + } + } + impl ToString for WorkspaceDeployUiSettingsIncludeTypeItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Trigger => "trigger".to_string(), + } + } + } + impl std::str::FromStr for WorkspaceDeployUiSettingsIncludeTypeItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "trigger" => Ok(Self::Trigger), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceGetCriticalAlertsResponse { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub alerts: Vec, + ///Total number of pages based on the page size. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub total_pages: Option, + ///Total number of rows matching the query. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub total_rows: Option, + } + impl From<&WorkspaceGetCriticalAlertsResponse> + for WorkspaceGetCriticalAlertsResponse { + fn from(value: &WorkspaceGetCriticalAlertsResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceGitSyncSettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_type: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub repositories: Vec, + } + impl From<&WorkspaceGitSyncSettings> for WorkspaceGitSyncSettings { + fn from(value: &WorkspaceGitSyncSettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WorkspaceGitSyncSettingsIncludeTypeItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "resourcetype")] + Resourcetype, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "user")] + User, + #[serde(rename = "group")] + Group, + } + impl From<&WorkspaceGitSyncSettingsIncludeTypeItem> + for WorkspaceGitSyncSettingsIncludeTypeItem { + fn from(value: &WorkspaceGitSyncSettingsIncludeTypeItem) -> Self { + value.clone() + } + } + impl ToString for WorkspaceGitSyncSettingsIncludeTypeItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Folder => "folder".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Resourcetype => "resourcetype".to_string(), + Self::Schedule => "schedule".to_string(), + Self::User => "user".to_string(), + Self::Group => "group".to_string(), + } + } + } + impl std::str::FromStr for WorkspaceGitSyncSettingsIncludeTypeItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "folder" => Ok(Self::Folder), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "resourcetype" => Ok(Self::Resourcetype), + "schedule" => Ok(Self::Schedule), + "user" => Ok(Self::User), + "group" => Ok(Self::Group), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceInvite { + pub email: String, + pub is_admin: bool, + pub operator: bool, + pub workspace_id: String, + } + impl From<&WorkspaceInvite> for WorkspaceInvite { + fn from(value: &WorkspaceInvite) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceMuteCriticalAlertsUiBody { + ///Whether critical alerts should be muted. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mute_critical_alerts: Option, + } + impl From<&WorkspaceMuteCriticalAlertsUiBody> for WorkspaceMuteCriticalAlertsUiBody { + fn from(value: &WorkspaceMuteCriticalAlertsUiBody) -> Self { + value.clone() + } + } + pub mod defaults { + pub(super) fn default_bool() -> bool { + V + } + } +} +#[derive(Clone, Debug)] +/**Client for Windmill API + +Version: 1.478.1*/ +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} +impl Client { + /// Create a new client. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new(baseurl: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] + let client = { + let dur = std::time::Duration::from_secs(15); + reqwest::ClientBuilder::new().connect_timeout(dur).timeout(dur) + }; + #[cfg(target_arch = "wasm32")] + let client = reqwest::ClientBuilder::new(); + Self::new_with_client(baseurl, client.build().unwrap()) + } + /// Construct a new client with an existing `reqwest::Client`, + /// allowing more control over its configuration. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + /// Get the base URL to which requests are made. + pub fn baseurl(&self) -> &String { + &self.baseurl + } + /// Get the internal `reqwest::Client` used to make requests. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + /// Get the version of this API. + /// + /// This string is pulled directly from the source OpenAPI + /// document and may be in any format the API selects. + pub fn api_version(&self) -> &'static str { + "1.478.1" + } +} +impl Client { + /**get backend version + +Sends a `GET` request to `/version` + +*/ + pub async fn backend_version<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/version", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**is backend up to date + +Sends a `GET` request to `/uptodate` + +*/ + pub async fn backend_uptodate<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/uptodate", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get license id + +Sends a `GET` request to `/ee_license` + +*/ + pub async fn get_license_id<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/ee_license", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get openapi yaml spec + +Sends a `GET` request to `/openapi.yaml` + +*/ + pub async fn get_open_api_yaml<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/openapi.yaml", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get audit log (requires admin privilege) + +Sends a `GET` request to `/w/{workspace}/audit/get/{id}` + +*/ + pub async fn get_audit_log<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/audit/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list audit logs (requires admin privilege) + +Sends a `GET` request to `/w/{workspace}/audit/list` + +Arguments: +- `workspace` +- `action_kind`: filter on type of operation +- `after`: filter on created after (exclusive) timestamp +- `all_workspaces`: get audit logs for all workspaces +- `before`: filter on started before (inclusive) timestamp +- `exclude_operations`: comma separated list of operations to exclude +- `operation`: filter on exact or prefix name of operation +- `operations`: comma separated list of exact operations to include +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +- `resource`: filter on exact or prefix name of resource +- `username`: filter on exact username of user +*/ + pub async fn list_audit_logs<'a>( + &'a self, + workspace: &'a str, + action_kind: Option, + after: Option<&'a chrono::DateTime>, + all_workspaces: Option, + before: Option<&'a chrono::DateTime>, + exclude_operations: Option<&'a str>, + operation: Option<&'a str>, + operations: Option<&'a str>, + page: Option, + per_page: Option, + resource: Option<&'a str>, + username: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/audit/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(11usize); + if let Some(v) = &action_kind { + query.push(("action_kind", v.to_string())); + } + if let Some(v) = &after { + query.push(("after", v.to_string())); + } + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &before { + query.push(("before", v.to_string())); + } + if let Some(v) = &exclude_operations { + query.push(("exclude_operations", v.to_string())); + } + if let Some(v) = &operation { + query.push(("operation", v.to_string())); + } + if let Some(v) = &operations { + query.push(("operations", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &resource { + query.push(("resource", v.to_string())); + } + if let Some(v) = &username { + query.push(("username", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**login with password + +Sends a `POST` request to `/auth/login` + +Arguments: +- `body`: credentials +*/ + pub async fn login<'a>( + &'a self, + body: &'a types::Login, + ) -> Result, Error<()>> { + let url = format!("{}/auth/login", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**logout + +Sends a `POST` request to `/auth/logout` + +*/ + pub async fn logout<'a>(&'a self) -> Result, Error<()>> { + let url = format!("{}/auth/logout", self.baseurl,); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get user (require admin privilege) + +Sends a `GET` request to `/w/{workspace}/users/get/{username}` + +*/ + pub async fn get_user<'a>( + &'a self, + workspace: &'a str, + username: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& username.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update user (require admin privilege) + +Sends a `POST` request to `/w/{workspace}/users/update/{username}` + +Arguments: +- `workspace` +- `username` +- `body`: new user +*/ + pub async fn update_user<'a>( + &'a self, + workspace: &'a str, + username: &'a str, + body: &'a types::EditWorkspaceUser, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& username.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**is owner of path + +Sends a `GET` request to `/w/{workspace}/users/is_owner/{path}` + +*/ + pub async fn is_owner_of_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/is_owner/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set password + +Sends a `POST` request to `/users/setpassword` + +Arguments: +- `body`: set password +*/ + pub async fn set_password<'a>( + &'a self, + body: &'a types::SetPasswordBody, + ) -> Result, Error<()>> { + let url = format!("{}/users/setpassword", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set password for a specific user (require super admin) + +Sends a `POST` request to `/users/set_password_of/{user}` + +Arguments: +- `user` +- `body`: set password +*/ + pub async fn set_password_for_user<'a>( + &'a self, + user: &'a str, + body: &'a types::SetPasswordForUserBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/set_password_of/{}", self.baseurl, encode_path(& user.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set login type for a specific user (require super admin) + +Sends a `POST` request to `/users/set_login_type/{user}` + +Arguments: +- `user` +- `body`: set login type +*/ + pub async fn set_login_type_for_user<'a>( + &'a self, + user: &'a str, + body: &'a types::SetLoginTypeForUserBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/set_login_type/{}", self.baseurl, encode_path(& user.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create user + +Sends a `POST` request to `/users/create` + +Arguments: +- `body`: user info +*/ + pub async fn create_user_globally<'a>( + &'a self, + body: &'a types::CreateUserGloballyBody, + ) -> Result, Error<()>> { + let url = format!("{}/users/create", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global update user (require super admin) + +Sends a `POST` request to `/users/update/{email}` + +Arguments: +- `email` +- `body`: new user info +*/ + pub async fn global_user_update<'a>( + &'a self, + email: &'a str, + body: &'a types::GlobalUserUpdateBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/update/{}", self.baseurl, encode_path(& email.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global username info (require super admin) + +Sends a `GET` request to `/users/username_info/{email}` + +*/ + pub async fn global_username_info<'a>( + &'a self, + email: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/username_info/{}", self.baseurl, encode_path(& email.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global rename user (require super admin) + +Sends a `POST` request to `/users/rename/{email}` + +Arguments: +- `email` +- `body`: new username +*/ + pub async fn global_user_rename<'a>( + &'a self, + email: &'a str, + body: &'a types::GlobalUserRenameBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/rename/{}", self.baseurl, encode_path(& email.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global delete user (require super admin) + +Sends a `DELETE` request to `/users/delete/{email}` + +*/ + pub async fn global_user_delete<'a>( + &'a self, + email: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/delete/{}", self.baseurl, encode_path(& email.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global overwrite users (require super admin and EE) + +Sends a `POST` request to `/users/overwrite` + +Arguments: +- `body`: List of users +*/ + pub async fn global_users_overwrite<'a>( + &'a self, + body: &'a Vec, + ) -> Result, Error<()>> { + let url = format!("{}/users/overwrite", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**global export users (require super admin and EE) + +Sends a `GET` request to `/users/export` + +*/ + pub async fn global_users_export<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/users/export", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete user (require admin privilege) + +Sends a `DELETE` request to `/w/{workspace}/users/delete/{username}` + +*/ + pub async fn delete_user<'a>( + &'a self, + workspace: &'a str, + username: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& username.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all workspaces visible to me + +Sends a `GET` request to `/workspaces/list` + +*/ + pub async fn list_workspaces<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/workspaces/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**is domain allowed for auto invi + +Sends a `GET` request to `/workspaces/allowed_domain_auto_invite` + +*/ + pub async fn is_domain_allowed<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/workspaces/allowed_domain_auto_invite", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all workspaces visible to me with user info + +Sends a `GET` request to `/workspaces/users` + +*/ + pub async fn list_user_workspaces<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/workspaces/users", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all workspaces as super admin (require to be super admin) + +Sends a `GET` request to `/workspaces/list_as_superadmin` + +Arguments: +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_workspaces_as_super_admin<'a>( + &'a self, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!("{}/workspaces/list_as_superadmin", self.baseurl,); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create workspace + +Sends a `POST` request to `/workspaces/create` + +Arguments: +- `body`: new token +*/ + pub async fn create_workspace<'a>( + &'a self, + body: &'a types::CreateWorkspace, + ) -> Result, Error<()>> { + let url = format!("{}/workspaces/create", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists workspace + +Sends a `POST` request to `/workspaces/exists` + +Arguments: +- `body`: id of workspace +*/ + pub async fn exists_workspace<'a>( + &'a self, + body: &'a types::ExistsWorkspaceBody, + ) -> Result, Error<()>> { + let url = format!("{}/workspaces/exists", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists username + +Sends a `POST` request to `/workspaces/exists_username` + +*/ + pub async fn exists_username<'a>( + &'a self, + body: &'a types::ExistsUsernameBody, + ) -> Result, Error<()>> { + let url = format!("{}/workspaces/exists_username", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get global settings + +Sends a `GET` request to `/settings/global/{key}` + +*/ + pub async fn get_global<'a>( + &'a self, + key: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/settings/global/{}", self.baseurl, encode_path(& key.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**post global settings + +Sends a `POST` request to `/settings/global/{key}` + +Arguments: +- `key` +- `body`: value set +*/ + pub async fn set_global<'a>( + &'a self, + key: &'a str, + body: &'a types::SetGlobalBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/settings/global/{}", self.baseurl, encode_path(& key.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get local settings + +Sends a `GET` request to `/settings/local` + +*/ + pub async fn get_local<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/settings/local", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test smtp + +Sends a `POST` request to `/settings/test_smtp` + +Arguments: +- `body`: test smtp payload +*/ + pub async fn test_smtp<'a>( + &'a self, + body: &'a types::TestSmtpBody, + ) -> Result, Error<()>> { + let url = format!("{}/settings/test_smtp", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test critical channels + +Sends a `POST` request to `/settings/test_critical_channels` + +Arguments: +- `body`: test critical channel payload +*/ + pub async fn test_critical_channels<'a>( + &'a self, + body: &'a Vec, + ) -> Result, Error<()>> { + let url = format!("{}/settings/test_critical_channels", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Get all critical alerts + +Sends a `GET` request to `/settings/critical_alerts` + +*/ + pub async fn get_critical_alerts<'a>( + &'a self, + acknowledged: Option, + page: Option, + page_size: Option, + ) -> Result, Error<()>> { + let url = format!("{}/settings/critical_alerts", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &acknowledged { + query.push(("acknowledged", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &page_size { + query.push(("page_size", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Acknowledge a critical alert + +Sends a `POST` request to `/settings/critical_alerts/{id}/acknowledge` + +Arguments: +- `id`: The ID of the critical alert to acknowledge +*/ + pub async fn acknowledge_critical_alert<'a>( + &'a self, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/settings/critical_alerts/{}/acknowledge", self.baseurl, encode_path(& id + .to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Acknowledge all unacknowledged critical alerts + +Sends a `POST` request to `/settings/critical_alerts/acknowledge_all` + +*/ + pub async fn acknowledge_all_critical_alerts<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/settings/critical_alerts/acknowledge_all", self.baseurl,); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test license key + +Sends a `POST` request to `/settings/test_license_key` + +Arguments: +- `body`: test license key +*/ + pub async fn test_license_key<'a>( + &'a self, + body: &'a types::TestLicenseKeyBody, + ) -> Result, Error<()>> { + let url = format!("{}/settings/test_license_key", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test object storage config + +Sends a `POST` request to `/settings/test_object_storage_config` + +Arguments: +- `body`: test object storage config +*/ + pub async fn test_object_storage_config<'a>( + &'a self, + body: &'a std::collections::HashMap, + ) -> Result, Error<()>> { + let url = format!("{}/settings/test_object_storage_config", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**send stats + +Sends a `POST` request to `/settings/send_stats` + +*/ + pub async fn send_stats<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/settings/send_stats", self.baseurl,); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get latest key renewal attempt + +Sends a `GET` request to `/settings/latest_key_renewal_attempt` + +*/ + pub async fn get_latest_key_renewal_attempt<'a>( + &'a self, + ) -> Result< + ResponseValue>, + Error<()>, + > { + let url = format!("{}/settings/latest_key_renewal_attempt", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**renew license key + +Sends a `POST` request to `/settings/renew_license_key` + +*/ + pub async fn renew_license_key<'a>( + &'a self, + license_key: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!("{}/settings/renew_license_key", self.baseurl,); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &license_key { + query.push(("license_key", v.to_string())); + } + let request = self.client.post(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create customer portal session + +Sends a `POST` request to `/settings/customer_portal` + +*/ + pub async fn create_customer_portal_session<'a>( + &'a self, + license_key: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!("{}/settings/customer_portal", self.baseurl,); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &license_key { + query.push(("license_key", v.to_string())); + } + let request = self.client.post(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test metadata + +Sends a `POST` request to `/saml/test_metadata` + +Arguments: +- `body`: test metadata +*/ + pub async fn test_metadata<'a>( + &'a self, + body: &'a str, + ) -> Result, Error<()>> { + let url = format!("{}/saml/test_metadata", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list global settings + +Sends a `GET` request to `/settings/list_global` + +*/ + pub async fn list_global_settings<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/settings/list_global", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get current user email (if logged in) + +Sends a `GET` request to `/users/email` + +*/ + pub async fn get_current_email<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/email", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**refresh the current token + +Sends a `GET` request to `/users/refresh_token` + +*/ + pub async fn refresh_user_token<'a>( + &'a self, + if_expiring_in_less_than_s: Option, + ) -> Result, Error<()>> { + let url = format!("{}/users/refresh_token", self.baseurl,); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &if_expiring_in_less_than_s { + query.push(("if_expiring_in_less_than_s", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get tutorial progress + +Sends a `GET` request to `/users/tutorial_progress` + +*/ + pub async fn get_tutorial_progress<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/tutorial_progress", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update tutorial progress + +Sends a `POST` request to `/users/tutorial_progress` + +Arguments: +- `body`: progress update +*/ + pub async fn update_tutorial_progress<'a>( + &'a self, + body: &'a types::UpdateTutorialProgressBody, + ) -> Result, Error<()>> { + let url = format!("{}/users/tutorial_progress", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**leave instance + +Sends a `POST` request to `/users/leave_instance` + +*/ + pub async fn leave_instance<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/leave_instance", self.baseurl,); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get current usage outside of premium workspaces + +Sends a `GET` request to `/users/usage` + +*/ + pub async fn get_usage<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/usage", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get all runnables in every workspace + +Sends a `GET` request to `/users/all_runnables` + +*/ + pub async fn get_runnable<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/all_runnables", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get current global whoami (if logged in) + +Sends a `GET` request to `/users/whoami` + +*/ + pub async fn global_whoami<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/users/whoami", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all workspace invites + +Sends a `GET` request to `/users/list_invites` + +*/ + pub async fn list_workspace_invites<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/users/list_invites", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**whoami + +Sends a `GET` request to `/w/{workspace}/users/whoami` + +*/ + pub async fn whoami<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/whoami", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**accept invite to workspace + +Sends a `POST` request to `/users/accept_invite` + +Arguments: +- `body`: accept invite +*/ + pub async fn accept_invite<'a>( + &'a self, + body: &'a types::AcceptInviteBody, + ) -> Result, Error<()>> { + let url = format!("{}/users/accept_invite", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**decline invite to workspace + +Sends a `POST` request to `/users/decline_invite` + +Arguments: +- `body`: decline invite +*/ + pub async fn decline_invite<'a>( + &'a self, + body: &'a types::DeclineInviteBody, + ) -> Result, Error<()>> { + let url = format!("{}/users/decline_invite", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**invite user to workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/invite_user` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn invite_user<'a>( + &'a self, + workspace: &'a str, + body: &'a types::InviteUserBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/invite_user", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**add user to workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/add_user` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn add_user<'a>( + &'a self, + workspace: &'a str, + body: &'a types::AddUserBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/add_user", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete user invite + +Sends a `POST` request to `/w/{workspace}/workspaces/delete_invite` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn delete_invite<'a>( + &'a self, + workspace: &'a str, + body: &'a types::DeleteInviteBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/delete_invite", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**archive workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/archive` + +*/ + pub async fn archive_workspace<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/archive", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**unarchive workspace + +Sends a `POST` request to `/workspaces/unarchive/{workspace}` + +*/ + pub async fn unarchive_workspace<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/workspaces/unarchive/{}", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete workspace (require super admin) + +Sends a `DELETE` request to `/workspaces/delete/{workspace}` + +*/ + pub async fn delete_workspace<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/workspaces/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**leave workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/leave` + +*/ + pub async fn leave_workspace<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/leave", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get workspace name + +Sends a `GET` request to `/w/{workspace}/workspaces/get_workspace_name` + +*/ + pub async fn get_workspace_name<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/get_workspace_name", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**change workspace name + +Sends a `POST` request to `/w/{workspace}/workspaces/change_workspace_name` + +*/ + pub async fn change_workspace_name<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ChangeWorkspaceNameBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/change_workspace_name", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**change workspace id + +Sends a `POST` request to `/w/{workspace}/workspaces/change_workspace_id` + +*/ + pub async fn change_workspace_id<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ChangeWorkspaceIdBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/change_workspace_id", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**change workspace id + +Sends a `POST` request to `/w/{workspace}/workspaces/change_workspace_color` + +*/ + pub async fn change_workspace_color<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ChangeWorkspaceColorBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/change_workspace_color", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**whois + +Sends a `GET` request to `/w/{workspace}/users/whois/{username}` + +*/ + pub async fn whois<'a>( + &'a self, + workspace: &'a str, + username: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/whois/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& username.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Update operator settings for a workspace + +Updates the operator settings for a specific workspace. Requires workspace admin privileges. + +Sends a `POST` request to `/w/{workspace}/workspaces/operator_settings` + +*/ + pub async fn update_operator_settings<'a>( + &'a self, + workspace: &'a str, + body: &'a types::OperatorSettings, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/operator_settings", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists email + +Sends a `GET` request to `/users/exists/{email}` + +*/ + pub async fn exists_email<'a>( + &'a self, + email: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/exists/{}", self.baseurl, encode_path(& email.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all users as super admin (require to be super amdin) + +Sends a `GET` request to `/users/list_as_super_admin` + +Arguments: +- `active_only`: filter only active users +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_users_as_super_admin<'a>( + &'a self, + active_only: Option, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!("{}/users/list_as_super_admin", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &active_only { + query.push(("active_only", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list pending invites for a workspace + +Sends a `GET` request to `/w/{workspace}/workspaces/list_pending_invites` + +*/ + pub async fn list_pending_invites<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/list_pending_invites", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get settings + +Sends a `GET` request to `/w/{workspace}/workspaces/get_settings` + +*/ + pub async fn get_settings<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/get_settings", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get deploy to + +Sends a `GET` request to `/w/{workspace}/workspaces/get_deploy_to` + +*/ + pub async fn get_deploy_to<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/get_deploy_to", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get if workspace is premium + +Sends a `GET` request to `/w/{workspace}/workspaces/is_premium` + +*/ + pub async fn get_is_premium<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/is_premium", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get premium info + +Sends a `GET` request to `/w/{workspace}/workspaces/premium_info` + +*/ + pub async fn get_premium_info<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/premium_info", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set automatic billing + +Sends a `POST` request to `/w/{workspace}/workspaces/set_automatic_billing` + +Arguments: +- `workspace` +- `body`: automatic billing +*/ + pub async fn set_automatic_billing<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetAutomaticBillingBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/set_automatic_billing", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get threshold alert info + +Sends a `GET` request to `/w/{workspace}/workspaces/threshold_alert` + +*/ + pub async fn get_threshold_alert<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/threshold_alert", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set threshold alert info + +Sends a `POST` request to `/w/{workspace}/workspaces/threshold_alert` + +Arguments: +- `workspace` +- `body`: threshold alert info +*/ + pub async fn set_threshold_alert<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetThresholdAlertBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/threshold_alert", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit slack command + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_slack_command` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn edit_slack_command<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditSlackCommandBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_slack_command", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit teams command + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_teams_command` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn edit_teams_command<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditTeamsCommandBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_teams_command", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list available teams ids + +Sends a `GET` request to `/w/{workspace}/workspaces/available_teams_ids` + +*/ + pub async fn list_available_teams_ids<'a>( + &'a self, + workspace: &'a str, + ) -> Result< + ResponseValue>, + Error<()>, + > { + let url = format!( + "{}/w/{}/workspaces/available_teams_ids", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list available teams channels + +Sends a `GET` request to `/w/{workspace}/workspaces/available_teams_channels` + +*/ + pub async fn list_available_teams_channels<'a>( + &'a self, + workspace: &'a str, + ) -> Result< + ResponseValue>, + Error<()>, + > { + let url = format!( + "{}/w/{}/workspaces/available_teams_channels", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**connect teams + +Sends a `POST` request to `/w/{workspace}/workspaces/connect_teams` + +Arguments: +- `workspace` +- `body`: connect teams +*/ + pub async fn connect_teams<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ConnectTeamsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/connect_teams", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run a job that sends a message to Slack + +Sends a `POST` request to `/w/{workspace}/workspaces/run_slack_message_test_job` + +Arguments: +- `workspace` +- `body`: path to hub script to run and its corresponding args +*/ + pub async fn run_slack_message_test_job<'a>( + &'a self, + workspace: &'a str, + body: &'a types::RunSlackMessageTestJobBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/run_slack_message_test_job", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run a job that sends a message to Teams + +Sends a `POST` request to `/w/{workspace}/workspaces/run_teams_message_test_job` + +Arguments: +- `workspace` +- `body`: path to hub script to run and its corresponding args +*/ + pub async fn run_teams_message_test_job<'a>( + &'a self, + workspace: &'a str, + body: &'a types::RunTeamsMessageTestJobBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/run_teams_message_test_job", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit deploy to + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_deploy_to` + +*/ + pub async fn edit_deploy_to<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditDeployToBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_deploy_to", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit auto invite + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_auto_invite` + +Arguments: +- `workspace` +- `body`: WorkspaceInvite +*/ + pub async fn edit_auto_invite<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditAutoInviteBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_auto_invite", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit webhook + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_webhook` + +Arguments: +- `workspace` +- `body`: WorkspaceWebhook +*/ + pub async fn edit_webhook<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditWebhookBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_webhook", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit copilot config + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_copilot_config` + +Arguments: +- `workspace` +- `body`: WorkspaceCopilotConfig +*/ + pub async fn edit_copilot_config<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditCopilotConfigBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_copilot_config", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get copilot info + +Sends a `GET` request to `/w/{workspace}/workspaces/get_copilot_info` + +*/ + pub async fn get_copilot_info<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/get_copilot_info", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit error handler + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_error_handler` + +Arguments: +- `workspace` +- `body`: WorkspaceErrorHandler +*/ + pub async fn edit_error_handler<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditErrorHandlerBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_error_handler", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit large file storage settings + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_large_file_storage_config` + +Arguments: +- `workspace` +- `body`: LargeFileStorage info +*/ + pub async fn edit_large_file_storage_config<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditLargeFileStorageConfigBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_large_file_storage_config", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit workspace git sync settings + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_git_sync_config` + +Arguments: +- `workspace` +- `body`: Workspace Git sync settings +*/ + pub async fn edit_workspace_git_sync_config<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditWorkspaceGitSyncConfigBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_git_sync_config", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit workspace deploy ui settings + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_deploy_ui_config` + +Arguments: +- `workspace` +- `body`: Workspace deploy UI settings +*/ + pub async fn edit_workspace_deploy_ui_settings<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditWorkspaceDeployUiSettingsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_deploy_ui_config", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit default app for workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/edit_default_app` + +Arguments: +- `workspace` +- `body`: Workspace default app +*/ + pub async fn edit_workspace_default_app<'a>( + &'a self, + workspace: &'a str, + body: &'a types::EditWorkspaceDefaultAppBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/edit_default_app", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get default scripts for workspace + +Sends a `GET` request to `/w/{workspace}/workspaces/default_scripts` + +*/ + pub async fn get_default_scripts<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/default_scripts", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**edit default scripts for workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/default_scripts` + +Arguments: +- `workspace` +- `body`: Workspace default app +*/ + pub async fn edit_default_scripts<'a>( + &'a self, + workspace: &'a str, + body: &'a types::WorkspaceDefaultScripts, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/default_scripts", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set environment variable + +Sends a `POST` request to `/w/{workspace}/workspaces/set_environment_variable` + +Arguments: +- `workspace` +- `body`: Workspace default app +*/ + pub async fn set_environment_variable<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetEnvironmentVariableBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/set_environment_variable", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**retrieves the encryption key for this workspace + +Sends a `GET` request to `/w/{workspace}/workspaces/encryption_key` + +*/ + pub async fn get_workspace_encryption_key<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/encryption_key", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update the encryption key for this workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/encryption_key` + +Arguments: +- `workspace` +- `body`: New encryption key +*/ + pub async fn set_workspace_encryption_key<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetWorkspaceEncryptionKeyBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/encryption_key", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get default app for workspace + +Sends a `GET` request to `/w/{workspace}/workspaces/default_app` + +*/ + pub async fn get_workspace_default_app<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/default_app", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get large file storage config + +Sends a `GET` request to `/w/{workspace}/workspaces/get_large_file_storage_config` + +*/ + pub async fn get_large_file_storage_config<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/get_large_file_storage_config", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get usage + +Sends a `GET` request to `/w/{workspace}/workspaces/usage` + +*/ + pub async fn get_workspace_usage<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/usage", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get used triggers + +Sends a `GET` request to `/w/{workspace}/workspaces/used_triggers` + +*/ + pub async fn get_used_triggers<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/used_triggers", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list users + +Sends a `GET` request to `/w/{workspace}/users/list` + +*/ + pub async fn list_users<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/users/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list users usage + +Sends a `GET` request to `/w/{workspace}/users/list_usage` + +*/ + pub async fn list_users_usage<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/users/list_usage", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list usernames + +Sends a `GET` request to `/w/{workspace}/users/list_usernames` + +*/ + pub async fn list_usernames<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/users/list_usernames", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get email from username + +Sends a `GET` request to `/w/{workspace}/users/username_to_email/{username}` + +*/ + pub async fn username_to_email<'a>( + &'a self, + workspace: &'a str, + username: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/users/username_to_email/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& username.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create token + +Sends a `POST` request to `/users/tokens/create` + +Arguments: +- `body`: new token +*/ + pub async fn create_token<'a>( + &'a self, + body: &'a types::NewToken, + ) -> Result, Error<()>> { + let url = format!("{}/users/tokens/create", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create token to impersonate a user (require superadmin) + +Sends a `POST` request to `/users/tokens/impersonate` + +Arguments: +- `body`: new token +*/ + pub async fn create_token_impersonate<'a>( + &'a self, + body: &'a types::NewTokenImpersonate, + ) -> Result, Error<()>> { + let url = format!("{}/users/tokens/impersonate", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete token + +Sends a `DELETE` request to `/users/tokens/delete/{token_prefix}` + +*/ + pub async fn delete_token<'a>( + &'a self, + token_prefix: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/users/tokens/delete/{}", self.baseurl, encode_path(& token_prefix + .to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list token + +Sends a `GET` request to `/users/tokens/list` + +Arguments: +- `exclude_ephemeral` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_tokens<'a>( + &'a self, + exclude_ephemeral: Option, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!("{}/users/tokens/list", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &exclude_ephemeral { + query.push(("exclude_ephemeral", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get OIDC token (ee only) + +Sends a `POST` request to `/w/{workspace}/oidc/token/{audience}` + +*/ + pub async fn get_oidc_token<'a>( + &'a self, + workspace: &'a str, + audience: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oidc/token/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& audience.to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create variable + +Sends a `POST` request to `/w/{workspace}/variables/create` + +Arguments: +- `workspace` +- `already_encrypted` +- `body`: new variable +*/ + pub async fn create_variable<'a>( + &'a self, + workspace: &'a str, + already_encrypted: Option, + body: &'a types::CreateVariable, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &already_encrypted { + query.push(("already_encrypted", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**encrypt value + +Sends a `POST` request to `/w/{workspace}/variables/encrypt` + +Arguments: +- `workspace` +- `body`: new variable +*/ + pub async fn encrypt_value<'a>( + &'a self, + workspace: &'a str, + body: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/encrypt", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete variable + +Sends a `DELETE` request to `/w/{workspace}/variables/delete/{path}` + +*/ + pub async fn delete_variable<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update variable + +Sends a `POST` request to `/w/{workspace}/variables/update/{path}` + +Arguments: +- `workspace` +- `path` +- `already_encrypted` +- `body`: updated variable +*/ + pub async fn update_variable<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + already_encrypted: Option, + body: &'a types::EditVariable, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &already_encrypted { + query.push(("already_encrypted", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get variable + +Sends a `GET` request to `/w/{workspace}/variables/get/{path}` + +Arguments: +- `workspace` +- `path` +- `decrypt_secret`: ask to decrypt secret if this variable is secret +(if not secret no effect, default: true) + +- `include_encrypted`: ask to include the encrypted value if secret and decrypt secret is not true (default: false) + +*/ + pub async fn get_variable<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + decrypt_secret: Option, + include_encrypted: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &decrypt_secret { + query.push(("decrypt_secret", v.to_string())); + } + if let Some(v) = &include_encrypted { + query.push(("include_encrypted", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get variable value + +Sends a `GET` request to `/w/{workspace}/variables/get_value/{path}` + +*/ + pub async fn get_variable_value<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/get_value/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does variable exists at path + +Sends a `GET` request to `/w/{workspace}/variables/exists/{path}` + +*/ + pub async fn exists_variable<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/variables/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list variables + +Sends a `GET` request to `/w/{workspace}/variables/list` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_variable<'a>( + &'a self, + workspace: &'a str, + page: Option, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/variables/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list contextual variables + +Sends a `GET` request to `/w/{workspace}/variables/list_contextual` + +*/ + pub async fn list_contextual_variables<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/variables/list_contextual", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Get all critical alerts for this workspace + +Sends a `GET` request to `/w/{workspace}/workspaces/critical_alerts` + +*/ + pub async fn workspace_get_critical_alerts<'a>( + &'a self, + workspace: &'a str, + acknowledged: Option, + page: Option, + page_size: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/critical_alerts", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &acknowledged { + query.push(("acknowledged", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &page_size { + query.push(("page_size", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Acknowledge a critical alert for this workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/critical_alerts/{id}/acknowledge` + +Arguments: +- `workspace` +- `id`: The ID of the critical alert to acknowledge +*/ + pub async fn workspace_acknowledge_critical_alert<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/critical_alerts/{}/acknowledge", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Acknowledge all unacknowledged critical alerts for this workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/critical_alerts/acknowledge_all` + +*/ + pub async fn workspace_acknowledge_all_critical_alerts<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/critical_alerts/acknowledge_all", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Mute critical alert UI for this workspace + +Sends a `POST` request to `/w/{workspace}/workspaces/critical_alerts/mute` + +Arguments: +- `workspace` +- `body`: Boolean flag to mute critical alerts. +*/ + pub async fn workspace_mute_critical_alerts_ui<'a>( + &'a self, + workspace: &'a str, + body: &'a types::WorkspaceMuteCriticalAlertsUiBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/workspaces/critical_alerts/mute", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**login with oauth authorization flow + +Sends a `POST` request to `/oauth/login_callback/{client_name}` + +Arguments: +- `client_name` +- `body`: Partially filled script +*/ + pub async fn login_with_oauth<'a>( + &'a self, + client_name: &'a str, + body: &'a types::LoginWithOauthBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/oauth/login_callback/{}", self.baseurl, encode_path(& client_name + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**connect slack callback + +Sends a `POST` request to `/w/{workspace}/oauth/connect_slack_callback` + +Arguments: +- `workspace` +- `body`: code endpoint +*/ + pub async fn connect_slack_callback<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ConnectSlackCallbackBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/connect_slack_callback", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**connect slack callback instance + +Sends a `POST` request to `/oauth/connect_slack_callback` + +Arguments: +- `body`: code endpoint +*/ + pub async fn connect_slack_callback_instance<'a>( + &'a self, + body: &'a types::ConnectSlackCallbackInstanceBody, + ) -> Result, Error<()>> { + let url = format!("{}/oauth/connect_slack_callback", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**connect callback + +Sends a `POST` request to `/oauth/connect_callback/{client_name}` + +Arguments: +- `client_name` +- `body`: code endpoint +*/ + pub async fn connect_callback<'a>( + &'a self, + client_name: &'a str, + body: &'a types::ConnectCallbackBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/oauth/connect_callback/{}", self.baseurl, encode_path(& client_name + .to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create OAuth account + +Sends a `POST` request to `/w/{workspace}/oauth/create_account` + +Arguments: +- `workspace` +- `body`: code endpoint +*/ + pub async fn create_account<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateAccountBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/create_account", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**refresh token + +Sends a `POST` request to `/w/{workspace}/oauth/refresh_token/{id}` + +Arguments: +- `workspace` +- `id` +- `body`: variable path +*/ + pub async fn refresh_token<'a>( + &'a self, + workspace: &'a str, + id: i64, + body: &'a types::RefreshTokenBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/refresh_token/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**disconnect account + +Sends a `POST` request to `/w/{workspace}/oauth/disconnect/{id}` + +*/ + pub async fn disconnect_account<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/disconnect/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**disconnect slack + +Sends a `POST` request to `/w/{workspace}/oauth/disconnect_slack` + +*/ + pub async fn disconnect_slack<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/disconnect_slack", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**disconnect teams + +Sends a `POST` request to `/w/{workspace}/oauth/disconnect_teams` + +*/ + pub async fn disconnect_teams<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/oauth/disconnect_teams", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list oauth logins + +Sends a `GET` request to `/oauth/list_logins` + +*/ + pub async fn list_o_auth_logins<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/oauth/list_logins", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list oauth connects + +Sends a `GET` request to `/oauth/list_connects` + +*/ + pub async fn list_o_auth_connects<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/oauth/list_connects", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get oauth connect + +Sends a `GET` request to `/oauth/get_connect/{client}` + +Arguments: +- `client`: client name +*/ + pub async fn get_o_auth_connect<'a>( + &'a self, + client: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/oauth/get_connect/{}", self.baseurl, encode_path(& client.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**synchronize Microsoft Teams information (teams/channels) + +Sends a `POST` request to `/teams/sync` + +*/ + pub async fn sync_teams<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/teams/sync", self.baseurl,); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**send update to Microsoft Teams activity + +Respond to a Microsoft Teams activity after a workspace command is run + +Sends a `POST` request to `/teams/activities` + +*/ + pub async fn send_message_to_conversation<'a>( + &'a self, + body: &'a types::SendMessageToConversationBody, + ) -> Result, Error<()>> { + let url = format!("{}/teams/activities", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create resource + +Sends a `POST` request to `/w/{workspace}/resources/create` + +Arguments: +- `workspace` +- `update_if_exists` +- `body`: new resource +*/ + pub async fn create_resource<'a>( + &'a self, + workspace: &'a str, + update_if_exists: Option, + body: &'a types::CreateResource, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &update_if_exists { + query.push(("update_if_exists", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete resource + +Sends a `DELETE` request to `/w/{workspace}/resources/delete/{path}` + +*/ + pub async fn delete_resource<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update resource + +Sends a `POST` request to `/w/{workspace}/resources/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated resource +*/ + pub async fn update_resource<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditResource, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update resource value + +Sends a `POST` request to `/w/{workspace}/resources/update_value/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated resource +*/ + pub async fn update_resource_value<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::UpdateResourceValueBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/update_value/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get resource + +Sends a `GET` request to `/w/{workspace}/resources/get/{path}` + +*/ + pub async fn get_resource<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get resource interpolated (variables and resources are fully unrolled) + +Sends a `GET` request to `/w/{workspace}/resources/get_value_interpolated/{path}` + +Arguments: +- `workspace` +- `path` +- `job_id`: job id +*/ + pub async fn get_resource_value_interpolated<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + job_id: Option<&'a uuid::Uuid>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/get_value_interpolated/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get resource value + +Sends a `GET` request to `/w/{workspace}/resources/get_value/{path}` + +*/ + pub async fn get_resource_value<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/get_value/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does resource exists + +Sends a `GET` request to `/w/{workspace}/resources/exists/{path}` + +*/ + pub async fn exists_resource<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list resources + +Sends a `GET` request to `/w/{workspace}/resources/list` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +- `resource_type`: resource_types to list from, separated by ',', +- `resource_type_exclude`: resource_types to not list from, separated by ',', +*/ + pub async fn list_resource<'a>( + &'a self, + workspace: &'a str, + page: Option, + path_start: Option<&'a str>, + per_page: Option, + resource_type: Option<&'a str>, + resource_type_exclude: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/resources/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &resource_type { + query.push(("resource_type", v.to_string())); + } + if let Some(v) = &resource_type_exclude { + query.push(("resource_type_exclude", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list resources for search + +Sends a `GET` request to `/w/{workspace}/resources/list_search` + +*/ + pub async fn list_search_resource<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/resources/list_search", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list resource names + +Sends a `GET` request to `/w/{workspace}/resources/list_names/{name}` + +*/ + pub async fn list_resource_names<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/resources/list_names/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create resource_type + +Sends a `POST` request to `/w/{workspace}/resources/type/create` + +Arguments: +- `workspace` +- `body`: new resource_type +*/ + pub async fn create_resource_type<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ResourceType, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get map from resource type to format extension + +Sends a `GET` request to `/w/{workspace}/resources/file_resource_type_to_file_ext_map` + +*/ + pub async fn file_resource_type_to_file_ext_map<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/file_resource_type_to_file_ext_map", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete resource_type + +Sends a `DELETE` request to `/w/{workspace}/resources/type/delete/{path}` + +*/ + pub async fn delete_resource_type<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update resource_type + +Sends a `POST` request to `/w/{workspace}/resources/type/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated resource_type +*/ + pub async fn update_resource_type<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditResourceType, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get resource_type + +Sends a `GET` request to `/w/{workspace}/resources/type/get/{path}` + +*/ + pub async fn get_resource_type<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does resource_type exists + +Sends a `GET` request to `/w/{workspace}/resources/type/exists/{path}` + +*/ + pub async fn exists_resource_type<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list resource_types + +Sends a `GET` request to `/w/{workspace}/resources/type/list` + +*/ + pub async fn list_resource_type<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list resource_types names + +Sends a `GET` request to `/w/{workspace}/resources/type/listnames` + +*/ + pub async fn list_resource_type_names<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/resources/type/listnames", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**query resource types by similarity + +Sends a `GET` request to `/w/{workspace}/embeddings/query_resource_types` + +Arguments: +- `workspace` +- `limit`: query limit +- `text`: query text +*/ + pub async fn query_resource_types<'a>( + &'a self, + workspace: &'a str, + limit: Option, + text: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/embeddings/query_resource_types", self.baseurl, encode_path(& + workspace.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + query.push(("text", text.to_string())); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list hub integrations + +Sends a `GET` request to `/integrations/hub/list` + +Arguments: +- `kind`: query integrations kind +*/ + pub async fn list_hub_integrations<'a>( + &'a self, + kind: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!("{}/integrations/hub/list", self.baseurl,); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &kind { + query.push(("kind", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all hub flows + +Sends a `GET` request to `/flows/hub/list` + +*/ + pub async fn list_hub_flows<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/flows/hub/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get hub flow by id + +Sends a `GET` request to `/flows/hub/get/{id}` + +*/ + pub async fn get_hub_flow_by_id<'a>( + &'a self, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/flows/hub/get/{}", self.baseurl, encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all hub apps + +Sends a `GET` request to `/apps/hub/list` + +*/ + pub async fn list_hub_apps<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/apps/hub/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get hub app by id + +Sends a `GET` request to `/apps/hub/get/{id}` + +*/ + pub async fn get_hub_app_by_id<'a>( + &'a self, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/apps/hub/get/{}", self.baseurl, encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get public app by custom path + +Sends a `GET` request to `/apps_u/public_app_by_custom_path/{custom_path}` + +*/ + pub async fn get_public_app_by_custom_path<'a>( + &'a self, + custom_path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/apps_u/public_app_by_custom_path/{}", self.baseurl, encode_path(& + custom_path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get hub script content by path + +Sends a `GET` request to `/scripts/hub/get/{path}` + +*/ + pub async fn get_hub_script_content_by_path<'a>( + &'a self, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/scripts/hub/get/{}", self.baseurl, encode_path(& path.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get full hub script by path + +Sends a `GET` request to `/scripts/hub/get_full/{path}` + +*/ + pub async fn get_hub_script_by_path<'a>( + &'a self, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/scripts/hub/get_full/{}", self.baseurl, encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get top hub scripts + +Sends a `GET` request to `/scripts/hub/top` + +Arguments: +- `app`: query scripts app +- `kind`: query scripts kind +- `limit`: query limit +*/ + pub async fn get_top_hub_scripts<'a>( + &'a self, + app: Option<&'a str>, + kind: Option<&'a str>, + limit: Option, + ) -> Result, Error<()>> { + let url = format!("{}/scripts/hub/top", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &app { + query.push(("app", v.to_string())); + } + if let Some(v) = &kind { + query.push(("kind", v.to_string())); + } + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**query hub scripts by similarity + +Sends a `GET` request to `/embeddings/query_hub_scripts` + +Arguments: +- `app`: query scripts app +- `kind`: query scripts kind +- `limit`: query limit +- `text`: query text +*/ + pub async fn query_hub_scripts<'a>( + &'a self, + app: Option<&'a str>, + kind: Option<&'a str>, + limit: Option, + text: &'a str, + ) -> Result>, Error<()>> { + let url = format!("{}/embeddings/query_hub_scripts", self.baseurl,); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &app { + query.push(("app", v.to_string())); + } + if let Some(v) = &kind { + query.push(("kind", v.to_string())); + } + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + query.push(("text", text.to_string())); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list scripts for search + +Sends a `GET` request to `/w/{workspace}/scripts/list_search` + +*/ + pub async fn list_search_script<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/scripts/list_search", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all scripts + +Sends a `GET` request to `/w/{workspace}/scripts/list` + +Arguments: +- `workspace` +- `created_by`: mask to filter exact matching user creator +- `first_parent_hash`: mask to filter scripts whom first direct parent has exact hash +- `include_draft_only`: (default false) +include scripts that have no deployed version + +- `include_without_main`: (default false) +include scripts without an exported main function + +- `is_template`: (default regardless) +if true show only the templates +if false show only the non templates +if not defined, show all regardless of if the script is a template + +- `kinds`: (default regardless) +script kinds to filter, split by comma + +- `last_parent_hash`: mask to filter scripts whom last parent in the chain has exact hash. +Beware that each script stores only a limited number of parents. Hence +the last parent hash for a script is not necessarily its top-most parent. +To find the top-most parent you will have to jump from last to last hash + until finding the parent + +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `parent_hash`: is the hash present in the array of stored parent hashes for this script. +The same warning applies than for last_parent_hash. A script only store a +limited number of direct parent + +- `path_exact`: mask to filter exact matching path +- `path_start`: mask to filter matching starting path +- `per_page`: number of items to return for a given page (default 30, max 100) +- `show_archived`: (default false) +show only the archived files. +when multiple archived hash share the same path, only the ones with the latest create_at +are +ed. + +- `starred_only`: (default false) +show only the starred items + +- `with_deployment_msg`: (default false) +include deployment message + +*/ + pub async fn list_scripts<'a>( + &'a self, + workspace: &'a str, + created_by: Option<&'a str>, + first_parent_hash: Option<&'a str>, + include_draft_only: Option, + include_without_main: Option, + is_template: Option, + kinds: Option<&'a str>, + last_parent_hash: Option<&'a str>, + order_desc: Option, + page: Option, + parent_hash: Option<&'a str>, + path_exact: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + show_archived: Option, + starred_only: Option, + with_deployment_msg: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/scripts/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(16usize); + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &first_parent_hash { + query.push(("first_parent_hash", v.to_string())); + } + if let Some(v) = &include_draft_only { + query.push(("include_draft_only", v.to_string())); + } + if let Some(v) = &include_without_main { + query.push(("include_without_main", v.to_string())); + } + if let Some(v) = &is_template { + query.push(("is_template", v.to_string())); + } + if let Some(v) = &kinds { + query.push(("kinds", v.to_string())); + } + if let Some(v) = &last_parent_hash { + query.push(("last_parent_hash", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_hash { + query.push(("parent_hash", v.to_string())); + } + if let Some(v) = &path_exact { + query.push(("path_exact", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &show_archived { + query.push(("show_archived", v.to_string())); + } + if let Some(v) = &starred_only { + query.push(("starred_only", v.to_string())); + } + if let Some(v) = &with_deployment_msg { + query.push(("with_deployment_msg", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all scripts paths + +Sends a `GET` request to `/w/{workspace}/scripts/list_paths` + +*/ + pub async fn list_script_paths<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/list_paths", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create draft + +Sends a `POST` request to `/w/{workspace}/drafts/create` + +*/ + pub async fn create_draft<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateDraftBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/drafts/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete draft + +Sends a `DELETE` request to `/w/{workspace}/drafts/delete/{kind}/{path}` + +*/ + pub async fn delete_draft<'a>( + &'a self, + workspace: &'a str, + kind: types::DeleteDraftKind, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/drafts/delete/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& kind.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create script + +Sends a `POST` request to `/w/{workspace}/scripts/create` + +Arguments: +- `workspace` +- `body`: Partially filled script +*/ + pub async fn create_script<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewScript, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Toggle ON and OFF the workspace error handler for a given script + +Sends a `POST` request to `/w/{workspace}/scripts/toggle_workspace_error_handler/p/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: Workspace error handler enabled +*/ + pub async fn toggle_workspace_error_handler_for_script<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::ToggleWorkspaceErrorHandlerForScriptBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/toggle_workspace_error_handler/p/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get all instance custom tags (tags are used to dispatch jobs to different worker groups) + +Sends a `GET` request to `/workers/custom_tags` + +*/ + pub async fn get_custom_tags<'a>( + &'a self, + show_workspace_restriction: Option, + workspace: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!("{}/workers/custom_tags", self.baseurl,); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &show_workspace_restriction { + query.push(("show_workspace_restriction", v.to_string())); + } + if let Some(v) = &workspace { + query.push(("workspace", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get all instance default tags + +Sends a `GET` request to `/workers/get_default_tags` + +*/ + pub async fn ge_default_tags<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/workers/get_default_tags", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**is default tags per workspace + +Sends a `GET` request to `/workers/is_default_tags_per_workspace` + +*/ + pub async fn is_default_tags_per_workspace<'a>( + &'a self, + ) -> Result, Error<()>> { + let url = format!("{}/workers/is_default_tags_per_workspace", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**archive script by path + +Sends a `POST` request to `/w/{workspace}/scripts/archive/p/{path}` + +*/ + pub async fn archive_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/archive/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**archive script by hash + +Sends a `POST` request to `/w/{workspace}/scripts/archive/h/{hash}` + +*/ + pub async fn archive_script_by_hash<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/archive/h/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& hash.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete script by hash (erase content but keep hash, require admin) + +Sends a `POST` request to `/w/{workspace}/scripts/delete/h/{hash}` + +*/ + pub async fn delete_script_by_hash<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/delete/h/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& hash.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete script at a given path (require admin) + +Sends a `POST` request to `/w/{workspace}/scripts/delete/p/{path}` + +Arguments: +- `workspace` +- `path` +- `keep_captures`: keep captures +*/ + pub async fn delete_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + keep_captures: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/delete/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &keep_captures { + query.push(("keep_captures", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get script by path + +Sends a `GET` request to `/w/{workspace}/scripts/get/p/{path}` + +*/ + pub async fn get_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + with_starred_info: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/get/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &with_starred_info { + query.push(("with_starred_info", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get triggers count of script + +Sends a `GET` request to `/w/{workspace}/scripts/get_triggers_count/{path}` + +*/ + pub async fn get_triggers_count_of_script<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/get_triggers_count/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get tokens with script scope + +Sends a `GET` request to `/w/{workspace}/scripts/list_tokens/{path}` + +*/ + pub async fn list_tokens_of_script<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/scripts/list_tokens/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get script by path with draft + +Sends a `GET` request to `/w/{workspace}/scripts/get/draft/{path}` + +*/ + pub async fn get_script_by_path_with_draft<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/get/draft/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get history of a script by path + +Sends a `GET` request to `/w/{workspace}/scripts/history/p/{path}` + +*/ + pub async fn get_script_history_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/scripts/history/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get scripts's latest version (hash) + +Sends a `GET` request to `/w/{workspace}/scripts/get_latest_version/{path}` + +*/ + pub async fn get_script_latest_version<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/get_latest_version/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update history of a script + +Sends a `POST` request to `/w/{workspace}/scripts/history_update/h/{hash}/p/{path}` + +Arguments: +- `workspace` +- `hash` +- `path` +- `body`: Script deployment message +*/ + pub async fn update_script_history<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + path: &'a str, + body: &'a types::UpdateScriptHistoryBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/history_update/h/{}/p/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& hash.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**raw script by path + +Sends a `GET` request to `/w/{workspace}/scripts/raw/p/{path}` + +*/ + pub async fn raw_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/raw/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**raw script by path with a token (mostly used by lsp to be used with import maps to resolve scripts) + +Sends a `GET` request to `/scripts_u/tokened_raw/{workspace}/{token}/{path}` + +*/ + pub async fn raw_script_by_path_tokened<'a>( + &'a self, + workspace: &'a str, + token: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/scripts_u/tokened_raw/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& token.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists script by path + +Sends a `GET` request to `/w/{workspace}/scripts/exists/p/{path}` + +*/ + pub async fn exists_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/exists/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get script by hash + +Sends a `GET` request to `/w/{workspace}/scripts/get/h/{hash}` + +*/ + pub async fn get_script_by_hash<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + with_starred_info: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/get/h/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& hash.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &with_starred_info { + query.push(("with_starred_info", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**raw script by hash + +Sends a `GET` request to `/w/{workspace}/scripts/raw/h/{path}` + +*/ + pub async fn raw_script_by_hash<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/raw/h/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get script deployment status + +Sends a `GET` request to `/w/{workspace}/scripts/deployment_status/h/{hash}` + +*/ + pub async fn get_script_deployment_status<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/deployment_status/h/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& hash.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script by path + +Sends a `POST` request to `/w/{workspace}/jobs/run/p/{path}` + +Arguments: +- `workspace` +- `path` +- `cache_ttl`: Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl +- `invisible_to_owner`: make the run invisible to the the script owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `scheduled_for`: when to schedule this job (leave empty for immediate run) +- `scheduled_in_secs`: schedule the script to execute in the number of seconds starting now +- `skip_preprocessor`: skip the preprocessor +- `tag`: Override the tag to use +- `body`: script args +*/ + pub async fn run_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + cache_ttl: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + scheduled_for: Option<&'a chrono::DateTime>, + scheduled_in_secs: Option, + skip_preprocessor: Option, + tag: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/p/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(8usize); + if let Some(v) = &cache_ttl { + query.push(("cache_ttl", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &scheduled_for { + query.push(("scheduled_for", v.to_string())); + } + if let Some(v) = &scheduled_in_secs { + query.push(("scheduled_in_secs", v.to_string())); + } + if let Some(v) = &skip_preprocessor { + query.push(("skip_preprocessor", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script by path in openai format + +Sends a `POST` request to `/w/{workspace}/jobs/openai_sync/p/{path}` + +Arguments: +- `workspace` +- `path` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `queue_limit`: The maximum size of the queue for which the request would get rejected if that job would push it above that limit + +- `body`: script args +*/ + pub async fn openai_sync_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + include_header: Option<&'a str>, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + queue_limit: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/openai_sync/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &queue_limit { + query.push(("queue_limit", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script by path with get + +Sends a `GET` request to `/w/{workspace}/jobs/run_wait_result/p/{path}` + +Arguments: +- `workspace` +- `path` +- `cache_ttl`: Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `payload`: The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent +`encodeURIComponent(btoa(JSON.stringify({a: 2})))` + +- `queue_limit`: The maximum size of the queue for which the request would get rejected if that job would push it above that limit + +- `tag`: Override the tag to use +*/ + pub async fn run_wait_result_script_by_path_get<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + cache_ttl: Option<&'a str>, + include_header: Option<&'a str>, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + payload: Option<&'a str>, + queue_limit: Option<&'a str>, + tag: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run_wait_result/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &cache_ttl { + query.push(("cache_ttl", v.to_string())); + } + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &payload { + query.push(("payload", v.to_string())); + } + if let Some(v) = &queue_limit { + query.push(("queue_limit", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script by path + +Sends a `POST` request to `/w/{workspace}/jobs/run_wait_result/p/{path}` + +Arguments: +- `workspace` +- `path` +- `cache_ttl`: Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `queue_limit`: The maximum size of the queue for which the request would get rejected if that job would push it above that limit + +- `tag`: Override the tag to use +- `body`: script args +*/ + pub async fn run_wait_result_script_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + cache_ttl: Option<&'a str>, + include_header: Option<&'a str>, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + queue_limit: Option<&'a str>, + tag: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run_wait_result/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(6usize); + if let Some(v) = &cache_ttl { + query.push(("cache_ttl", v.to_string())); + } + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &queue_limit { + query.push(("queue_limit", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run flow by path and wait until completion in openai format + +Sends a `POST` request to `/w/{workspace}/jobs/openai_sync/f/{path}` + +Arguments: +- `workspace` +- `path` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `queue_limit`: The maximum size of the queue for which the request would get rejected if that job would push it above that limit + +- `body`: script args +*/ + pub async fn openai_sync_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + include_header: Option<&'a str>, + job_id: Option<&'a uuid::Uuid>, + queue_limit: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/openai_sync/f/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &queue_limit { + query.push(("queue_limit", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run flow by path and wait until completion + +Sends a `POST` request to `/w/{workspace}/jobs/run_wait_result/f/{path}` + +Arguments: +- `workspace` +- `path` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `queue_limit`: The maximum size of the queue for which the request would get rejected if that job would push it above that limit + +- `body`: script args +*/ + pub async fn run_wait_result_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + include_header: Option<&'a str>, + job_id: Option<&'a uuid::Uuid>, + queue_limit: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run_wait_result/f/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &queue_limit { + query.push(("queue_limit", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job result by id + +Sends a `GET` request to `/w/{workspace}/jobs/result_by_id/{flow_job_id}/{node_id}` + +*/ + pub async fn result_by_id<'a>( + &'a self, + workspace: &'a str, + flow_job_id: &'a str, + node_id: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/result_by_id/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& flow_job_id.to_string()), encode_path(& node_id + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all flow paths + +Sends a `GET` request to `/w/{workspace}/flows/list_paths` + +*/ + pub async fn list_flow_paths<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/list_paths", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list flows for search + +Sends a `GET` request to `/w/{workspace}/flows/list_search` + +*/ + pub async fn list_search_flow<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/flows/list_search", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all flows + +Sends a `GET` request to `/w/{workspace}/flows/list` + +Arguments: +- `workspace` +- `created_by`: mask to filter exact matching user creator +- `include_draft_only`: (default false) +include items that have no deployed version + +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `path_exact`: mask to filter exact matching path +- `path_start`: mask to filter matching starting path +- `per_page`: number of items to return for a given page (default 30, max 100) +- `show_archived`: (default false) +show only the archived files. +when multiple archived hash share the same path, only the ones with the latest create_at +are displayed. + +- `starred_only`: (default false) +show only the starred items + +- `with_deployment_msg`: (default false) +include deployment message + +*/ + pub async fn list_flows<'a>( + &'a self, + workspace: &'a str, + created_by: Option<&'a str>, + include_draft_only: Option, + order_desc: Option, + page: Option, + path_exact: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + show_archived: Option, + starred_only: Option, + with_deployment_msg: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/flows/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(10usize); + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &include_draft_only { + query.push(("include_draft_only", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path_exact { + query.push(("path_exact", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &show_archived { + query.push(("show_archived", v.to_string())); + } + if let Some(v) = &starred_only { + query.push(("starred_only", v.to_string())); + } + if let Some(v) = &with_deployment_msg { + query.push(("with_deployment_msg", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow history by path + +Sends a `GET` request to `/w/{workspace}/flows/history/p/{path}` + +*/ + pub async fn get_flow_history<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/flows/history/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow's latest version + +Sends a `GET` request to `/w/{workspace}/flows/get_latest_version/{path}` + +*/ + pub async fn get_flow_latest_version<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get_latest_version/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list flow paths from workspace runnable + +Sends a `GET` request to `/w/{workspace}/flows/list_paths_from_workspace_runnable/{runnable_kind}/{path}` + +*/ + pub async fn list_flow_paths_from_workspace_runnable<'a>( + &'a self, + workspace: &'a str, + runnable_kind: types::ListFlowPathsFromWorkspaceRunnableRunnableKind, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/flows/list_paths_from_workspace_runnable/{}/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& runnable_kind + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow version + +Sends a `GET` request to `/w/{workspace}/flows/get/v/{version}/p/{path}` + +*/ + pub async fn get_flow_version<'a>( + &'a self, + workspace: &'a str, + version: f64, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get/v/{}/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& version.to_string()), encode_path(& path + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update flow history + +Sends a `POST` request to `/w/{workspace}/flows/history_update/v/{version}/p/{path}` + +Arguments: +- `workspace` +- `version` +- `path` +- `body`: Flow deployment message +*/ + pub async fn update_flow_history<'a>( + &'a self, + workspace: &'a str, + version: f64, + path: &'a str, + body: &'a types::UpdateFlowHistoryBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/history_update/v/{}/p/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& version.to_string()), encode_path(& + path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow by path + +Sends a `GET` request to `/w/{workspace}/flows/get/{path}` + +*/ + pub async fn get_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + with_starred_info: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &with_starred_info { + query.push(("with_starred_info", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow deployment status + +Sends a `GET` request to `/w/{workspace}/flows/deployment_status/p/{path}` + +*/ + pub async fn get_flow_deployment_status<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/deployment_status/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get triggers count of flow + +Sends a `GET` request to `/w/{workspace}/flows/get_triggers_count/{path}` + +*/ + pub async fn get_triggers_count_of_flow<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get_triggers_count/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get tokens with flow scope + +Sends a `GET` request to `/w/{workspace}/flows/list_tokens/{path}` + +*/ + pub async fn list_tokens_of_flow<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/flows/list_tokens/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Toggle ON and OFF the workspace error handler for a given flow + +Sends a `POST` request to `/w/{workspace}/flows/toggle_workspace_error_handler/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: Workspace error handler enabled +*/ + pub async fn toggle_workspace_error_handler_for_flow<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::ToggleWorkspaceErrorHandlerForFlowBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/toggle_workspace_error_handler/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow by path with draft + +Sends a `GET` request to `/w/{workspace}/flows/get/draft/{path}` + +*/ + pub async fn get_flow_by_path_with_draft<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get/draft/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists flow by path + +Sends a `GET` request to `/w/{workspace}/flows/exists/{path}` + +*/ + pub async fn exists_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create flow + +Sends a `POST` request to `/w/{workspace}/flows/create` + +Arguments: +- `workspace` +- `body`: Partially filled flow +*/ + pub async fn create_flow<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateFlowBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update flow + +Sends a `POST` request to `/w/{workspace}/flows/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: Partially filled flow +*/ + pub async fn update_flow<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::UpdateFlowBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**archive flow by path + +Sends a `POST` request to `/w/{workspace}/flows/archive/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: archiveFlow +*/ + pub async fn archive_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::ArchiveFlowByPathBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/archive/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete flow by path + +Sends a `DELETE` request to `/w/{workspace}/flows/delete/{path}` + +Arguments: +- `workspace` +- `path` +- `keep_captures`: keep captures +*/ + pub async fn delete_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + keep_captures: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &keep_captures { + query.push(("keep_captures", v.to_string())); + } + let request = self.client.delete(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all raw apps + +Sends a `GET` request to `/w/{workspace}/raw_apps/list` + +Arguments: +- `workspace` +- `created_by`: mask to filter exact matching user creator +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `path_exact`: mask to filter exact matching path +- `path_start`: mask to filter matching starting path +- `per_page`: number of items to return for a given page (default 30, max 100) +- `starred_only`: (default false) +show only the starred items + +*/ + pub async fn list_raw_apps<'a>( + &'a self, + workspace: &'a str, + created_by: Option<&'a str>, + order_desc: Option, + page: Option, + path_exact: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + starred_only: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/raw_apps/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path_exact { + query.push(("path_exact", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &starred_only { + query.push(("starred_only", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does an app exisst at path + +Sends a `GET` request to `/w/{workspace}/raw_apps/exists/{path}` + +*/ + pub async fn exists_raw_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/raw_apps/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app by path + +Sends a `GET` request to `/w/{workspace}/apps/get_data/{version}/{path}` + +*/ + pub async fn get_raw_app_data<'a>( + &'a self, + workspace: &'a str, + version: f64, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get_data/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& version.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list apps for search + +Sends a `GET` request to `/w/{workspace}/apps/list_search` + +*/ + pub async fn list_search_app<'a>( + &'a self, + workspace: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/apps/list_search", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all apps + +Sends a `GET` request to `/w/{workspace}/apps/list` + +Arguments: +- `workspace` +- `created_by`: mask to filter exact matching user creator +- `include_draft_only`: (default false) +include items that have no deployed version + +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `path_exact`: mask to filter exact matching path +- `path_start`: mask to filter matching starting path +- `per_page`: number of items to return for a given page (default 30, max 100) +- `starred_only`: (default false) +show only the starred items + +- `with_deployment_msg`: (default false) +include deployment message + +*/ + pub async fn list_apps<'a>( + &'a self, + workspace: &'a str, + created_by: Option<&'a str>, + include_draft_only: Option, + order_desc: Option, + page: Option, + path_exact: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + starred_only: Option, + with_deployment_msg: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/apps/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(9usize); + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &include_draft_only { + query.push(("include_draft_only", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path_exact { + query.push(("path_exact", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &starred_only { + query.push(("starred_only", v.to_string())); + } + if let Some(v) = &with_deployment_msg { + query.push(("with_deployment_msg", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create app + +Sends a `POST` request to `/w/{workspace}/apps/create` + +Arguments: +- `workspace` +- `body`: new app +*/ + pub async fn create_app<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateAppBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does an app exisst at path + +Sends a `GET` request to `/w/{workspace}/apps/exists/{path}` + +*/ + pub async fn exists_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/exists/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app by path + +Sends a `GET` request to `/w/{workspace}/apps/get/p/{path}` + +*/ + pub async fn get_app_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + with_starred_info: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get/p/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &with_starred_info { + query.push(("with_starred_info", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app lite by path + +Sends a `GET` request to `/w/{workspace}/apps/get/lite/{path}` + +*/ + pub async fn get_app_lite_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get/lite/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app by path with draft + +Sends a `GET` request to `/w/{workspace}/apps/get/draft/{path}` + +*/ + pub async fn get_app_by_path_with_draft<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get/draft/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app history by path + +Sends a `GET` request to `/w/{workspace}/apps/history/p/{path}` + +*/ + pub async fn get_app_history_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/apps/history/p/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get apps's latest version + +Sends a `GET` request to `/w/{workspace}/apps/get_latest_version/{path}` + +*/ + pub async fn get_app_latest_version<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get_latest_version/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update app history + +Sends a `POST` request to `/w/{workspace}/apps/history_update/a/{id}/v/{version}` + +Arguments: +- `workspace` +- `id` +- `version` +- `body`: App deployment message +*/ + pub async fn update_app_history<'a>( + &'a self, + workspace: &'a str, + id: i64, + version: i64, + body: &'a types::UpdateAppHistoryBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/history_update/a/{}/v/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& id.to_string()), encode_path(& version + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get public app by secret + +Sends a `GET` request to `/w/{workspace}/apps_u/public_app/{path}` + +*/ + pub async fn get_public_app_by_secret<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps_u/public_app/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get public resource + +Sends a `GET` request to `/w/{workspace}/apps_u/public_resource/{path}` + +*/ + pub async fn get_public_resource<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps_u/public_resource/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get public secret of app + +Sends a `GET` request to `/w/{workspace}/apps/secret_of/{path}` + +*/ + pub async fn get_public_secret_of_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/secret_of/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get app by version + +Sends a `GET` request to `/w/{workspace}/apps/get/v/{id}` + +*/ + pub async fn get_app_by_version<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/get/v/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create raw app + +Sends a `POST` request to `/w/{workspace}/raw_apps/create` + +Arguments: +- `workspace` +- `body`: new raw app +*/ + pub async fn create_raw_app<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateRawAppBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/raw_apps/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update app + +Sends a `POST` request to `/w/{workspace}/raw_apps/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updateraw app +*/ + pub async fn update_raw_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::UpdateRawAppBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/raw_apps/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete raw app + +Sends a `DELETE` request to `/w/{workspace}/raw_apps/delete/{path}` + +*/ + pub async fn delete_raw_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/raw_apps/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete app + +Sends a `DELETE` request to `/w/{workspace}/apps/delete/{path}` + +*/ + pub async fn delete_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/delete/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update app + +Sends a `POST` request to `/w/{workspace}/apps/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: update app +*/ + pub async fn update_app<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::UpdateAppBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/update/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**check if custom path exists + +Sends a `GET` request to `/w/{workspace}/apps/custom_path_exists/{custom_path}` + +*/ + pub async fn custom_path_exists<'a>( + &'a self, + workspace: &'a str, + custom_path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps/custom_path_exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& custom_path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**executeComponent + +Sends a `POST` request to `/w/{workspace}/apps_u/execute_component/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: update app +*/ + pub async fn execute_component<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::ExecuteComponentBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps_u/execute_component/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**upload s3 file from app + +Sends a `POST` request to `/w/{workspace}/apps_u/upload_s3_file/{path}` + +Arguments: +- `workspace` +- `path` +- `content_disposition` +- `content_type` +- `file_extension` +- `file_key` +- `resource_type` +- `s3_resource_path` +- `storage` +- `body`: File content +*/ + pub async fn upload_s3_file_from_app<'a, B: Into>( + &'a self, + workspace: &'a str, + path: &'a str, + content_disposition: Option<&'a str>, + content_type: Option<&'a str>, + file_extension: Option<&'a str>, + file_key: Option<&'a str>, + resource_type: Option<&'a str>, + s3_resource_path: Option<&'a str>, + storage: Option<&'a str>, + body: B, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps_u/upload_s3_file/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &content_disposition { + query.push(("content_disposition", v.to_string())); + } + if let Some(v) = &content_type { + query.push(("content_type", v.to_string())); + } + if let Some(v) = &file_extension { + query.push(("file_extension", v.to_string())); + } + if let Some(v) = &file_key { + query.push(("file_key", v.to_string())); + } + if let Some(v) = &resource_type { + query.push(("resource_type", v.to_string())); + } + if let Some(v) = &s3_resource_path { + query.push(("s3_resource_path", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::CONTENT_TYPE, + reqwest::header::HeaderValue::from_static("application/octet-stream"), + ) + .body(body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete s3 file from app + +Sends a `DELETE` request to `/w/{workspace}/apps_u/delete_s3_file` + +*/ + pub async fn delete_s3_file_from_app<'a>( + &'a self, + workspace: &'a str, + delete_token: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/apps_u/delete_s3_file", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + query.push(("delete_token", delete_token.to_string())); + let request = self.client.delete(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run flow by path + +Sends a `POST` request to `/w/{workspace}/jobs/run/f/{path}` + +Arguments: +- `workspace` +- `path` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `invisible_to_owner`: make the run invisible to the the flow owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `scheduled_for`: when to schedule this job (leave empty for immediate run) +- `scheduled_in_secs`: schedule the script to execute in the number of seconds starting now +- `skip_preprocessor`: skip the preprocessor +- `tag`: Override the tag to use +- `body`: flow args +*/ + pub async fn run_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + include_header: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + scheduled_for: Option<&'a chrono::DateTime>, + scheduled_in_secs: Option, + skip_preprocessor: Option, + tag: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/f/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(8usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &scheduled_for { + query.push(("scheduled_for", v.to_string())); + } + if let Some(v) = &scheduled_in_secs { + query.push(("scheduled_in_secs", v.to_string())); + } + if let Some(v) = &skip_preprocessor { + query.push(("skip_preprocessor", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**restart a completed flow at a given step + +Sends a `POST` request to `/w/{workspace}/jobs/restart/f/{id}/from/{step_id}/{branch_or_iteration_n}` + +Arguments: +- `workspace` +- `id` +- `step_id`: step id to restart the flow from +- `branch_or_iteration_n`: for branchall or loop, the iteration at which the flow should restart +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `invisible_to_owner`: make the run invisible to the the flow owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `scheduled_for`: when to schedule this job (leave empty for immediate run) +- `scheduled_in_secs`: schedule the script to execute in the number of seconds starting now +- `tag`: Override the tag to use +- `body`: flow args +*/ + pub async fn restart_flow_at_step<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + step_id: &'a str, + branch_or_iteration_n: i64, + include_header: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + scheduled_for: Option<&'a chrono::DateTime>, + scheduled_in_secs: Option, + tag: Option<&'a str>, + body: &'a types::ScriptArgs, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/restart/f/{}/from/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& step_id + .to_string()), encode_path(& branch_or_iteration_n.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &scheduled_for { + query.push(("scheduled_for", v.to_string())); + } + if let Some(v) = &scheduled_in_secs { + query.push(("scheduled_in_secs", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script by hash + +Sends a `POST` request to `/w/{workspace}/jobs/run/h/{hash}` + +Arguments: +- `workspace` +- `hash` +- `cache_ttl`: Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `invisible_to_owner`: make the run invisible to the the script owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `scheduled_for`: when to schedule this job (leave empty for immediate run) +- `scheduled_in_secs`: schedule the script to execute in the number of seconds starting now +- `skip_preprocessor`: skip the preprocessor +- `tag`: Override the tag to use +- `body`: Partially filled args +*/ + pub async fn run_script_by_hash<'a>( + &'a self, + workspace: &'a str, + hash: &'a str, + cache_ttl: Option<&'a str>, + include_header: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + parent_job: Option<&'a uuid::Uuid>, + scheduled_for: Option<&'a chrono::DateTime>, + scheduled_in_secs: Option, + skip_preprocessor: Option, + tag: Option<&'a str>, + body: &'a std::collections::HashMap, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/h/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& hash.to_string()), + ); + let mut query = Vec::with_capacity(9usize); + if let Some(v) = &cache_ttl { + query.push(("cache_ttl", v.to_string())); + } + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &scheduled_for { + query.push(("scheduled_for", v.to_string())); + } + if let Some(v) = &scheduled_in_secs { + query.push(("scheduled_in_secs", v.to_string())); + } + if let Some(v) = &skip_preprocessor { + query.push(("skip_preprocessor", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run script preview + +Sends a `POST` request to `/w/{workspace}/jobs/run/preview` + +Arguments: +- `workspace` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `invisible_to_owner`: make the run invisible to the the script owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `body`: preview +*/ + pub async fn run_script_preview<'a>( + &'a self, + workspace: &'a str, + include_header: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + body: &'a types::Preview, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/preview", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run code-workflow task + +Sends a `POST` request to `/w/{workspace}/jobs/workflow_as_code/{job_id}/{entrypoint}` + +Arguments: +- `workspace` +- `job_id` +- `entrypoint` +- `body`: preview +*/ + pub async fn run_code_workflow_task<'a>( + &'a self, + workspace: &'a str, + job_id: &'a str, + entrypoint: &'a str, + body: &'a types::WorkflowTask, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/workflow_as_code/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& job_id.to_string()), encode_path(& entrypoint + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run a one-off dependencies job + +Sends a `POST` request to `/w/{workspace}/jobs/run/dependencies` + +Arguments: +- `workspace` +- `body`: raw script content +*/ + pub async fn run_raw_script_dependencies<'a>( + &'a self, + workspace: &'a str, + body: &'a types::RunRawScriptDependenciesBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/dependencies", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**run flow preview + +Sends a `POST` request to `/w/{workspace}/jobs/run/preview_flow` + +Arguments: +- `workspace` +- `include_header`: List of headers's keys (separated with ',') whove value are added to the args +Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key + +- `invisible_to_owner`: make the run invisible to the the script owner (default false) +- `job_id`: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) +- `body`: preview +*/ + pub async fn run_flow_preview<'a>( + &'a self, + workspace: &'a str, + include_header: Option<&'a str>, + invisible_to_owner: Option, + job_id: Option<&'a uuid::Uuid>, + body: &'a types::FlowPreview, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/run/preview_flow", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &include_header { + query.push(("include_header", v.to_string())); + } + if let Some(v) = &invisible_to_owner { + query.push(("invisible_to_owner", v.to_string())); + } + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all queued jobs + +Sends a `GET` request to `/w/{workspace}/jobs/queue/list` + +Arguments: +- `workspace` +- `all_workspaces`: get jobs from all workspaces (only valid if request come from the `admins` workspace) +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `created_by`: mask to filter exact matching user creator +- `is_not_schedule`: is not a scheduled job +- `job_kinds`: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `per_page`: number of items to return for a given page (default 30, max 100) +- `result`: filter on jobs containing those result as a json subset (@> in postgres) +- `running`: filter on running jobs +- `schedule_path`: mask to filter by schedule path +- `scheduled_for_before_now`: filter on jobs scheduled_for before now (hence waitinf for a worker) +- `script_hash`: mask to filter exact matching path +- `script_path_exact`: mask to filter exact matching path +- `script_path_start`: mask to filter matching starting path +- `started_after`: filter on started after (exclusive) timestamp +- `started_before`: filter on started before (inclusive) timestamp +- `success`: filter on successful jobs +- `suspended`: filter on suspended jobs +- `tag`: filter on jobs with a given tag/worker group +- `worker`: worker this job was ran on +*/ + pub async fn list_queue<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + args: Option<&'a str>, + created_by: Option<&'a str>, + is_not_schedule: Option, + job_kinds: Option<&'a str>, + order_desc: Option, + page: Option, + parent_job: Option<&'a uuid::Uuid>, + per_page: Option, + result: Option<&'a str>, + running: Option, + schedule_path: Option<&'a str>, + scheduled_for_before_now: Option, + script_hash: Option<&'a str>, + script_path_exact: Option<&'a str>, + script_path_start: Option<&'a str>, + started_after: Option<&'a chrono::DateTime>, + started_before: Option<&'a chrono::DateTime>, + success: Option, + suspended: Option, + tag: Option<&'a str>, + worker: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/jobs/queue/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(22usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &is_not_schedule { + query.push(("is_not_schedule", v.to_string())); + } + if let Some(v) = &job_kinds { + query.push(("job_kinds", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &result { + query.push(("result", v.to_string())); + } + if let Some(v) = &running { + query.push(("running", v.to_string())); + } + if let Some(v) = &schedule_path { + query.push(("schedule_path", v.to_string())); + } + if let Some(v) = &scheduled_for_before_now { + query.push(("scheduled_for_before_now", v.to_string())); + } + if let Some(v) = &script_hash { + query.push(("script_hash", v.to_string())); + } + if let Some(v) = &script_path_exact { + query.push(("script_path_exact", v.to_string())); + } + if let Some(v) = &script_path_start { + query.push(("script_path_start", v.to_string())); + } + if let Some(v) = &started_after { + query.push(("started_after", v.to_string())); + } + if let Some(v) = &started_before { + query.push(("started_before", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &suspended { + query.push(("suspended", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + if let Some(v) = &worker { + query.push(("worker", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get queue count + +Sends a `GET` request to `/w/{workspace}/jobs/queue/count` + +Arguments: +- `workspace` +- `all_workspaces`: get jobs from all workspaces (only valid if request come from the `admins` workspace) +*/ + pub async fn get_queue_count<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/queue/count", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get completed count + +Sends a `GET` request to `/w/{workspace}/jobs/completed/count` + +*/ + pub async fn get_completed_count<'a>( + &'a self, + workspace: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/completed/count", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**count number of completed jobs with filter + +Sends a `GET` request to `/w/{workspace}/jobs/completed/count_jobs` + +*/ + pub async fn count_completed_jobs<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + completed_after_s_ago: Option, + success: Option, + tags: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/completed/count_jobs", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &completed_after_s_ago { + query.push(("completed_after_s_ago", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &tags { + query.push(("tags", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get the ids of all jobs matching the given filters + +Sends a `GET` request to `/w/{workspace}/jobs/queue/list_filtered_uuids` + +Arguments: +- `workspace` +- `all_workspaces`: get jobs from all workspaces (only valid if request come from the `admins` workspace) +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `concurrency_key` +- `created_by`: mask to filter exact matching user creator +- `is_not_schedule`: is not a scheduled job +- `job_kinds`: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `per_page`: number of items to return for a given page (default 30, max 100) +- `result`: filter on jobs containing those result as a json subset (@> in postgres) +- `running`: filter on running jobs +- `schedule_path`: mask to filter by schedule path +- `scheduled_for_before_now`: filter on jobs scheduled_for before now (hence waitinf for a worker) +- `script_hash`: mask to filter exact matching path +- `script_path_exact`: mask to filter exact matching path +- `script_path_start`: mask to filter matching starting path +- `started_after`: filter on started after (exclusive) timestamp +- `started_before`: filter on started before (inclusive) timestamp +- `success`: filter on successful jobs +- `suspended`: filter on suspended jobs +- `tag`: filter on jobs with a given tag/worker group +*/ + pub async fn list_filtered_uuids<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + args: Option<&'a str>, + concurrency_key: Option<&'a str>, + created_by: Option<&'a str>, + is_not_schedule: Option, + job_kinds: Option<&'a str>, + order_desc: Option, + page: Option, + parent_job: Option<&'a uuid::Uuid>, + per_page: Option, + result: Option<&'a str>, + running: Option, + schedule_path: Option<&'a str>, + scheduled_for_before_now: Option, + script_hash: Option<&'a str>, + script_path_exact: Option<&'a str>, + script_path_start: Option<&'a str>, + started_after: Option<&'a chrono::DateTime>, + started_before: Option<&'a chrono::DateTime>, + success: Option, + suspended: Option, + tag: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/jobs/queue/list_filtered_uuids", self.baseurl, encode_path(& + workspace.to_string()), + ); + let mut query = Vec::with_capacity(22usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &concurrency_key { + query.push(("concurrency_key", v.to_string())); + } + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &is_not_schedule { + query.push(("is_not_schedule", v.to_string())); + } + if let Some(v) = &job_kinds { + query.push(("job_kinds", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &result { + query.push(("result", v.to_string())); + } + if let Some(v) = &running { + query.push(("running", v.to_string())); + } + if let Some(v) = &schedule_path { + query.push(("schedule_path", v.to_string())); + } + if let Some(v) = &scheduled_for_before_now { + query.push(("scheduled_for_before_now", v.to_string())); + } + if let Some(v) = &script_hash { + query.push(("script_hash", v.to_string())); + } + if let Some(v) = &script_path_exact { + query.push(("script_path_exact", v.to_string())); + } + if let Some(v) = &script_path_start { + query.push(("script_path_start", v.to_string())); + } + if let Some(v) = &started_after { + query.push(("started_after", v.to_string())); + } + if let Some(v) = &started_before { + query.push(("started_before", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &suspended { + query.push(("suspended", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**cancel jobs based on the given uuids + +Sends a `POST` request to `/w/{workspace}/jobs/queue/cancel_selection` + +Arguments: +- `workspace` +- `body`: uuids of the jobs to cancel +*/ + pub async fn cancel_selection<'a>( + &'a self, + workspace: &'a str, + body: &'a Vec, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/jobs/queue/cancel_selection", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all completed jobs + +Sends a `GET` request to `/w/{workspace}/jobs/completed/list` + +Arguments: +- `workspace` +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `created_by`: mask to filter exact matching user creator +- `has_null_parent`: has null parent +- `is_flow_step`: is the job a flow step +- `is_not_schedule`: is not a scheduled job +- `is_skipped`: is the job skipped +- `job_kinds`: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, +- `label`: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') +- `order_desc`: order by desc order (default true) +- `page`: which page to return (start at 1, default 1) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `per_page`: number of items to return for a given page (default 30, max 100) +- `result`: filter on jobs containing those result as a json subset (@> in postgres) +- `schedule_path`: mask to filter by schedule path +- `script_hash`: mask to filter exact matching path +- `script_path_exact`: mask to filter exact matching path +- `script_path_start`: mask to filter matching starting path +- `started_after`: filter on started after (exclusive) timestamp +- `started_before`: filter on started before (inclusive) timestamp +- `success`: filter on successful jobs +- `tag`: filter on jobs with a given tag/worker group +- `worker`: worker this job was ran on +*/ + pub async fn list_completed_jobs<'a>( + &'a self, + workspace: &'a str, + args: Option<&'a str>, + created_by: Option<&'a str>, + has_null_parent: Option, + is_flow_step: Option, + is_not_schedule: Option, + is_skipped: Option, + job_kinds: Option<&'a str>, + label: Option<&'a str>, + order_desc: Option, + page: Option, + parent_job: Option<&'a uuid::Uuid>, + per_page: Option, + result: Option<&'a str>, + schedule_path: Option<&'a str>, + script_hash: Option<&'a str>, + script_path_exact: Option<&'a str>, + script_path_start: Option<&'a str>, + started_after: Option<&'a chrono::DateTime>, + started_before: Option<&'a chrono::DateTime>, + success: Option, + tag: Option<&'a str>, + worker: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/jobs/completed/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(22usize); + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &has_null_parent { + query.push(("has_null_parent", v.to_string())); + } + if let Some(v) = &is_flow_step { + query.push(("is_flow_step", v.to_string())); + } + if let Some(v) = &is_not_schedule { + query.push(("is_not_schedule", v.to_string())); + } + if let Some(v) = &is_skipped { + query.push(("is_skipped", v.to_string())); + } + if let Some(v) = &job_kinds { + query.push(("job_kinds", v.to_string())); + } + if let Some(v) = &label { + query.push(("label", v.to_string())); + } + if let Some(v) = &order_desc { + query.push(("order_desc", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &result { + query.push(("result", v.to_string())); + } + if let Some(v) = &schedule_path { + query.push(("schedule_path", v.to_string())); + } + if let Some(v) = &script_hash { + query.push(("script_hash", v.to_string())); + } + if let Some(v) = &script_path_exact { + query.push(("script_path_exact", v.to_string())); + } + if let Some(v) = &script_path_start { + query.push(("script_path_start", v.to_string())); + } + if let Some(v) = &started_after { + query.push(("started_after", v.to_string())); + } + if let Some(v) = &started_before { + query.push(("started_before", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + if let Some(v) = &worker { + query.push(("worker", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list all jobs + +Sends a `GET` request to `/w/{workspace}/jobs/list` + +Arguments: +- `workspace` +- `all_workspaces`: get jobs from all workspaces (only valid if request come from the `admins` workspace) +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `created_after`: filter on created after (exclusive) timestamp +- `created_before`: filter on created before (inclusive) timestamp +- `created_by`: mask to filter exact matching user creator +- `created_or_started_after`: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp +- `created_or_started_after_completed_jobs`: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs +- `created_or_started_before`: filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp +- `has_null_parent`: has null parent +- `is_flow_step`: is the job a flow step +- `is_not_schedule`: is not a scheduled job +- `is_skipped`: is the job skipped +- `job_kinds`: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, +- `label`: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') +- `page`: which page to return (start at 1, default 1) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `per_page`: number of items to return for a given page (default 30, max 100) +- `result`: filter on jobs containing those result as a json subset (@> in postgres) +- `running`: filter on running jobs +- `schedule_path`: mask to filter by schedule path +- `scheduled_for_before_now`: filter on jobs scheduled_for before now (hence waitinf for a worker) +- `script_hash`: mask to filter exact matching path +- `script_path_exact`: mask to filter exact matching path +- `script_path_start`: mask to filter matching starting path +- `started_after`: filter on started after (exclusive) timestamp +- `started_before`: filter on started before (inclusive) timestamp +- `success`: filter on successful jobs +- `suspended`: filter on suspended jobs +- `tag`: filter on jobs with a given tag/worker group +- `worker`: worker this job was ran on +*/ + pub async fn list_jobs<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + args: Option<&'a str>, + created_after: Option<&'a chrono::DateTime>, + created_before: Option<&'a chrono::DateTime>, + created_by: Option<&'a str>, + created_or_started_after: Option<&'a chrono::DateTime>, + created_or_started_after_completed_jobs: Option< + &'a chrono::DateTime, + >, + created_or_started_before: Option<&'a chrono::DateTime>, + has_null_parent: Option, + is_flow_step: Option, + is_not_schedule: Option, + is_skipped: Option, + job_kinds: Option<&'a str>, + label: Option<&'a str>, + page: Option, + parent_job: Option<&'a uuid::Uuid>, + per_page: Option, + result: Option<&'a str>, + running: Option, + schedule_path: Option<&'a str>, + scheduled_for_before_now: Option, + script_hash: Option<&'a str>, + script_path_exact: Option<&'a str>, + script_path_start: Option<&'a str>, + started_after: Option<&'a chrono::DateTime>, + started_before: Option<&'a chrono::DateTime>, + success: Option, + suspended: Option, + tag: Option<&'a str>, + worker: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/jobs/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(30usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &created_after { + query.push(("created_after", v.to_string())); + } + if let Some(v) = &created_before { + query.push(("created_before", v.to_string())); + } + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &created_or_started_after { + query.push(("created_or_started_after", v.to_string())); + } + if let Some(v) = &created_or_started_after_completed_jobs { + query.push(("created_or_started_after_completed_jobs", v.to_string())); + } + if let Some(v) = &created_or_started_before { + query.push(("created_or_started_before", v.to_string())); + } + if let Some(v) = &has_null_parent { + query.push(("has_null_parent", v.to_string())); + } + if let Some(v) = &is_flow_step { + query.push(("is_flow_step", v.to_string())); + } + if let Some(v) = &is_not_schedule { + query.push(("is_not_schedule", v.to_string())); + } + if let Some(v) = &is_skipped { + query.push(("is_skipped", v.to_string())); + } + if let Some(v) = &job_kinds { + query.push(("job_kinds", v.to_string())); + } + if let Some(v) = &label { + query.push(("label", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &result { + query.push(("result", v.to_string())); + } + if let Some(v) = &running { + query.push(("running", v.to_string())); + } + if let Some(v) = &schedule_path { + query.push(("schedule_path", v.to_string())); + } + if let Some(v) = &scheduled_for_before_now { + query.push(("scheduled_for_before_now", v.to_string())); + } + if let Some(v) = &script_hash { + query.push(("script_hash", v.to_string())); + } + if let Some(v) = &script_path_exact { + query.push(("script_path_exact", v.to_string())); + } + if let Some(v) = &script_path_start { + query.push(("script_path_start", v.to_string())); + } + if let Some(v) = &started_after { + query.push(("started_after", v.to_string())); + } + if let Some(v) = &started_before { + query.push(("started_before", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &suspended { + query.push(("suspended", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + if let Some(v) = &worker { + query.push(("worker", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get db clock + +Sends a `GET` request to `/jobs/db_clock` + +*/ + pub async fn get_db_clock<'a>(&'a self) -> Result, Error<()>> { + let url = format!("{}/jobs/db_clock", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Count jobs by tag + +Sends a `GET` request to `/jobs/completed/count_by_tag` + +Arguments: +- `horizon_secs`: Past Time horizon in seconds (when to start the count = now - horizon) (default is 3600) +- `workspace_id`: Specific workspace ID to filter results (optional) +*/ + pub async fn count_jobs_by_tag<'a>( + &'a self, + horizon_secs: Option, + workspace_id: Option<&'a str>, + ) -> Result>, Error<()>> { + let url = format!("{}/jobs/completed/count_by_tag", self.baseurl,); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &horizon_secs { + query.push(("horizon_secs", v.to_string())); + } + if let Some(v) = &workspace_id { + query.push(("workspace_id", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job + +Sends a `GET` request to `/w/{workspace}/jobs_u/get/{id}` + +*/ + pub async fn get_job<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + no_logs: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& id.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &no_logs { + query.push(("no_logs", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get root job id + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_root_job_id/{id}` + +*/ + pub async fn get_root_job_id<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_root_job_id/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job logs + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_logs/{id}` + +*/ + pub async fn get_job_logs<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_logs/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job args + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_args/{id}` + +*/ + pub async fn get_job_args<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_args/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job updates + +Sends a `GET` request to `/w/{workspace}/jobs_u/getupdate/{id}` + +*/ + pub async fn get_job_updates<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + get_progress: Option, + log_offset: Option, + running: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/getupdate/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &get_progress { + query.push(("get_progress", v.to_string())); + } + if let Some(v) = &log_offset { + query.push(("log_offset", v.to_string())); + } + if let Some(v) = &running { + query.push(("running", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get log file from object store + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_log_file/{path}` + +*/ + pub async fn get_log_file_from_store<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_log_file/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow debug info + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_flow_debug_info/{id}` + +*/ + pub async fn get_flow_debug_info<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_flow_debug_info/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get completed job + +Sends a `GET` request to `/w/{workspace}/jobs_u/completed/get/{id}` + +*/ + pub async fn get_completed_job<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/completed/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get completed job result + +Sends a `GET` request to `/w/{workspace}/jobs_u/completed/get_result/{id}` + +*/ + pub async fn get_completed_job_result<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + approver: Option<&'a str>, + resume_id: Option, + secret: Option<&'a str>, + suspended_job: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/completed/get_result/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& id.to_string()), + ); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + if let Some(v) = &resume_id { + query.push(("resume_id", v.to_string())); + } + if let Some(v) = &secret { + query.push(("secret", v.to_string())); + } + if let Some(v) = &suspended_job { + query.push(("suspended_job", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get completed job result if job is completed + +Sends a `GET` request to `/w/{workspace}/jobs_u/completed/get_result_maybe/{id}` + +*/ + pub async fn get_completed_job_result_maybe<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + get_started: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/completed/get_result_maybe/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& id.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &get_started { + query.push(("get_started", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete completed job (erase content but keep run id) + +Sends a `POST` request to `/w/{workspace}/jobs/completed/delete/{id}` + +*/ + pub async fn delete_completed_job<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/completed/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**cancel queued or running job + +Sends a `POST` request to `/w/{workspace}/jobs_u/queue/cancel/{id}` + +Arguments: +- `workspace` +- `id` +- `body`: reason +*/ + pub async fn cancel_queued_job<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + body: &'a types::CancelQueuedJobBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/queue/cancel/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**cancel all queued jobs for persistent script + +Sends a `POST` request to `/w/{workspace}/jobs_u/queue/cancel_persistent/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: reason +*/ + pub async fn cancel_persistent_queued_jobs<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::CancelPersistentQueuedJobsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/queue/cancel_persistent/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**force cancel queued job + +Sends a `POST` request to `/w/{workspace}/jobs_u/queue/force_cancel/{id}` + +Arguments: +- `workspace` +- `id` +- `body`: reason +*/ + pub async fn force_cancel_queued_job<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + body: &'a types::ForceCancelQueuedJobBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/queue/force_cancel/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create an HMac signature given a job id and a resume id + +Sends a `GET` request to `/w/{workspace}/jobs/job_signature/{id}/{resume_id}` + +*/ + pub async fn create_job_signature<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + approver: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/job_signature/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get resume urls given a job_id, resume_id and a nonce to resume a flow + +Sends a `GET` request to `/w/{workspace}/jobs/resume_urls/{id}/{resume_id}` + +*/ + pub async fn get_resume_urls<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + approver: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/resume_urls/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**generate interactive slack approval for suspended job + +Sends a `GET` request to `/w/{workspace}/jobs/slack_approval/{id}` + +*/ + pub async fn get_slack_approval_payload<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + approver: Option<&'a str>, + channel_id: &'a str, + default_args_json: Option<&'a str>, + dynamic_enums_json: Option<&'a str>, + flow_step_id: &'a str, + message: Option<&'a str>, + slack_resource_path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/slack_approval/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + query.push(("channel_id", channel_id.to_string())); + if let Some(v) = &default_args_json { + query.push(("default_args_json", v.to_string())); + } + if let Some(v) = &dynamic_enums_json { + query.push(("dynamic_enums_json", v.to_string())); + } + query.push(("flow_step_id", flow_step_id.to_string())); + if let Some(v) = &message { + query.push(("message", v.to_string())); + } + query.push(("slack_resource_path", slack_resource_path.to_string())); + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**resume a job for a suspended flow + +Sends a `GET` request to `/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}` + +Arguments: +- `workspace` +- `id` +- `resume_id` +- `signature` +- `approver` +- `payload`: The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent +`encodeURIComponent(btoa(JSON.stringify({a: 2})))` + +*/ + pub async fn resume_suspended_job_get<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + signature: &'a str, + approver: Option<&'a str>, + payload: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/resume/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), encode_path(& signature.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + if let Some(v) = &payload { + query.push(("payload", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**resume a job for a suspended flow + +Sends a `POST` request to `/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}` + +*/ + pub async fn resume_suspended_job_post<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + signature: &'a str, + approver: Option<&'a str>, + body: &'a std::collections::HashMap, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/resume/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), encode_path(& signature.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow user state at a given key + +Sends a `GET` request to `/w/{workspace}/jobs/flow/user_states/{id}/{key}` + +*/ + pub async fn get_flow_user_state<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + key: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/flow/user_states/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& key.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set flow user state at a given key + +Sends a `POST` request to `/w/{workspace}/jobs/flow/user_states/{id}/{key}` + +Arguments: +- `workspace` +- `id` +- `key` +- `body`: new value +*/ + pub async fn set_flow_user_state<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + key: &'a str, + body: &'a serde_json::Value, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/flow/user_states/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& key.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**resume a job for a suspended flow as an owner + +Sends a `POST` request to `/w/{workspace}/jobs/flow/resume/{id}` + +*/ + pub async fn resume_suspended_flow_as_owner<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + body: &'a std::collections::HashMap, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs/flow/resume/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**cancel a job for a suspended flow + +Sends a `GET` request to `/w/{workspace}/jobs_u/cancel/{id}/{resume_id}/{signature}` + +*/ + pub async fn cancel_suspended_job_get<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + signature: &'a str, + approver: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/cancel/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), encode_path(& signature.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**cancel a job for a suspended flow + +Sends a `POST` request to `/w/{workspace}/jobs_u/cancel/{id}/{resume_id}/{signature}` + +*/ + pub async fn cancel_suspended_job_post<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + signature: &'a str, + approver: Option<&'a str>, + body: &'a std::collections::HashMap, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/cancel/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), encode_path(& signature.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get parent flow job of suspended job + +Sends a `GET` request to `/w/{workspace}/jobs_u/get_flow/{id}/{resume_id}/{signature}` + +*/ + pub async fn get_suspended_job_flow<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + resume_id: i64, + signature: &'a str, + approver: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/jobs_u/get_flow/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), encode_path(& resume_id + .to_string()), encode_path(& signature.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &approver { + query.push(("approver", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**preview schedule + +Sends a `POST` request to `/schedules/preview` + +Arguments: +- `body`: schedule +*/ + pub async fn preview_schedule<'a>( + &'a self, + body: &'a types::PreviewScheduleBody, + ) -> Result>>, Error<()>> { + let url = format!("{}/schedules/preview", self.baseurl,); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create schedule + +Sends a `POST` request to `/w/{workspace}/schedules/create` + +Arguments: +- `workspace` +- `body`: new schedule +*/ + pub async fn create_schedule<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewSchedule, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update schedule + +Sends a `POST` request to `/w/{workspace}/schedules/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated schedule +*/ + pub async fn update_schedule<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditSchedule, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled schedule + +Sends a `POST` request to `/w/{workspace}/schedules/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated schedule enable +*/ + pub async fn set_schedule_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetScheduleEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/setenabled/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete schedule + +Sends a `DELETE` request to `/w/{workspace}/schedules/delete/{path}` + +*/ + pub async fn delete_schedule<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get schedule + +Sends a `GET` request to `/w/{workspace}/schedules/get/{path}` + +*/ + pub async fn get_schedule<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does schedule exists + +Sends a `GET` request to `/w/{workspace}/schedules/exists/{path}` + +*/ + pub async fn exists_schedule<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list schedules + +Sends a `GET` request to `/w/{workspace}/schedules/list` + +Arguments: +- `workspace` +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_schedules<'a>( + &'a self, + workspace: &'a str, + args: Option<&'a str>, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/schedules/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(6usize); + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list schedules with last 20 jobs + +Sends a `GET` request to `/w/{workspace}/schedules/list_with_jobs` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_schedules_with_jobs<'a>( + &'a self, + workspace: &'a str, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/schedules/list_with_jobs", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Set default error or recoevery handler + +Sends a `POST` request to `/w/{workspace}/schedules/setdefaulthandler` + +Arguments: +- `workspace` +- `body`: Handler description +*/ + pub async fn set_default_error_or_recovery_handler<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetDefaultErrorOrRecoveryHandlerBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/setdefaulthandler", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create http trigger + +Sends a `POST` request to `/w/{workspace}/http_triggers/create` + +Arguments: +- `workspace` +- `body`: new http trigger +*/ + pub async fn create_http_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewHttpTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update http trigger + +Sends a `POST` request to `/w/{workspace}/http_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_http_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditHttpTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete http trigger + +Sends a `DELETE` request to `/w/{workspace}/http_triggers/delete/{path}` + +*/ + pub async fn delete_http_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get http trigger + +Sends a `GET` request to `/w/{workspace}/http_triggers/get/{path}` + +*/ + pub async fn get_http_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list http triggers + +Sends a `GET` request to `/w/{workspace}/http_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_http_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does http trigger exists + +Sends a `GET` request to `/w/{workspace}/http_triggers/exists/{path}` + +*/ + pub async fn exists_http_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does route exists + +Sends a `POST` request to `/w/{workspace}/http_triggers/route_exists` + +Arguments: +- `workspace` +- `body`: route exists request +*/ + pub async fn exists_route<'a>( + &'a self, + workspace: &'a str, + body: &'a types::ExistsRouteBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/http_triggers/route_exists", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create websocket trigger + +Sends a `POST` request to `/w/{workspace}/websocket_triggers/create` + +Arguments: +- `workspace` +- `body`: new websocket trigger +*/ + pub async fn create_websocket_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewWebsocketTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update websocket trigger + +Sends a `POST` request to `/w/{workspace}/websocket_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_websocket_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditWebsocketTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete websocket trigger + +Sends a `DELETE` request to `/w/{workspace}/websocket_triggers/delete/{path}` + +*/ + pub async fn delete_websocket_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get websocket trigger + +Sends a `GET` request to `/w/{workspace}/websocket_triggers/get/{path}` + +*/ + pub async fn get_websocket_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list websocket triggers + +Sends a `GET` request to `/w/{workspace}/websocket_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_websocket_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does websocket trigger exists + +Sends a `GET` request to `/w/{workspace}/websocket_triggers/exists/{path}` + +*/ + pub async fn exists_websocket_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled websocket trigger + +Sends a `POST` request to `/w/{workspace}/websocket_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated websocket trigger enable +*/ + pub async fn set_websocket_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetWebsocketTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/setenabled/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test websocket connection + +Sends a `POST` request to `/w/{workspace}/websocket_triggers/test` + +Arguments: +- `workspace` +- `body`: test websocket connection +*/ + pub async fn test_websocket_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestWebsocketConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/websocket_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create kafka trigger + +Sends a `POST` request to `/w/{workspace}/kafka_triggers/create` + +Arguments: +- `workspace` +- `body`: new kafka trigger +*/ + pub async fn create_kafka_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewKafkaTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update kafka trigger + +Sends a `POST` request to `/w/{workspace}/kafka_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_kafka_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditKafkaTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete kafka trigger + +Sends a `DELETE` request to `/w/{workspace}/kafka_triggers/delete/{path}` + +*/ + pub async fn delete_kafka_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get kafka trigger + +Sends a `GET` request to `/w/{workspace}/kafka_triggers/get/{path}` + +*/ + pub async fn get_kafka_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list kafka triggers + +Sends a `GET` request to `/w/{workspace}/kafka_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_kafka_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does kafka trigger exists + +Sends a `GET` request to `/w/{workspace}/kafka_triggers/exists/{path}` + +*/ + pub async fn exists_kafka_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled kafka trigger + +Sends a `POST` request to `/w/{workspace}/kafka_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated kafka trigger enable +*/ + pub async fn set_kafka_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetKafkaTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/setenabled/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test kafka connection + +Sends a `POST` request to `/w/{workspace}/kafka_triggers/test` + +Arguments: +- `workspace` +- `body`: test kafka connection +*/ + pub async fn test_kafka_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestKafkaConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/kafka_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create nats trigger + +Sends a `POST` request to `/w/{workspace}/nats_triggers/create` + +Arguments: +- `workspace` +- `body`: new nats trigger +*/ + pub async fn create_nats_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewNatsTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update nats trigger + +Sends a `POST` request to `/w/{workspace}/nats_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_nats_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditNatsTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete nats trigger + +Sends a `DELETE` request to `/w/{workspace}/nats_triggers/delete/{path}` + +*/ + pub async fn delete_nats_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get nats trigger + +Sends a `GET` request to `/w/{workspace}/nats_triggers/get/{path}` + +*/ + pub async fn get_nats_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list nats triggers + +Sends a `GET` request to `/w/{workspace}/nats_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_nats_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does nats trigger exists + +Sends a `GET` request to `/w/{workspace}/nats_triggers/exists/{path}` + +*/ + pub async fn exists_nats_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled nats trigger + +Sends a `POST` request to `/w/{workspace}/nats_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated nats trigger enable +*/ + pub async fn set_nats_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetNatsTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/setenabled/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test NATS connection + +Sends a `POST` request to `/w/{workspace}/nats_triggers/test` + +Arguments: +- `workspace` +- `body`: test nats connection +*/ + pub async fn test_nats_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestNatsConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/nats_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create sqs trigger + +Sends a `POST` request to `/w/{workspace}/sqs_triggers/create` + +Arguments: +- `workspace` +- `body`: new sqs trigger +*/ + pub async fn create_sqs_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewSqsTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update sqs trigger + +Sends a `POST` request to `/w/{workspace}/sqs_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_sqs_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditSqsTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete sqs trigger + +Sends a `DELETE` request to `/w/{workspace}/sqs_triggers/delete/{path}` + +*/ + pub async fn delete_sqs_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get sqs trigger + +Sends a `GET` request to `/w/{workspace}/sqs_triggers/get/{path}` + +*/ + pub async fn get_sqs_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list sqs triggers + +Sends a `GET` request to `/w/{workspace}/sqs_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_sqs_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does sqs trigger exists + +Sends a `GET` request to `/w/{workspace}/sqs_triggers/exists/{path}` + +*/ + pub async fn exists_sqs_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled sqs trigger + +Sends a `POST` request to `/w/{workspace}/sqs_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated sqs trigger enable +*/ + pub async fn set_sqs_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetSqsTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/setenabled/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test sqs connection + +Sends a `POST` request to `/w/{workspace}/sqs_triggers/test` + +Arguments: +- `workspace` +- `body`: test sqs connection +*/ + pub async fn test_sqs_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestSqsConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/sqs_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create mqtt trigger + +Sends a `POST` request to `/w/{workspace}/mqtt_triggers/create` + +Arguments: +- `workspace` +- `body`: new mqtt trigger +*/ + pub async fn create_mqtt_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewMqttTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update mqtt trigger + +Sends a `POST` request to `/w/{workspace}/mqtt_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_mqtt_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditMqttTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete mqtt trigger + +Sends a `DELETE` request to `/w/{workspace}/mqtt_triggers/delete/{path}` + +*/ + pub async fn delete_mqtt_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get mqtt trigger + +Sends a `GET` request to `/w/{workspace}/mqtt_triggers/get/{path}` + +*/ + pub async fn get_mqtt_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list mqtt triggers + +Sends a `GET` request to `/w/{workspace}/mqtt_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_mqtt_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does mqtt trigger exists + +Sends a `GET` request to `/w/{workspace}/mqtt_triggers/exists/{path}` + +*/ + pub async fn exists_mqtt_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled mqtt trigger + +Sends a `POST` request to `/w/{workspace}/mqtt_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated mqtt trigger enable +*/ + pub async fn set_mqtt_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetMqttTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/setenabled/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test mqtt connection + +Sends a `POST` request to `/w/{workspace}/mqtt_triggers/test` + +Arguments: +- `workspace` +- `body`: test mqtt connection +*/ + pub async fn test_mqtt_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestMqttConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/mqtt_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**check if postgres configuration is set to logical + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}` + +*/ + pub async fn is_valid_postgres_configuration<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/is_valid_postgres_configuration/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create template script + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/create_template_script` + +Arguments: +- `workspace` +- `body`: template script +*/ + pub async fn create_template_script<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TemplateScript, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/create_template_script", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get template script + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/get_template_script/{id}` + +*/ + pub async fn get_template_script<'a>( + &'a self, + workspace: &'a str, + id: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/get_template_script/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& id.to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list postgres replication slot + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/slot/list/{path}` + +*/ + pub async fn list_postgres_replication_slot<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/slot/list/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create replication slot for postgres + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/slot/create/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: new slot for postgres +*/ + pub async fn create_postgres_replication_slot<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::Slot, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/slot/create/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete postgres replication slot + +Sends a `DELETE` request to `/w/{workspace}/postgres_triggers/slot/delete/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: replication slot of postgres +*/ + pub async fn delete_postgres_replication_slot<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::Slot, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/slot/delete/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list postgres publication + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/publication/list/{path}` + +*/ + pub async fn list_postgres_publication<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/publication/list/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get postgres publication + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/publication/get/{publication}/{path}` + +*/ + pub async fn get_postgres_publication<'a>( + &'a self, + workspace: &'a str, + publication: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/publication/get/{}/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& publication.to_string()), + encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create publication for postgres + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/publication/create/{publication}/{path}` + +Arguments: +- `workspace` +- `publication` +- `path` +- `body`: new publication for postgres +*/ + pub async fn create_postgres_publication<'a>( + &'a self, + workspace: &'a str, + publication: &'a str, + path: &'a str, + body: &'a types::PublicationData, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/publication/create/{}/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& publication.to_string()), + encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update publication for postgres + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/publication/update/{publication}/{path}` + +Arguments: +- `workspace` +- `publication` +- `path` +- `body`: update publication for postgres +*/ + pub async fn update_postgres_publication<'a>( + &'a self, + workspace: &'a str, + publication: &'a str, + path: &'a str, + body: &'a types::PublicationData, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/publication/update/{}/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& publication.to_string()), + encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete postgres publication + +Sends a `DELETE` request to `/w/{workspace}/postgres_triggers/publication/delete/{publication}/{path}` + +*/ + pub async fn delete_postgres_publication<'a>( + &'a self, + workspace: &'a str, + publication: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/publication/delete/{}/{}", self.baseurl, + encode_path(& workspace.to_string()), encode_path(& publication.to_string()), + encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create postgres trigger + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/create` + +Arguments: +- `workspace` +- `body`: new postgres trigger +*/ + pub async fn create_postgres_trigger<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewPostgresTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update postgres trigger + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated trigger +*/ + pub async fn update_postgres_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditPostgresTrigger, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete postgres trigger + +Sends a `DELETE` request to `/w/{workspace}/postgres_triggers/delete/{path}` + +*/ + pub async fn delete_postgres_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get postgres trigger + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/get/{path}` + +*/ + pub async fn get_postgres_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list postgres triggers + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/list` + +Arguments: +- `workspace` +- `is_flow` +- `page`: which page to return (start at 1, default 1) +- `path`: filter by path +- `path_start` +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_postgres_triggers<'a>( + &'a self, + workspace: &'a str, + is_flow: Option, + page: Option, + path: Option<&'a str>, + path_start: Option<&'a str>, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/list", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &is_flow { + query.push(("is_flow", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &path { + query.push(("path", v.to_string())); + } + if let Some(v) = &path_start { + query.push(("path_start", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**does postgres trigger exists + +Sends a `GET` request to `/w/{workspace}/postgres_triggers/exists/{path}` + +*/ + pub async fn exists_postgres_trigger<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set enabled postgres trigger + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/setenabled/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated postgres trigger enable +*/ + pub async fn set_postgres_trigger_enabled<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::SetPostgresTriggerEnabledBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/setenabled/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**test postgres connection + +Sends a `POST` request to `/w/{workspace}/postgres_triggers/test` + +Arguments: +- `workspace` +- `body`: test postgres connection +*/ + pub async fn test_postgres_connection<'a>( + &'a self, + workspace: &'a str, + body: &'a types::TestPostgresConnectionBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/postgres_triggers/test", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list instance groups + +Sends a `GET` request to `/groups/list` + +*/ + pub async fn list_instance_groups<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/groups/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get instance group + +Sends a `GET` request to `/groups/get/{name}` + +*/ + pub async fn get_instance_group<'a>( + &'a self, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/groups/get/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create instance group + +Sends a `POST` request to `/groups/create` + +Arguments: +- `body`: create instance group +*/ + pub async fn create_instance_group<'a>( + &'a self, + body: &'a types::CreateInstanceGroupBody, + ) -> Result, Error<()>> { + let url = format!("{}/groups/create", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update instance group + +Sends a `POST` request to `/groups/update/{name}` + +Arguments: +- `name` +- `body`: update instance group +*/ + pub async fn update_instance_group<'a>( + &'a self, + name: &'a str, + body: &'a types::UpdateInstanceGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/groups/update/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete instance group + +Sends a `DELETE` request to `/groups/delete/{name}` + +*/ + pub async fn delete_instance_group<'a>( + &'a self, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/groups/delete/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**add user to instance group + +Sends a `POST` request to `/groups/adduser/{name}` + +Arguments: +- `name` +- `body`: user to add to instance group +*/ + pub async fn add_user_to_instance_group<'a>( + &'a self, + name: &'a str, + body: &'a types::AddUserToInstanceGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/groups/adduser/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**remove user from instance group + +Sends a `POST` request to `/groups/removeuser/{name}` + +Arguments: +- `name` +- `body`: user to remove from instance group +*/ + pub async fn remove_user_from_instance_group<'a>( + &'a self, + name: &'a str, + body: &'a types::RemoveUserFromInstanceGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/groups/removeuser/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**export instance groups + +Sends a `GET` request to `/groups/export` + +*/ + pub async fn export_instance_groups<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/groups/export", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**overwrite instance groups + +Sends a `POST` request to `/groups/overwrite` + +Arguments: +- `body`: overwrite instance groups +*/ + pub async fn overwrite_instance_groups<'a>( + &'a self, + body: &'a Vec, + ) -> Result, Error<()>> { + let url = format!("{}/groups/overwrite", self.baseurl,); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list groups + +Sends a `GET` request to `/w/{workspace}/groups/list` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_groups<'a>( + &'a self, + workspace: &'a str, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/groups/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list group names + +Sends a `GET` request to `/w/{workspace}/groups/listnames` + +Arguments: +- `workspace` +- `only_member_of`: only list the groups the user is member of (default false) +*/ + pub async fn list_group_names<'a>( + &'a self, + workspace: &'a str, + only_member_of: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/groups/listnames", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &only_member_of { + query.push(("only_member_of", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create group + +Sends a `POST` request to `/w/{workspace}/groups/create` + +Arguments: +- `workspace` +- `body`: create group +*/ + pub async fn create_group<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update group + +Sends a `POST` request to `/w/{workspace}/groups/update/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: updated group +*/ + pub async fn update_group<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::UpdateGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete group + +Sends a `DELETE` request to `/w/{workspace}/groups/delete/{name}` + +*/ + pub async fn delete_group<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get group + +Sends a `GET` request to `/w/{workspace}/groups/get/{name}` + +*/ + pub async fn get_group<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**add user to group + +Sends a `POST` request to `/w/{workspace}/groups/adduser/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: added user to group +*/ + pub async fn add_user_to_group<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::AddUserToGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/adduser/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**remove user to group + +Sends a `POST` request to `/w/{workspace}/groups/removeuser/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: added user to group +*/ + pub async fn remove_user_to_group<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::RemoveUserToGroupBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/groups/removeuser/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list folders + +Sends a `GET` request to `/w/{workspace}/folders/list` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +*/ + pub async fn list_folders<'a>( + &'a self, + workspace: &'a str, + page: Option, + per_page: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/folders/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list folder names + +Sends a `GET` request to `/w/{workspace}/folders/listnames` + +Arguments: +- `workspace` +- `only_member_of`: only list the folders the user is member of (default false) +*/ + pub async fn list_folder_names<'a>( + &'a self, + workspace: &'a str, + only_member_of: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/folders/listnames", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &only_member_of { + query.push(("only_member_of", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create folder + +Sends a `POST` request to `/w/{workspace}/folders/create` + +Arguments: +- `workspace` +- `body`: create folder +*/ + pub async fn create_folder<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateFolderBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update folder + +Sends a `POST` request to `/w/{workspace}/folders/update/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: update folder +*/ + pub async fn update_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::UpdateFolderBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete folder + +Sends a `DELETE` request to `/w/{workspace}/folders/delete/{name}` + +*/ + pub async fn delete_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get folder + +Sends a `GET` request to `/w/{workspace}/folders/get/{name}` + +*/ + pub async fn get_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists folder + +Sends a `GET` request to `/w/{workspace}/folders/exists/{name}` + +*/ + pub async fn exists_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/exists/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get folder usage + +Sends a `GET` request to `/w/{workspace}/folders/getusage/{name}` + +*/ + pub async fn get_folder_usage<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/getusage/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**add owner to folder + +Sends a `POST` request to `/w/{workspace}/folders/addowner/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: owner user to folder +*/ + pub async fn add_owner_to_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::AddOwnerToFolderBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/addowner/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**remove owner to folder + +Sends a `POST` request to `/w/{workspace}/folders/removeowner/{name}` + +Arguments: +- `workspace` +- `name` +- `body`: added owner to folder +*/ + pub async fn remove_owner_to_folder<'a>( + &'a self, + workspace: &'a str, + name: &'a str, + body: &'a types::RemoveOwnerToFolderBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/folders/removeowner/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list workers + +Sends a `GET` request to `/workers/list` + +Arguments: +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +- `ping_since`: number of seconds the worker must have had a last ping more recent of (default to 300) +*/ + pub async fn list_workers<'a>( + &'a self, + page: Option, + per_page: Option, + ping_since: Option, + ) -> Result>, Error<()>> { + let url = format!("{}/workers/list", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &ping_since { + query.push(("ping_since", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**exists worker with tag + +Sends a `GET` request to `/workers/exists_worker_with_tag` + +*/ + pub async fn exists_worker_with_tag<'a>( + &'a self, + tag: &'a str, + ) -> Result, Error<()>> { + let url = format!("{}/workers/exists_worker_with_tag", self.baseurl,); + let mut query = Vec::with_capacity(1usize); + query.push(("tag", tag.to_string())); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get queue metrics + +Sends a `GET` request to `/workers/queue_metrics` + +*/ + pub async fn get_queue_metrics<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/workers/queue_metrics", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get counts of jobs waiting for an executor per tag + +Sends a `GET` request to `/workers/queue_counts` + +*/ + pub async fn get_counts_of_jobs_waiting_per_tag<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/workers/queue_counts", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list worker groups + +Sends a `GET` request to `/configs/list_worker_groups` + +*/ + pub async fn list_worker_groups<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/configs/list_worker_groups", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get config + +Sends a `GET` request to `/configs/get/{name}` + +*/ + pub async fn get_config<'a>( + &'a self, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/configs/get/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Update config + +Sends a `POST` request to `/configs/update/{name}` + +Arguments: +- `name` +- `body`: worker group +*/ + pub async fn update_config<'a>( + &'a self, + name: &'a str, + body: &'a serde_json::Value, + ) -> Result, Error<()>> { + let url = format!( + "{}/configs/update/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Delete Config + +Sends a `DELETE` request to `/configs/update/{name}` + +*/ + pub async fn delete_config<'a>( + &'a self, + name: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/configs/update/{}", self.baseurl, encode_path(& name.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list configs + +Sends a `GET` request to `/configs/list` + +*/ + pub async fn list_configs<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/configs/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**List autoscaling events + +Sends a `GET` request to `/configs/list_autoscaling_events/{worker_group}` + +*/ + pub async fn list_autoscaling_events<'a>( + &'a self, + worker_group: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/configs/list_autoscaling_events/{}", self.baseurl, encode_path(& + worker_group.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get granular acls + +Sends a `GET` request to `/w/{workspace}/acls/get/{kind}/{path}` + +*/ + pub async fn get_granular_acls<'a>( + &'a self, + workspace: &'a str, + kind: types::GetGranularAclsKind, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/acls/get/{}/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& kind.to_string()), encode_path(& path.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**add granular acls + +Sends a `POST` request to `/w/{workspace}/acls/add/{kind}/{path}` + +Arguments: +- `workspace` +- `kind` +- `path` +- `body`: acl to add +*/ + pub async fn add_granular_acls<'a>( + &'a self, + workspace: &'a str, + kind: types::AddGranularAclsKind, + path: &'a str, + body: &'a types::AddGranularAclsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/acls/add/{}/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& kind.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**remove granular acls + +Sends a `POST` request to `/w/{workspace}/acls/remove/{kind}/{path}` + +Arguments: +- `workspace` +- `kind` +- `path` +- `body`: acl to add +*/ + pub async fn remove_granular_acls<'a>( + &'a self, + workspace: &'a str, + kind: types::RemoveGranularAclsKind, + path: &'a str, + body: &'a types::RemoveGranularAclsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/acls/remove/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& kind.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set capture config + +Sends a `POST` request to `/w/{workspace}/capture/set_config` + +Arguments: +- `workspace` +- `body`: capture config +*/ + pub async fn set_capture_config<'a>( + &'a self, + workspace: &'a str, + body: &'a types::SetCaptureConfigBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/capture/set_config", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**ping capture config + +Sends a `POST` request to `/w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}` + +*/ + pub async fn ping_capture_config<'a>( + &'a self, + workspace: &'a str, + trigger_kind: types::CaptureTriggerKind, + runnable_kind: types::PingCaptureConfigRunnableKind, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/capture/ping_config/{}/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& trigger_kind.to_string()), encode_path(& + runnable_kind.to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get capture configs for a script or flow + +Sends a `GET` request to `/w/{workspace}/capture/get_configs/{runnable_kind}/{path}` + +*/ + pub async fn get_capture_configs<'a>( + &'a self, + workspace: &'a str, + runnable_kind: types::GetCaptureConfigsRunnableKind, + path: &'a str, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/capture/get_configs/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& runnable_kind.to_string()), encode_path(& path + .to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list captures for a script or flow + +Sends a `GET` request to `/w/{workspace}/capture/list/{runnable_kind}/{path}` + +Arguments: +- `workspace` +- `runnable_kind` +- `path` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +- `trigger_kind` +*/ + pub async fn list_captures<'a>( + &'a self, + workspace: &'a str, + runnable_kind: types::ListCapturesRunnableKind, + path: &'a str, + page: Option, + per_page: Option, + trigger_kind: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/capture/list/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& runnable_kind.to_string()), encode_path(& path + .to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &trigger_kind { + query.push(("trigger_kind", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**move captures and configs for a script or flow + +Sends a `POST` request to `/w/{workspace}/capture/move/{runnable_kind}/{path}` + +Arguments: +- `workspace` +- `runnable_kind` +- `path` +- `body`: move captures and configs to a new path +*/ + pub async fn move_captures_and_configs<'a>( + &'a self, + workspace: &'a str, + runnable_kind: types::MoveCapturesAndConfigsRunnableKind, + path: &'a str, + body: &'a types::MoveCapturesAndConfigsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/capture/move/{}/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& runnable_kind.to_string()), encode_path(& path + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get a capture + +Sends a `GET` request to `/w/{workspace}/capture/{id}` + +*/ + pub async fn get_capture<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/capture/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**delete a capture + +Sends a `DELETE` request to `/w/{workspace}/capture/{id}` + +*/ + pub async fn delete_capture<'a>( + &'a self, + workspace: &'a str, + id: i64, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/capture/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& id.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**star item + +Sends a `POST` request to `/w/{workspace}/favorites/star` + +*/ + pub async fn star<'a>( + &'a self, + workspace: &'a str, + body: &'a types::StarBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/favorites/star", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**unstar item + +Sends a `POST` request to `/w/{workspace}/favorites/unstar` + +*/ + pub async fn unstar<'a>( + &'a self, + workspace: &'a str, + body: &'a types::UnstarBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/favorites/unstar", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**List Inputs used in previously completed jobs + +Sends a `GET` request to `/w/{workspace}/inputs/history` + +Arguments: +- `workspace` +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `include_preview` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +- `runnable_id` +- `runnable_type` +*/ + pub async fn get_input_history<'a>( + &'a self, + workspace: &'a str, + args: Option<&'a str>, + include_preview: Option, + page: Option, + per_page: Option, + runnable_id: Option<&'a str>, + runnable_type: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/inputs/history", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(6usize); + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &include_preview { + query.push(("include_preview", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &runnable_id { + query.push(("runnable_id", v.to_string())); + } + if let Some(v) = &runnable_type { + query.push(("runnable_type", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Get args from history or saved input + +Sends a `GET` request to `/w/{workspace}/inputs/{jobOrInputId}/args` + +*/ + pub async fn get_args_from_history_or_saved_input<'a>( + &'a self, + workspace: &'a str, + job_or_input_id: &'a str, + allow_large: Option, + input: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/inputs/{}/args", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& job_or_input_id.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &allow_large { + query.push(("allow_large", v.to_string())); + } + if let Some(v) = &input { + query.push(("input", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**List saved Inputs for a Runnable + +Sends a `GET` request to `/w/{workspace}/inputs/list` + +Arguments: +- `workspace` +- `page`: which page to return (start at 1, default 1) +- `per_page`: number of items to return for a given page (default 30, max 100) +- `runnable_id` +- `runnable_type` +*/ + pub async fn list_inputs<'a>( + &'a self, + workspace: &'a str, + page: Option, + per_page: Option, + runnable_id: Option<&'a str>, + runnable_type: Option, + ) -> Result>, Error<()>> { + let url = format!( + "{}/w/{}/inputs/list", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &runnable_id { + query.push(("runnable_id", v.to_string())); + } + if let Some(v) = &runnable_type { + query.push(("runnable_type", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Create an Input for future use in a script or flow + +Sends a `POST` request to `/w/{workspace}/inputs/create` + +Arguments: +- `workspace` +- `runnable_id` +- `runnable_type` +- `body`: Input +*/ + pub async fn create_input<'a>( + &'a self, + workspace: &'a str, + runnable_id: Option<&'a str>, + runnable_type: Option, + body: &'a types::CreateInput, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/inputs/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &runnable_id { + query.push(("runnable_id", v.to_string())); + } + if let Some(v) = &runnable_type { + query.push(("runnable_type", v.to_string())); + } + let request = self.client.post(url).json(&body).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Update an Input + +Sends a `POST` request to `/w/{workspace}/inputs/update` + +Arguments: +- `workspace` +- `body`: UpdateInput +*/ + pub async fn update_input<'a>( + &'a self, + workspace: &'a str, + body: &'a types::UpdateInput, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/inputs/update", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Delete a Saved Input + +Sends a `POST` request to `/w/{workspace}/inputs/delete/{input}` + +*/ + pub async fn delete_input<'a>( + &'a self, + workspace: &'a str, + input: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/inputs/delete/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& input.to_string()), + ); + let request = self.client.post(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket + +Sends a `POST` request to `/w/{workspace}/job_helpers/duckdb_connection_settings` + +Arguments: +- `workspace` +- `body`: S3 resource to connect to +*/ + pub async fn duckdb_connection_settings<'a>( + &'a self, + workspace: &'a str, + body: &'a types::DuckdbConnectionSettingsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/duckdb_connection_settings", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket + +Sends a `POST` request to `/w/{workspace}/job_helpers/v2/duckdb_connection_settings` + +Arguments: +- `workspace` +- `body`: S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used +*/ + pub async fn duckdb_connection_settings_v2<'a>( + &'a self, + workspace: &'a str, + body: &'a types::DuckdbConnectionSettingsV2Body, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/v2/duckdb_connection_settings", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket + +Sends a `POST` request to `/w/{workspace}/job_helpers/polars_connection_settings` + +Arguments: +- `workspace` +- `body`: S3 resource to connect to +*/ + pub async fn polars_connection_settings<'a>( + &'a self, + workspace: &'a str, + body: &'a types::PolarsConnectionSettingsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/polars_connection_settings", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket + +Sends a `POST` request to `/w/{workspace}/job_helpers/v2/polars_connection_settings` + +Arguments: +- `workspace` +- `body`: S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used +*/ + pub async fn polars_connection_settings_v2<'a>( + &'a self, + workspace: &'a str, + body: &'a types::PolarsConnectionSettingsV2Body, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/v2/polars_connection_settings", self.baseurl, + encode_path(& workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Returns the s3 resource associated to the provided path, or the workspace default S3 resource + +Sends a `POST` request to `/w/{workspace}/job_helpers/v2/s3_resource_info` + +Arguments: +- `workspace` +- `body`: S3 resource path to use. If empty, the S3 resource defined in the workspace settings will be used +*/ + pub async fn s3_resource_info<'a>( + &'a self, + workspace: &'a str, + body: &'a types::S3ResourceInfoBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/v2/s3_resource_info", self.baseurl, encode_path(& + workspace.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Test connection to the workspace object storage + +Sends a `GET` request to `/w/{workspace}/job_helpers/test_connection` + +*/ + pub async fn dataset_storage_test_connection<'a>( + &'a self, + workspace: &'a str, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/test_connection", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**List the file keys available in a workspace object storage + +Sends a `GET` request to `/w/{workspace}/job_helpers/list_stored_files` + +*/ + pub async fn list_stored_files<'a>( + &'a self, + workspace: &'a str, + marker: Option<&'a str>, + max_keys: i64, + prefix: Option<&'a str>, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/list_stored_files", self.baseurl, encode_path(& + workspace.to_string()), + ); + let mut query = Vec::with_capacity(4usize); + if let Some(v) = &marker { + query.push(("marker", v.to_string())); + } + query.push(("max_keys", max_keys.to_string())); + if let Some(v) = &prefix { + query.push(("prefix", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Load metadata of the file + +Sends a `GET` request to `/w/{workspace}/job_helpers/load_file_metadata` + +*/ + pub async fn load_file_metadata<'a>( + &'a self, + workspace: &'a str, + file_key: &'a str, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/load_file_metadata", self.baseurl, encode_path(& + workspace.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + query.push(("file_key", file_key.to_string())); + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Load a preview of the file + +Sends a `GET` request to `/w/{workspace}/job_helpers/load_file_preview` + +*/ + pub async fn load_file_preview<'a>( + &'a self, + workspace: &'a str, + csv_has_header: Option, + csv_separator: Option<&'a str>, + file_key: &'a str, + file_mime_type: Option<&'a str>, + file_size_in_bytes: Option, + read_bytes_from: Option, + read_bytes_length: Option, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/load_file_preview", self.baseurl, encode_path(& + workspace.to_string()), + ); + let mut query = Vec::with_capacity(8usize); + if let Some(v) = &csv_has_header { + query.push(("csv_has_header", v.to_string())); + } + if let Some(v) = &csv_separator { + query.push(("csv_separator", v.to_string())); + } + query.push(("file_key", file_key.to_string())); + if let Some(v) = &file_mime_type { + query.push(("file_mime_type", v.to_string())); + } + if let Some(v) = &file_size_in_bytes { + query.push(("file_size_in_bytes", v.to_string())); + } + if let Some(v) = &read_bytes_from { + query.push(("read_bytes_from", v.to_string())); + } + if let Some(v) = &read_bytes_length { + query.push(("read_bytes_length", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Load a preview of a parquet file + +Sends a `GET` request to `/w/{workspace}/job_helpers/load_parquet_preview/{path}` + +*/ + pub async fn load_parquet_preview<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + limit: Option, + offset: Option, + search_col: Option<&'a str>, + search_term: Option<&'a str>, + sort_col: Option<&'a str>, + sort_desc: Option, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/load_parquet_preview/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + if let Some(v) = &offset { + query.push(("offset", v.to_string())); + } + if let Some(v) = &search_col { + query.push(("search_col", v.to_string())); + } + if let Some(v) = &search_term { + query.push(("search_term", v.to_string())); + } + if let Some(v) = &sort_col { + query.push(("sort_col", v.to_string())); + } + if let Some(v) = &sort_desc { + query.push(("sort_desc", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Load the table row count + +Sends a `GET` request to `/w/{workspace}/job_helpers/load_table_count/{path}` + +*/ + pub async fn load_table_row_count<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + search_col: Option<&'a str>, + search_term: Option<&'a str>, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/load_table_count/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &search_col { + query.push(("search_col", v.to_string())); + } + if let Some(v) = &search_term { + query.push(("search_term", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Load a preview of a csv file + +Sends a `GET` request to `/w/{workspace}/job_helpers/load_csv_preview/{path}` + +*/ + pub async fn load_csv_preview<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + csv_separator: Option<&'a str>, + limit: Option, + offset: Option, + search_col: Option<&'a str>, + search_term: Option<&'a str>, + sort_col: Option<&'a str>, + sort_desc: Option, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/load_csv_preview/{}", self.baseurl, encode_path(& + workspace.to_string()), encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(8usize); + if let Some(v) = &csv_separator { + query.push(("csv_separator", v.to_string())); + } + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + if let Some(v) = &offset { + query.push(("offset", v.to_string())); + } + if let Some(v) = &search_col { + query.push(("search_col", v.to_string())); + } + if let Some(v) = &search_term { + query.push(("search_term", v.to_string())); + } + if let Some(v) = &sort_col { + query.push(("sort_col", v.to_string())); + } + if let Some(v) = &sort_desc { + query.push(("sort_desc", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Permanently delete file from S3 + +Sends a `DELETE` request to `/w/{workspace}/job_helpers/delete_s3_file` + +*/ + pub async fn delete_s3_file<'a>( + &'a self, + workspace: &'a str, + file_key: &'a str, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/delete_s3_file", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(2usize); + query.push(("file_key", file_key.to_string())); + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .delete(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Move a S3 file from one path to the other within the same bucket + +Sends a `GET` request to `/w/{workspace}/job_helpers/move_s3_file` + +*/ + pub async fn move_s3_file<'a>( + &'a self, + workspace: &'a str, + dest_file_key: &'a str, + src_file_key: &'a str, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/move_s3_file", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(3usize); + query.push(("dest_file_key", dest_file_key.to_string())); + query.push(("src_file_key", src_file_key.to_string())); + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Upload file to S3 bucket + +Sends a `POST` request to `/w/{workspace}/job_helpers/upload_s3_file` + +Arguments: +- `workspace` +- `content_disposition` +- `content_type` +- `file_extension` +- `file_key` +- `resource_type` +- `s3_resource_path` +- `storage` +- `body`: File content +*/ + pub async fn file_upload<'a, B: Into>( + &'a self, + workspace: &'a str, + content_disposition: Option<&'a str>, + content_type: Option<&'a str>, + file_extension: Option<&'a str>, + file_key: Option<&'a str>, + resource_type: Option<&'a str>, + s3_resource_path: Option<&'a str>, + storage: Option<&'a str>, + body: B, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/upload_s3_file", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(7usize); + if let Some(v) = &content_disposition { + query.push(("content_disposition", v.to_string())); + } + if let Some(v) = &content_type { + query.push(("content_type", v.to_string())); + } + if let Some(v) = &file_extension { + query.push(("file_extension", v.to_string())); + } + if let Some(v) = &file_key { + query.push(("file_key", v.to_string())); + } + if let Some(v) = &resource_type { + query.push(("resource_type", v.to_string())); + } + if let Some(v) = &s3_resource_path { + query.push(("s3_resource_path", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::CONTENT_TYPE, + reqwest::header::HeaderValue::from_static("application/octet-stream"), + ) + .body(body) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Download file from S3 bucket + +Sends a `GET` request to `/w/{workspace}/job_helpers/download_s3_file` + +*/ + pub async fn file_download<'a>( + &'a self, + workspace: &'a str, + file_key: &'a str, + resource_type: Option<&'a str>, + s3_resource_path: Option<&'a str>, + storage: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/download_s3_file", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(4usize); + query.push(("file_key", file_key.to_string())); + if let Some(v) = &resource_type { + query.push(("resource_type", v.to_string())); + } + if let Some(v) = &s3_resource_path { + query.push(("s3_resource_path", v.to_string())); + } + if let Some(v) = &storage { + query.push(("storage", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Download file to S3 bucket + +Sends a `GET` request to `/w/{workspace}/job_helpers/download_s3_parquet_file_as_csv` + +*/ + pub async fn file_download_parquet_as_csv<'a>( + &'a self, + workspace: &'a str, + file_key: &'a str, + resource_type: Option<&'a str>, + s3_resource_path: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_helpers/download_s3_parquet_file_as_csv", self.baseurl, + encode_path(& workspace.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + query.push(("file_key", file_key.to_string())); + if let Some(v) = &resource_type { + query.push(("resource_type", v.to_string())); + } + if let Some(v) = &s3_resource_path { + query.push(("s3_resource_path", v.to_string())); + } + let request = self.client.get(url).query(&query).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job metrics + +Sends a `POST` request to `/w/{workspace}/job_metrics/get/{id}` + +Arguments: +- `workspace` +- `id` +- `body`: parameters for statistics retrieval +*/ + pub async fn get_job_metrics<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + body: &'a types::GetJobMetricsBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_metrics/get/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**set job metrics + +Sends a `POST` request to `/w/{workspace}/job_metrics/set_progress/{id}` + +Arguments: +- `workspace` +- `id` +- `body`: parameters for statistics retrieval +*/ + pub async fn set_job_progress<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + body: &'a types::SetJobProgressBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_metrics/set_progress/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get job progress + +Sends a `GET` request to `/w/{workspace}/job_metrics/get_progress/{id}` + +*/ + pub async fn get_job_progress<'a>( + &'a self, + workspace: &'a str, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/job_metrics/get_progress/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**list log files ordered by timestamp + +Sends a `GET` request to `/service_logs/list_files` + +Arguments: +- `after`: filter on created after (exclusive) timestamp +- `before`: filter on started before (inclusive) timestamp +- `with_error` +*/ + pub async fn list_log_files<'a>( + &'a self, + after: Option<&'a chrono::DateTime>, + before: Option<&'a chrono::DateTime>, + with_error: Option, + ) -> Result>, Error<()>> { + let url = format!("{}/service_logs/list_files", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &after { + query.push(("after", v.to_string())); + } + if let Some(v) = &before { + query.push(("before", v.to_string())); + } + if let Some(v) = &with_error { + query.push(("with_error", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get log file by path + +Sends a `GET` request to `/service_logs/get_log_file/{path}` + +*/ + pub async fn get_log_file<'a>( + &'a self, + path: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/service_logs/get_log_file/{}", self.baseurl, encode_path(& path + .to_string()), + ); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**List all concurrency groups + +Sends a `GET` request to `/concurrency_groups/list` + +*/ + pub async fn list_concurrency_groups<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/concurrency_groups/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Delete concurrency group + +Sends a `DELETE` request to `/concurrency_groups/prune/{concurrency_id}` + +*/ + pub async fn delete_concurrency_group<'a>( + &'a self, + concurrency_id: &'a str, + ) -> Result< + ResponseValue>, + Error<()>, + > { + let url = format!( + "{}/concurrency_groups/prune/{}", self.baseurl, encode_path(& concurrency_id + .to_string()), + ); + let request = self + .client + .delete(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Get the concurrency key for a job that has concurrency limits enabled + +Sends a `GET` request to `/concurrency_groups/{id}/key` + +*/ + pub async fn get_concurrency_key<'a>( + &'a self, + id: &'a uuid::Uuid, + ) -> Result, Error<()>> { + let url = format!( + "{}/concurrency_groups/{}/key", self.baseurl, encode_path(& id.to_string()), + ); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Get intervals of job runtime concurrency + +Sends a `GET` request to `/w/{workspace}/concurrency_groups/list_jobs` + +Arguments: +- `workspace` +- `all_workspaces`: get jobs from all workspaces (only valid if request come from the `admins` workspace) +- `args`: filter on jobs containing those args as a json subset (@> in postgres) +- `concurrency_key` +- `created_by`: mask to filter exact matching user creator +- `created_or_started_after`: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp +- `created_or_started_after_completed_jobs`: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs +- `created_or_started_before`: filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp +- `has_null_parent`: has null parent +- `is_flow_step`: is the job a flow step +- `is_not_schedule`: is not a scheduled job +- `is_skipped`: is the job skipped +- `job_kinds`: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, +- `label`: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') +- `page`: which page to return (start at 1, default 1) +- `parent_job`: The parent job that is at the origin and responsible for the execution of this script if any +- `per_page`: number of items to return for a given page (default 30, max 100) +- `result`: filter on jobs containing those result as a json subset (@> in postgres) +- `row_limit` +- `running`: filter on running jobs +- `schedule_path`: mask to filter by schedule path +- `scheduled_for_before_now`: filter on jobs scheduled_for before now (hence waitinf for a worker) +- `script_hash`: mask to filter exact matching path +- `script_path_exact`: mask to filter exact matching path +- `script_path_start`: mask to filter matching starting path +- `started_after`: filter on started after (exclusive) timestamp +- `started_before`: filter on started before (inclusive) timestamp +- `success`: filter on successful jobs +- `tag`: filter on jobs with a given tag/worker group +*/ + pub async fn list_extended_jobs<'a>( + &'a self, + workspace: &'a str, + all_workspaces: Option, + args: Option<&'a str>, + concurrency_key: Option<&'a str>, + created_by: Option<&'a str>, + created_or_started_after: Option<&'a chrono::DateTime>, + created_or_started_after_completed_jobs: Option< + &'a chrono::DateTime, + >, + created_or_started_before: Option<&'a chrono::DateTime>, + has_null_parent: Option, + is_flow_step: Option, + is_not_schedule: Option, + is_skipped: Option, + job_kinds: Option<&'a str>, + label: Option<&'a str>, + page: Option, + parent_job: Option<&'a uuid::Uuid>, + per_page: Option, + result: Option<&'a str>, + row_limit: Option, + running: Option, + schedule_path: Option<&'a str>, + scheduled_for_before_now: Option, + script_hash: Option<&'a str>, + script_path_exact: Option<&'a str>, + script_path_start: Option<&'a str>, + started_after: Option<&'a chrono::DateTime>, + started_before: Option<&'a chrono::DateTime>, + success: Option, + tag: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/concurrency_groups/list_jobs", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(28usize); + if let Some(v) = &all_workspaces { + query.push(("all_workspaces", v.to_string())); + } + if let Some(v) = &args { + query.push(("args", v.to_string())); + } + if let Some(v) = &concurrency_key { + query.push(("concurrency_key", v.to_string())); + } + if let Some(v) = &created_by { + query.push(("created_by", v.to_string())); + } + if let Some(v) = &created_or_started_after { + query.push(("created_or_started_after", v.to_string())); + } + if let Some(v) = &created_or_started_after_completed_jobs { + query.push(("created_or_started_after_completed_jobs", v.to_string())); + } + if let Some(v) = &created_or_started_before { + query.push(("created_or_started_before", v.to_string())); + } + if let Some(v) = &has_null_parent { + query.push(("has_null_parent", v.to_string())); + } + if let Some(v) = &is_flow_step { + query.push(("is_flow_step", v.to_string())); + } + if let Some(v) = &is_not_schedule { + query.push(("is_not_schedule", v.to_string())); + } + if let Some(v) = &is_skipped { + query.push(("is_skipped", v.to_string())); + } + if let Some(v) = &job_kinds { + query.push(("job_kinds", v.to_string())); + } + if let Some(v) = &label { + query.push(("label", v.to_string())); + } + if let Some(v) = &page { + query.push(("page", v.to_string())); + } + if let Some(v) = &parent_job { + query.push(("parent_job", v.to_string())); + } + if let Some(v) = &per_page { + query.push(("per_page", v.to_string())); + } + if let Some(v) = &result { + query.push(("result", v.to_string())); + } + if let Some(v) = &row_limit { + query.push(("row_limit", v.to_string())); + } + if let Some(v) = &running { + query.push(("running", v.to_string())); + } + if let Some(v) = &schedule_path { + query.push(("schedule_path", v.to_string())); + } + if let Some(v) = &scheduled_for_before_now { + query.push(("scheduled_for_before_now", v.to_string())); + } + if let Some(v) = &script_hash { + query.push(("script_hash", v.to_string())); + } + if let Some(v) = &script_path_exact { + query.push(("script_path_exact", v.to_string())); + } + if let Some(v) = &script_path_start { + query.push(("script_path_start", v.to_string())); + } + if let Some(v) = &started_after { + query.push(("started_after", v.to_string())); + } + if let Some(v) = &started_before { + query.push(("started_before", v.to_string())); + } + if let Some(v) = &success { + query.push(("success", v.to_string())); + } + if let Some(v) = &tag { + query.push(("tag", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Search through jobs with a string query + +Sends a `GET` request to `/srch/w/{workspace}/index/search/job` + +*/ + pub async fn search_jobs_index<'a>( + &'a self, + workspace: &'a str, + search_query: &'a str, + ) -> Result, Error<()>> { + let url = format!( + "{}/srch/w/{}/index/search/job", self.baseurl, encode_path(& workspace + .to_string()), + ); + let mut query = Vec::with_capacity(1usize); + query.push(("search_query", search_query.to_string())); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Search through service logs with a string query + +Sends a `GET` request to `/srch/index/search/service_logs` + +*/ + pub async fn search_logs_index<'a>( + &'a self, + hostname: &'a str, + max_ts: Option<&'a chrono::DateTime>, + min_ts: Option<&'a chrono::DateTime>, + mode: &'a str, + search_query: &'a str, + worker_group: Option<&'a str>, + ) -> Result, Error<()>> { + let url = format!("{}/srch/index/search/service_logs", self.baseurl,); + let mut query = Vec::with_capacity(6usize); + query.push(("hostname", hostname.to_string())); + if let Some(v) = &max_ts { + query.push(("max_ts", v.to_string())); + } + if let Some(v) = &min_ts { + query.push(("min_ts", v.to_string())); + } + query.push(("mode", mode.to_string())); + query.push(("search_query", search_query.to_string())); + if let Some(v) = &worker_group { + query.push(("worker_group", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Search and count the log line hits on every provided host + +Sends a `GET` request to `/srch/index/search/count_service_logs` + +*/ + pub async fn count_search_logs_index<'a>( + &'a self, + max_ts: Option<&'a chrono::DateTime>, + min_ts: Option<&'a chrono::DateTime>, + search_query: &'a str, + ) -> Result, Error<()>> { + let url = format!("{}/srch/index/search/count_service_logs", self.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &max_ts { + query.push(("max_ts", v.to_string())); + } + if let Some(v) = &min_ts { + query.push(("min_ts", v.to_string())); + } + query.push(("search_query", search_query.to_string())); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**Restart container and delete the index to recreate it + +Sends a `DELETE` request to `/srch/index/delete/{idx_name}` + +*/ + pub async fn clear_index<'a>( + &'a self, + idx_name: types::ClearIndexIdxName, + ) -> Result, Error<()>> { + let url = format!( + "{}/srch/index/delete/{}", self.baseurl, encode_path(& idx_name.to_string()), + ); + let request = self.client.delete(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } +} +pub mod prelude { + pub use super::Client; +} diff --git a/backend/windmill-api-client/src/codegen.rs b/backend/windmill-api-client/src/codegen.rs new file mode 100644 index 0000000000..9bc26262d5 --- /dev/null +++ b/backend/windmill-api-client/src/codegen.rs @@ -0,0 +1,6904 @@ +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +#[allow(unused_imports)] +use reqwest::header::{HeaderMap, HeaderValue}; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AiProvider { + #[serde(rename = "openai")] + Openai, + #[serde(rename = "anthropic")] + Anthropic, + #[serde(rename = "mistral")] + Mistral, + #[serde(rename = "deepseek")] + Deepseek, + #[serde(rename = "googleai")] + Googleai, + #[serde(rename = "groq")] + Groq, + #[serde(rename = "openrouter")] + Openrouter, + #[serde(rename = "customai")] + Customai, + } + impl From<&AiProvider> for AiProvider { + fn from(value: &AiProvider) -> Self { + value.clone() + } + } + impl ToString for AiProvider { + fn to_string(&self) -> String { + match *self { + Self::Openai => "openai".to_string(), + Self::Anthropic => "anthropic".to_string(), + Self::Mistral => "mistral".to_string(), + Self::Deepseek => "deepseek".to_string(), + Self::Googleai => "googleai".to_string(), + Self::Groq => "groq".to_string(), + Self::Openrouter => "openrouter".to_string(), + Self::Customai => "customai".to_string(), + } + } + } + impl std::str::FromStr for AiProvider { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "openai" => Ok(Self::Openai), + "anthropic" => Ok(Self::Anthropic), + "mistral" => Ok(Self::Mistral), + "deepseek" => Ok(Self::Deepseek), + "googleai" => Ok(Self::Googleai), + "groq" => Ok(Self::Groq), + "openrouter" => Ok(Self::Openrouter), + "customai" => Ok(Self::Customai), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AiProvider { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AiProvider { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AiProvider { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AiResource { + pub path: String, + pub provider: AiProvider, + } + impl From<&AiResource> for AiResource { + fn from(value: &AiResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppHistory { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub version: i64, + } + impl From<&AppHistory> for AppHistory { + fn from(value: &AppHistory) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppWithLastVersion { + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_path: Option, + pub execution_mode: AppWithLastVersionExecutionMode, + pub extra_perms: std::collections::HashMap, + pub id: i64, + pub path: String, + pub policy: Policy, + pub summary: String, + pub value: std::collections::HashMap, + pub versions: Vec, + pub workspace_id: String, + } + impl From<&AppWithLastVersion> for AppWithLastVersion { + fn from(value: &AppWithLastVersion) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AppWithLastVersionExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&AppWithLastVersionExecutionMode> for AppWithLastVersionExecutionMode { + fn from(value: &AppWithLastVersionExecutionMode) -> Self { + value.clone() + } + } + impl ToString for AppWithLastVersionExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for AppWithLastVersionExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AppWithLastVersionExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AppWithLastVersionWDraft { + #[serde(flatten)] + pub app_with_last_version: AppWithLastVersion, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + } + impl From<&AppWithLastVersionWDraft> for AppWithLastVersionWDraft { + fn from(value: &AppWithLastVersionWDraft) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AuditLog { + pub action_kind: AuditLogActionKind, + pub id: i64, + pub operation: AuditLogOperation, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub parameters: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, + pub timestamp: chrono::DateTime, + pub username: String, + } + impl From<&AuditLog> for AuditLog { + fn from(value: &AuditLog) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AuditLogActionKind { + Created, + Updated, + Delete, + Execute, + } + impl From<&AuditLogActionKind> for AuditLogActionKind { + fn from(value: &AuditLogActionKind) -> Self { + value.clone() + } + } + impl ToString for AuditLogActionKind { + fn to_string(&self) -> String { + match *self { + Self::Created => "Created".to_string(), + Self::Updated => "Updated".to_string(), + Self::Delete => "Delete".to_string(), + Self::Execute => "Execute".to_string(), + } + } + } + impl std::str::FromStr for AuditLogActionKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Created" => Ok(Self::Created), + "Updated" => Ok(Self::Updated), + "Delete" => Ok(Self::Delete), + "Execute" => Ok(Self::Execute), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AuditLogActionKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AuditLogOperation { + #[serde(rename = "jobs.run")] + JobsRun, + #[serde(rename = "jobs.run.script")] + JobsRunScript, + #[serde(rename = "jobs.run.preview")] + JobsRunPreview, + #[serde(rename = "jobs.run.flow")] + JobsRunFlow, + #[serde(rename = "jobs.run.flow_preview")] + JobsRunFlowPreview, + #[serde(rename = "jobs.run.script_hub")] + JobsRunScriptHub, + #[serde(rename = "jobs.run.dependencies")] + JobsRunDependencies, + #[serde(rename = "jobs.run.identity")] + JobsRunIdentity, + #[serde(rename = "jobs.run.noop")] + JobsRunNoop, + #[serde(rename = "jobs.flow_dependencies")] + JobsFlowDependencies, + #[serde(rename = "jobs")] + Jobs, + #[serde(rename = "jobs.cancel")] + JobsCancel, + #[serde(rename = "jobs.force_cancel")] + JobsForceCancel, + #[serde(rename = "jobs.disapproval")] + JobsDisapproval, + #[serde(rename = "jobs.delete")] + JobsDelete, + #[serde(rename = "account.delete")] + AccountDelete, + #[serde(rename = "ai.request")] + AiRequest, + #[serde(rename = "resources.create")] + ResourcesCreate, + #[serde(rename = "resources.update")] + ResourcesUpdate, + #[serde(rename = "resources.delete")] + ResourcesDelete, + #[serde(rename = "resource_types.create")] + ResourceTypesCreate, + #[serde(rename = "resource_types.update")] + ResourceTypesUpdate, + #[serde(rename = "resource_types.delete")] + ResourceTypesDelete, + #[serde(rename = "schedule.create")] + ScheduleCreate, + #[serde(rename = "schedule.setenabled")] + ScheduleSetenabled, + #[serde(rename = "schedule.edit")] + ScheduleEdit, + #[serde(rename = "schedule.delete")] + ScheduleDelete, + #[serde(rename = "scripts.create")] + ScriptsCreate, + #[serde(rename = "scripts.update")] + ScriptsUpdate, + #[serde(rename = "scripts.archive")] + ScriptsArchive, + #[serde(rename = "scripts.delete")] + ScriptsDelete, + #[serde(rename = "users.create")] + UsersCreate, + #[serde(rename = "users.delete")] + UsersDelete, + #[serde(rename = "users.update")] + UsersUpdate, + #[serde(rename = "users.login")] + UsersLogin, + #[serde(rename = "users.login_failure")] + UsersLoginFailure, + #[serde(rename = "users.logout")] + UsersLogout, + #[serde(rename = "users.accept_invite")] + UsersAcceptInvite, + #[serde(rename = "users.decline_invite")] + UsersDeclineInvite, + #[serde(rename = "users.token.create")] + UsersTokenCreate, + #[serde(rename = "users.token.delete")] + UsersTokenDelete, + #[serde(rename = "users.add_to_workspace")] + UsersAddToWorkspace, + #[serde(rename = "users.add_global")] + UsersAddGlobal, + #[serde(rename = "users.setpassword")] + UsersSetpassword, + #[serde(rename = "users.impersonate")] + UsersImpersonate, + #[serde(rename = "users.leave_workspace")] + UsersLeaveWorkspace, + #[serde(rename = "oauth.login")] + OauthLogin, + #[serde(rename = "oauth.login_failure")] + OauthLoginFailure, + #[serde(rename = "oauth.signup")] + OauthSignup, + #[serde(rename = "variables.create")] + VariablesCreate, + #[serde(rename = "variables.delete")] + VariablesDelete, + #[serde(rename = "variables.update")] + VariablesUpdate, + #[serde(rename = "flows.create")] + FlowsCreate, + #[serde(rename = "flows.update")] + FlowsUpdate, + #[serde(rename = "flows.delete")] + FlowsDelete, + #[serde(rename = "flows.archive")] + FlowsArchive, + #[serde(rename = "apps.create")] + AppsCreate, + #[serde(rename = "apps.update")] + AppsUpdate, + #[serde(rename = "apps.delete")] + AppsDelete, + #[serde(rename = "folder.create")] + FolderCreate, + #[serde(rename = "folder.update")] + FolderUpdate, + #[serde(rename = "folder.delete")] + FolderDelete, + #[serde(rename = "folder.add_owner")] + FolderAddOwner, + #[serde(rename = "folder.remove_owner")] + FolderRemoveOwner, + #[serde(rename = "group.create")] + GroupCreate, + #[serde(rename = "group.delete")] + GroupDelete, + #[serde(rename = "group.edit")] + GroupEdit, + #[serde(rename = "group.adduser")] + GroupAdduser, + #[serde(rename = "group.removeuser")] + GroupRemoveuser, + #[serde(rename = "igroup.create")] + IgroupCreate, + #[serde(rename = "igroup.delete")] + IgroupDelete, + #[serde(rename = "igroup.adduser")] + IgroupAdduser, + #[serde(rename = "igroup.removeuser")] + IgroupRemoveuser, + #[serde(rename = "variables.decrypt_secret")] + VariablesDecryptSecret, + #[serde(rename = "workspaces.edit_command_script")] + WorkspacesEditCommandScript, + #[serde(rename = "workspaces.edit_deploy_to")] + WorkspacesEditDeployTo, + #[serde(rename = "workspaces.edit_auto_invite_domain")] + WorkspacesEditAutoInviteDomain, + #[serde(rename = "workspaces.edit_webhook")] + WorkspacesEditWebhook, + #[serde(rename = "workspaces.edit_copilot_config")] + WorkspacesEditCopilotConfig, + #[serde(rename = "workspaces.edit_error_handler")] + WorkspacesEditErrorHandler, + #[serde(rename = "workspaces.create")] + WorkspacesCreate, + #[serde(rename = "workspaces.update")] + WorkspacesUpdate, + #[serde(rename = "workspaces.archive")] + WorkspacesArchive, + #[serde(rename = "workspaces.unarchive")] + WorkspacesUnarchive, + #[serde(rename = "workspaces.delete")] + WorkspacesDelete, + } + impl From<&AuditLogOperation> for AuditLogOperation { + fn from(value: &AuditLogOperation) -> Self { + value.clone() + } + } + impl ToString for AuditLogOperation { + fn to_string(&self) -> String { + match *self { + Self::JobsRun => "jobs.run".to_string(), + Self::JobsRunScript => "jobs.run.script".to_string(), + Self::JobsRunPreview => "jobs.run.preview".to_string(), + Self::JobsRunFlow => "jobs.run.flow".to_string(), + Self::JobsRunFlowPreview => "jobs.run.flow_preview".to_string(), + Self::JobsRunScriptHub => "jobs.run.script_hub".to_string(), + Self::JobsRunDependencies => "jobs.run.dependencies".to_string(), + Self::JobsRunIdentity => "jobs.run.identity".to_string(), + Self::JobsRunNoop => "jobs.run.noop".to_string(), + Self::JobsFlowDependencies => "jobs.flow_dependencies".to_string(), + Self::Jobs => "jobs".to_string(), + Self::JobsCancel => "jobs.cancel".to_string(), + Self::JobsForceCancel => "jobs.force_cancel".to_string(), + Self::JobsDisapproval => "jobs.disapproval".to_string(), + Self::JobsDelete => "jobs.delete".to_string(), + Self::AccountDelete => "account.delete".to_string(), + Self::AiRequest => "ai.request".to_string(), + Self::ResourcesCreate => "resources.create".to_string(), + Self::ResourcesUpdate => "resources.update".to_string(), + Self::ResourcesDelete => "resources.delete".to_string(), + Self::ResourceTypesCreate => "resource_types.create".to_string(), + Self::ResourceTypesUpdate => "resource_types.update".to_string(), + Self::ResourceTypesDelete => "resource_types.delete".to_string(), + Self::ScheduleCreate => "schedule.create".to_string(), + Self::ScheduleSetenabled => "schedule.setenabled".to_string(), + Self::ScheduleEdit => "schedule.edit".to_string(), + Self::ScheduleDelete => "schedule.delete".to_string(), + Self::ScriptsCreate => "scripts.create".to_string(), + Self::ScriptsUpdate => "scripts.update".to_string(), + Self::ScriptsArchive => "scripts.archive".to_string(), + Self::ScriptsDelete => "scripts.delete".to_string(), + Self::UsersCreate => "users.create".to_string(), + Self::UsersDelete => "users.delete".to_string(), + Self::UsersUpdate => "users.update".to_string(), + Self::UsersLogin => "users.login".to_string(), + Self::UsersLoginFailure => "users.login_failure".to_string(), + Self::UsersLogout => "users.logout".to_string(), + Self::UsersAcceptInvite => "users.accept_invite".to_string(), + Self::UsersDeclineInvite => "users.decline_invite".to_string(), + Self::UsersTokenCreate => "users.token.create".to_string(), + Self::UsersTokenDelete => "users.token.delete".to_string(), + Self::UsersAddToWorkspace => "users.add_to_workspace".to_string(), + Self::UsersAddGlobal => "users.add_global".to_string(), + Self::UsersSetpassword => "users.setpassword".to_string(), + Self::UsersImpersonate => "users.impersonate".to_string(), + Self::UsersLeaveWorkspace => "users.leave_workspace".to_string(), + Self::OauthLogin => "oauth.login".to_string(), + Self::OauthLoginFailure => "oauth.login_failure".to_string(), + Self::OauthSignup => "oauth.signup".to_string(), + Self::VariablesCreate => "variables.create".to_string(), + Self::VariablesDelete => "variables.delete".to_string(), + Self::VariablesUpdate => "variables.update".to_string(), + Self::FlowsCreate => "flows.create".to_string(), + Self::FlowsUpdate => "flows.update".to_string(), + Self::FlowsDelete => "flows.delete".to_string(), + Self::FlowsArchive => "flows.archive".to_string(), + Self::AppsCreate => "apps.create".to_string(), + Self::AppsUpdate => "apps.update".to_string(), + Self::AppsDelete => "apps.delete".to_string(), + Self::FolderCreate => "folder.create".to_string(), + Self::FolderUpdate => "folder.update".to_string(), + Self::FolderDelete => "folder.delete".to_string(), + Self::FolderAddOwner => "folder.add_owner".to_string(), + Self::FolderRemoveOwner => "folder.remove_owner".to_string(), + Self::GroupCreate => "group.create".to_string(), + Self::GroupDelete => "group.delete".to_string(), + Self::GroupEdit => "group.edit".to_string(), + Self::GroupAdduser => "group.adduser".to_string(), + Self::GroupRemoveuser => "group.removeuser".to_string(), + Self::IgroupCreate => "igroup.create".to_string(), + Self::IgroupDelete => "igroup.delete".to_string(), + Self::IgroupAdduser => "igroup.adduser".to_string(), + Self::IgroupRemoveuser => "igroup.removeuser".to_string(), + Self::VariablesDecryptSecret => "variables.decrypt_secret".to_string(), + Self::WorkspacesEditCommandScript => { + "workspaces.edit_command_script".to_string() + } + Self::WorkspacesEditDeployTo => "workspaces.edit_deploy_to".to_string(), + Self::WorkspacesEditAutoInviteDomain => { + "workspaces.edit_auto_invite_domain".to_string() + } + Self::WorkspacesEditWebhook => "workspaces.edit_webhook".to_string(), + Self::WorkspacesEditCopilotConfig => { + "workspaces.edit_copilot_config".to_string() + } + Self::WorkspacesEditErrorHandler => { + "workspaces.edit_error_handler".to_string() + } + Self::WorkspacesCreate => "workspaces.create".to_string(), + Self::WorkspacesUpdate => "workspaces.update".to_string(), + Self::WorkspacesArchive => "workspaces.archive".to_string(), + Self::WorkspacesUnarchive => "workspaces.unarchive".to_string(), + Self::WorkspacesDelete => "workspaces.delete".to_string(), + } + } + } + impl std::str::FromStr for AuditLogOperation { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "jobs.run" => Ok(Self::JobsRun), + "jobs.run.script" => Ok(Self::JobsRunScript), + "jobs.run.preview" => Ok(Self::JobsRunPreview), + "jobs.run.flow" => Ok(Self::JobsRunFlow), + "jobs.run.flow_preview" => Ok(Self::JobsRunFlowPreview), + "jobs.run.script_hub" => Ok(Self::JobsRunScriptHub), + "jobs.run.dependencies" => Ok(Self::JobsRunDependencies), + "jobs.run.identity" => Ok(Self::JobsRunIdentity), + "jobs.run.noop" => Ok(Self::JobsRunNoop), + "jobs.flow_dependencies" => Ok(Self::JobsFlowDependencies), + "jobs" => Ok(Self::Jobs), + "jobs.cancel" => Ok(Self::JobsCancel), + "jobs.force_cancel" => Ok(Self::JobsForceCancel), + "jobs.disapproval" => Ok(Self::JobsDisapproval), + "jobs.delete" => Ok(Self::JobsDelete), + "account.delete" => Ok(Self::AccountDelete), + "ai.request" => Ok(Self::AiRequest), + "resources.create" => Ok(Self::ResourcesCreate), + "resources.update" => Ok(Self::ResourcesUpdate), + "resources.delete" => Ok(Self::ResourcesDelete), + "resource_types.create" => Ok(Self::ResourceTypesCreate), + "resource_types.update" => Ok(Self::ResourceTypesUpdate), + "resource_types.delete" => Ok(Self::ResourceTypesDelete), + "schedule.create" => Ok(Self::ScheduleCreate), + "schedule.setenabled" => Ok(Self::ScheduleSetenabled), + "schedule.edit" => Ok(Self::ScheduleEdit), + "schedule.delete" => Ok(Self::ScheduleDelete), + "scripts.create" => Ok(Self::ScriptsCreate), + "scripts.update" => Ok(Self::ScriptsUpdate), + "scripts.archive" => Ok(Self::ScriptsArchive), + "scripts.delete" => Ok(Self::ScriptsDelete), + "users.create" => Ok(Self::UsersCreate), + "users.delete" => Ok(Self::UsersDelete), + "users.update" => Ok(Self::UsersUpdate), + "users.login" => Ok(Self::UsersLogin), + "users.login_failure" => Ok(Self::UsersLoginFailure), + "users.logout" => Ok(Self::UsersLogout), + "users.accept_invite" => Ok(Self::UsersAcceptInvite), + "users.decline_invite" => Ok(Self::UsersDeclineInvite), + "users.token.create" => Ok(Self::UsersTokenCreate), + "users.token.delete" => Ok(Self::UsersTokenDelete), + "users.add_to_workspace" => Ok(Self::UsersAddToWorkspace), + "users.add_global" => Ok(Self::UsersAddGlobal), + "users.setpassword" => Ok(Self::UsersSetpassword), + "users.impersonate" => Ok(Self::UsersImpersonate), + "users.leave_workspace" => Ok(Self::UsersLeaveWorkspace), + "oauth.login" => Ok(Self::OauthLogin), + "oauth.login_failure" => Ok(Self::OauthLoginFailure), + "oauth.signup" => Ok(Self::OauthSignup), + "variables.create" => Ok(Self::VariablesCreate), + "variables.delete" => Ok(Self::VariablesDelete), + "variables.update" => Ok(Self::VariablesUpdate), + "flows.create" => Ok(Self::FlowsCreate), + "flows.update" => Ok(Self::FlowsUpdate), + "flows.delete" => Ok(Self::FlowsDelete), + "flows.archive" => Ok(Self::FlowsArchive), + "apps.create" => Ok(Self::AppsCreate), + "apps.update" => Ok(Self::AppsUpdate), + "apps.delete" => Ok(Self::AppsDelete), + "folder.create" => Ok(Self::FolderCreate), + "folder.update" => Ok(Self::FolderUpdate), + "folder.delete" => Ok(Self::FolderDelete), + "folder.add_owner" => Ok(Self::FolderAddOwner), + "folder.remove_owner" => Ok(Self::FolderRemoveOwner), + "group.create" => Ok(Self::GroupCreate), + "group.delete" => Ok(Self::GroupDelete), + "group.edit" => Ok(Self::GroupEdit), + "group.adduser" => Ok(Self::GroupAdduser), + "group.removeuser" => Ok(Self::GroupRemoveuser), + "igroup.create" => Ok(Self::IgroupCreate), + "igroup.delete" => Ok(Self::IgroupDelete), + "igroup.adduser" => Ok(Self::IgroupAdduser), + "igroup.removeuser" => Ok(Self::IgroupRemoveuser), + "variables.decrypt_secret" => Ok(Self::VariablesDecryptSecret), + "workspaces.edit_command_script" => Ok(Self::WorkspacesEditCommandScript), + "workspaces.edit_deploy_to" => Ok(Self::WorkspacesEditDeployTo), + "workspaces.edit_auto_invite_domain" => { + Ok(Self::WorkspacesEditAutoInviteDomain) + } + "workspaces.edit_webhook" => Ok(Self::WorkspacesEditWebhook), + "workspaces.edit_copilot_config" => Ok(Self::WorkspacesEditCopilotConfig), + "workspaces.edit_error_handler" => Ok(Self::WorkspacesEditErrorHandler), + "workspaces.create" => Ok(Self::WorkspacesCreate), + "workspaces.update" => Ok(Self::WorkspacesUpdate), + "workspaces.archive" => Ok(Self::WorkspacesArchive), + "workspaces.unarchive" => Ok(Self::WorkspacesUnarchive), + "workspaces.delete" => Ok(Self::WorkspacesDelete), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AuditLogOperation { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AuditLogOperation { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AuditLogOperation { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AutoscalingEvent { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub applied_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub desired_workers: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub event_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker_group: Option, + } + impl From<&AutoscalingEvent> for AutoscalingEvent { + fn from(value: &AutoscalingEvent) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchAll { + pub branches: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(rename = "type")] + pub type_: BranchAllType, + } + impl From<&BranchAll> for BranchAll { + fn from(value: &BranchAll) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchAllBranchesItem { + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&BranchAllBranchesItem> for BranchAllBranchesItem { + fn from(value: &BranchAllBranchesItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum BranchAllType { + #[serde(rename = "branchall")] + Branchall, + } + impl From<&BranchAllType> for BranchAllType { + fn from(value: &BranchAllType) -> Self { + value.clone() + } + } + impl ToString for BranchAllType { + fn to_string(&self) -> String { + match *self { + Self::Branchall => "branchall".to_string(), + } + } + } + impl std::str::FromStr for BranchAllType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branchall" => Ok(Self::Branchall), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for BranchAllType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for BranchAllType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for BranchAllType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchOne { + pub branches: Vec, + pub default: Vec, + #[serde(rename = "type")] + pub type_: BranchOneType, + } + impl From<&BranchOne> for BranchOne { + fn from(value: &BranchOne) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct BranchOneBranchesItem { + pub expr: String, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&BranchOneBranchesItem> for BranchOneBranchesItem { + fn from(value: &BranchOneBranchesItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum BranchOneType { + #[serde(rename = "branchone")] + Branchone, + } + impl From<&BranchOneType> for BranchOneType { + fn from(value: &BranchOneType) -> Self { + value.clone() + } + } + impl ToString for BranchOneType { + fn to_string(&self) -> String { + match *self { + Self::Branchone => "branchone".to_string(), + } + } + } + impl std::str::FromStr for BranchOneType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branchone" => Ok(Self::Branchone), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for BranchOneType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for BranchOneType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for BranchOneType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Capture { + pub created_at: chrono::DateTime, + pub id: i64, + pub payload: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trigger_extra: Option, + pub trigger_kind: CaptureTriggerKind, + } + impl From<&Capture> for Capture { + fn from(value: &Capture) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CaptureConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trigger_config: Option, + pub trigger_kind: CaptureTriggerKind, + } + impl From<&CaptureConfig> for CaptureConfig { + fn from(value: &CaptureConfig) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum CaptureTriggerKind { + #[serde(rename = "webhook")] + Webhook, + #[serde(rename = "http")] + Http, + #[serde(rename = "websocket")] + Websocket, + #[serde(rename = "kafka")] + Kafka, + #[serde(rename = "email")] + Email, + #[serde(rename = "nats")] + Nats, + #[serde(rename = "postgres")] + Postgres, + #[serde(rename = "sqs")] + Sqs, + #[serde(rename = "mqtt")] + Mqtt, + } + impl From<&CaptureTriggerKind> for CaptureTriggerKind { + fn from(value: &CaptureTriggerKind) -> Self { + value.clone() + } + } + impl ToString for CaptureTriggerKind { + fn to_string(&self) -> String { + match *self { + Self::Webhook => "webhook".to_string(), + Self::Http => "http".to_string(), + Self::Websocket => "websocket".to_string(), + Self::Kafka => "kafka".to_string(), + Self::Email => "email".to_string(), + Self::Nats => "nats".to_string(), + Self::Postgres => "postgres".to_string(), + Self::Sqs => "sqs".to_string(), + Self::Mqtt => "mqtt".to_string(), + } + } + } + impl std::str::FromStr for CaptureTriggerKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "webhook" => Ok(Self::Webhook), + "http" => Ok(Self::Http), + "websocket" => Ok(Self::Websocket), + "kafka" => Ok(Self::Kafka), + "email" => Ok(Self::Email), + "nats" => Ok(Self::Nats), + "postgres" => Ok(Self::Postgres), + "sqs" => Ok(Self::Sqs), + "mqtt" => Ok(Self::Mqtt), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for CaptureTriggerKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ChannelInfo { + ///The unique identifier of the channel + pub channel_id: String, + ///The display name of the channel + pub channel_name: String, + ///The service URL for the channel + pub service_url: String, + ///The Microsoft Teams tenant identifier + pub tenant_id: String, + } + impl From<&ChannelInfo> for ChannelInfo { + fn from(value: &ChannelInfo) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CompletedJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aggregate_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + pub canceled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_reason: Option, + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deleted: Option, + pub duration_ms: i64, + pub email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_status: Option, + pub id: uuid::Uuid, + pub is_flow_step: bool, + pub is_skipped: bool, + pub job_kind: CompletedJobJobKind, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub logs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mem_peak: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_job: Option, + /**The user (u/userfoo) or group (g/groupfoo) whom +the execution of this script will be permissioned_as and by extension its DT_TOKEN. +*/ + pub permissioned_as: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessed: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_flow: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub result: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_wait_time_ms: Option, + pub started_at: chrono::DateTime, + pub success: bool, + pub tag: String, + pub visible_to_owner: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&CompletedJob> for CompletedJob { + fn from(value: &CompletedJob) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum CompletedJobJobKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "preview")] + Preview, + #[serde(rename = "dependencies")] + Dependencies, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "flowdependencies")] + Flowdependencies, + #[serde(rename = "appdependencies")] + Appdependencies, + #[serde(rename = "flowpreview")] + Flowpreview, + #[serde(rename = "script_hub")] + ScriptHub, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "deploymentcallback")] + Deploymentcallback, + #[serde(rename = "singlescriptflow")] + Singlescriptflow, + #[serde(rename = "flowscript")] + Flowscript, + #[serde(rename = "flownode")] + Flownode, + #[serde(rename = "appscript")] + Appscript, + } + impl From<&CompletedJobJobKind> for CompletedJobJobKind { + fn from(value: &CompletedJobJobKind) -> Self { + value.clone() + } + } + impl ToString for CompletedJobJobKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Preview => "preview".to_string(), + Self::Dependencies => "dependencies".to_string(), + Self::Flow => "flow".to_string(), + Self::Flowdependencies => "flowdependencies".to_string(), + Self::Appdependencies => "appdependencies".to_string(), + Self::Flowpreview => "flowpreview".to_string(), + Self::ScriptHub => "script_hub".to_string(), + Self::Identity => "identity".to_string(), + Self::Deploymentcallback => "deploymentcallback".to_string(), + Self::Singlescriptflow => "singlescriptflow".to_string(), + Self::Flowscript => "flowscript".to_string(), + Self::Flownode => "flownode".to_string(), + Self::Appscript => "appscript".to_string(), + } + } + } + impl std::str::FromStr for CompletedJobJobKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "preview" => Ok(Self::Preview), + "dependencies" => Ok(Self::Dependencies), + "flow" => Ok(Self::Flow), + "flowdependencies" => Ok(Self::Flowdependencies), + "appdependencies" => Ok(Self::Appdependencies), + "flowpreview" => Ok(Self::Flowpreview), + "script_hub" => Ok(Self::ScriptHub), + "identity" => Ok(Self::Identity), + "deploymentcallback" => Ok(Self::Deploymentcallback), + "singlescriptflow" => Ok(Self::Singlescriptflow), + "flowscript" => Ok(Self::Flowscript), + "flownode" => Ok(Self::Flownode), + "appscript" => Ok(Self::Appscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for CompletedJobJobKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConcurrencyGroup { + pub concurrency_key: String, + pub total_running: f64, + } + impl From<&ConcurrencyGroup> for ConcurrencyGroup { + fn from(value: &ConcurrencyGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Config { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub config: std::collections::HashMap, + pub name: String, + } + impl From<&Config> for Config { + fn from(value: &Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ContextualVariable { + pub description: String, + pub is_custom: bool, + pub name: String, + pub value: String, + } + impl From<&ContextualVariable> for ContextualVariable { + fn from(value: &ContextualVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateFlowBody { + #[serde(flatten)] + pub open_flow_w_path: OpenFlowWPath, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + } + impl From<&CreateFlowBody> for CreateFlowBody { + fn from(value: &CreateFlowBody) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateInput { + pub args: std::collections::HashMap, + pub name: String, + } + impl From<&CreateInput> for CreateInput { + fn from(value: &CreateInput) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + pub path: String, + pub resource_type: String, + pub value: serde_json::Value, + } + impl From<&CreateResource> for CreateResource { + fn from(value: &CreateResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_oauth: Option, + pub is_secret: bool, + pub path: String, + pub value: String, + } + impl From<&CreateVariable> for CreateVariable { + fn from(value: &CreateVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateWorkspace { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + pub id: String, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + } + impl From<&CreateWorkspace> for CreateWorkspace { + fn from(value: &CreateWorkspace) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CriticalAlert { + ///Acknowledgment status of the alert, can be true, false, or null if not set + #[serde(default, skip_serializing_if = "Option::is_none")] + pub acknowledged: Option, + ///Type of alert (e.g., critical_error) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub alert_type: Option, + ///Time when the alert was created + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option>, + ///Unique identifier for the alert + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + ///The message content of the alert + #[serde(default, skip_serializing_if = "Option::is_none")] + pub message: Option, + ///Workspace id if the alert is in the scope of a workspace + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&CriticalAlert> for CriticalAlert { + fn from(value: &CriticalAlert) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditHttpTrigger { + pub http_method: EditHttpTriggerHttpMethod, + pub is_async: bool, + pub is_flow: bool, + pub is_static_website: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_string: Option, + pub requires_auth: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub route_path: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wrap_body: Option, + } + impl From<&EditHttpTrigger> for EditHttpTrigger { + fn from(value: &EditHttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum EditHttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&EditHttpTriggerHttpMethod> for EditHttpTriggerHttpMethod { + fn from(value: &EditHttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for EditHttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for EditHttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for EditHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditHttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&EditHttpTriggerStaticAssetConfig> for EditHttpTriggerStaticAssetConfig { + fn from(value: &EditHttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditKafkaTrigger { + pub group_id: String, + pub is_flow: bool, + pub kafka_resource_path: String, + pub path: String, + pub script_path: String, + pub topics: Vec, + } + impl From<&EditKafkaTrigger> for EditKafkaTrigger { + fn from(value: &EditKafkaTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditMqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + pub enabled: bool, + pub is_flow: bool, + pub mqtt_resource_path: String, + pub path: String, + pub script_path: String, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&EditMqttTrigger> for EditMqttTrigger { + fn from(value: &EditMqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditNatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + pub is_flow: bool, + pub nats_resource_path: String, + pub path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&EditNatsTrigger> for EditNatsTrigger { + fn from(value: &EditNatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditPostgresTrigger { + pub enabled: bool, + pub is_flow: bool, + pub path: String, + pub postgres_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication: Option, + pub publication_name: String, + pub replication_slot_name: String, + pub script_path: String, + } + impl From<&EditPostgresTrigger> for EditPostgresTrigger { + fn from(value: &EditPostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&EditResource> for EditResource { + fn from(value: &EditResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditResourceType { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + } + impl From<&EditResourceType> for EditResourceType { + fn from(value: &EditResourceType) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditSchedule { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&EditSchedule> for EditSchedule { + fn from(value: &EditSchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditSqsTrigger { + pub aws_resource_path: String, + pub enabled: bool, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub path: String, + pub queue_url: String, + pub script_path: String, + } + impl From<&EditSqsTrigger> for EditSqsTrigger { + fn from(value: &EditSqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_secret: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&EditVariable> for EditVariable { + fn from(value: &EditVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWebsocketTrigger { + pub can_return_message: bool, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&EditWebsocketTrigger> for EditWebsocketTrigger { + fn from(value: &EditWebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&EditWebsocketTriggerFiltersItem> for EditWebsocketTriggerFiltersItem { + fn from(value: &EditWebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditWorkspaceUser { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_admin: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator: Option, + } + impl From<&EditWorkspaceUser> for EditWorkspaceUser { + fn from(value: &EditWorkspaceUser) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExportedInstanceGroup { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub emails: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scim_display_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&ExportedInstanceGroup> for ExportedInstanceGroup { + fn from(value: &ExportedInstanceGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExportedUser { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub company: Option, + pub email: String, + pub first_time_user: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub password_hash: Option, + pub super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + pub verified: bool, + } + impl From<&ExportedUser> for ExportedUser { + fn from(value: &ExportedUser) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExtendedJobs { + pub jobs: Vec, + pub obscured_jobs: Vec, + ///Obscured jobs omitted for security because of too specific filtering + #[serde(default, skip_serializing_if = "Option::is_none")] + pub omitted_obscured_jobs: Option, + } + impl From<&ExtendedJobs> for ExtendedJobs { + fn from(value: &ExtendedJobs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ExtraPerms(pub std::collections::HashMap); + impl std::ops::Deref for ExtraPerms { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From for std::collections::HashMap { + fn from(value: ExtraPerms) -> Self { + value.0 + } + } + impl From<&ExtraPerms> for ExtraPerms { + fn from(value: &ExtraPerms) -> Self { + value.clone() + } + } + impl From> for ExtraPerms { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Flow { + #[serde(flatten)] + pub open_flow: OpenFlow, + #[serde(flatten)] + pub flow_metadata: FlowMetadata, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + } + impl From<&Flow> for Flow { + fn from(value: &Flow) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowMetadata { + pub archived: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub extra_perms: ExtraPerms, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&FlowMetadata> for FlowMetadata { + fn from(value: &FlowMetadata) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub continue_on_error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sleep: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop_after_all_iters_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop_after_if: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub suspend: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + pub value: FlowModuleValue, + } + impl From<&FlowModule> for FlowModule { + fn from(value: &FlowModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleMock { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub return_value: Option, + } + impl From<&FlowModuleMock> for FlowModuleMock { + fn from(value: &FlowModuleMock) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSkipIf { + pub expr: String, + } + impl From<&FlowModuleSkipIf> for FlowModuleSkipIf { + fn from(value: &FlowModuleSkipIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleStopAfterAllItersIf { + pub expr: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if_stopped: Option, + } + impl From<&FlowModuleStopAfterAllItersIf> for FlowModuleStopAfterAllItersIf { + fn from(value: &FlowModuleStopAfterAllItersIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleStopAfterIf { + pub expr: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if_stopped: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_message: Option + } + impl From<&FlowModuleStopAfterIf> for FlowModuleStopAfterIf { + fn from(value: &FlowModuleStopAfterIf) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSuspend { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub continue_on_disapprove_timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hide_cancel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub required_events: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resume_form: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_approval_disabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_auth_required: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_groups_required: Option, + } + impl From<&FlowModuleSuspend> for FlowModuleSuspend { + fn from(value: &FlowModuleSuspend) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowModuleSuspendResumeForm { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + } + impl From<&FlowModuleSuspendResumeForm> for FlowModuleSuspendResumeForm { + fn from(value: &FlowModuleSuspendResumeForm) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum FlowModuleValue { + RawScript(RawScript), + PathScript(PathScript), + PathFlow(PathFlow), + ForloopFlow(ForloopFlow), + WhileloopFlow(WhileloopFlow), + BranchOne(BranchOne), + BranchAll(BranchAll), + Identity(Identity), + } + impl From<&FlowModuleValue> for FlowModuleValue { + fn from(value: &FlowModuleValue) -> Self { + value.clone() + } + } + impl From for FlowModuleValue { + fn from(value: RawScript) -> Self { + Self::RawScript(value) + } + } + impl From for FlowModuleValue { + fn from(value: PathScript) -> Self { + Self::PathScript(value) + } + } + impl From for FlowModuleValue { + fn from(value: PathFlow) -> Self { + Self::PathFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: ForloopFlow) -> Self { + Self::ForloopFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: WhileloopFlow) -> Self { + Self::WhileloopFlow(value) + } + } + impl From for FlowModuleValue { + fn from(value: BranchOne) -> Self { + Self::BranchOne(value) + } + } + impl From for FlowModuleValue { + fn from(value: BranchAll) -> Self { + Self::BranchAll(value) + } + } + impl From for FlowModuleValue { + fn from(value: Identity) -> Self { + Self::Identity(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowPreview { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restarted_from: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub value: FlowValue, + } + impl From<&FlowPreview> for FlowPreview { + fn from(value: &FlowPreview) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatus { + pub failure_module: FlowStatusFailureModule, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessor_module: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub step: i64, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub user_states: std::collections::HashMap, + } + impl From<&FlowStatus> for FlowStatus { + fn from(value: &FlowStatus) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusFailureModule { + #[serde(flatten)] + pub flow_status_module: FlowStatusModule, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_module: Option, + } + impl From<&FlowStatusFailureModule> for FlowStatusFailureModule { + fn from(value: &FlowStatusFailureModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModule { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub approvers: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch_chosen: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branchall: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub count: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub failed_retries: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub flow_jobs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub flow_jobs_success: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub iterator: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub job: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub progress: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skipped: Option, + #[serde(rename = "type")] + pub type_: FlowStatusModuleType, + } + impl From<&FlowStatusModule> for FlowStatusModule { + fn from(value: &FlowStatusModule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleApproversItem { + pub approver: String, + pub resume_id: i64, + } + impl From<&FlowStatusModuleApproversItem> for FlowStatusModuleApproversItem { + fn from(value: &FlowStatusModuleApproversItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleBranchChosen { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde(rename = "type")] + pub type_: FlowStatusModuleBranchChosenType, + } + impl From<&FlowStatusModuleBranchChosen> for FlowStatusModuleBranchChosen { + fn from(value: &FlowStatusModuleBranchChosen) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum FlowStatusModuleBranchChosenType { + #[serde(rename = "branch")] + Branch, + #[serde(rename = "default")] + Default, + } + impl From<&FlowStatusModuleBranchChosenType> for FlowStatusModuleBranchChosenType { + fn from(value: &FlowStatusModuleBranchChosenType) -> Self { + value.clone() + } + } + impl ToString for FlowStatusModuleBranchChosenType { + fn to_string(&self) -> String { + match *self { + Self::Branch => "branch".to_string(), + Self::Default => "default".to_string(), + } + } + } + impl std::str::FromStr for FlowStatusModuleBranchChosenType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "branch" => Ok(Self::Branch), + "default" => Ok(Self::Default), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for FlowStatusModuleBranchChosenType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleBranchall { + pub branch: i64, + pub len: i64, + } + impl From<&FlowStatusModuleBranchall> for FlowStatusModuleBranchall { + fn from(value: &FlowStatusModuleBranchall) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusModuleIterator { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub index: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub itered: Vec, + } + impl From<&FlowStatusModuleIterator> for FlowStatusModuleIterator { + fn from(value: &FlowStatusModuleIterator) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum FlowStatusModuleType { + WaitingForPriorSteps, + WaitingForEvents, + WaitingForExecutor, + InProgress, + Success, + Failure, + } + impl From<&FlowStatusModuleType> for FlowStatusModuleType { + fn from(value: &FlowStatusModuleType) -> Self { + value.clone() + } + } + impl ToString for FlowStatusModuleType { + fn to_string(&self) -> String { + match *self { + Self::WaitingForPriorSteps => "WaitingForPriorSteps".to_string(), + Self::WaitingForEvents => "WaitingForEvents".to_string(), + Self::WaitingForExecutor => "WaitingForExecutor".to_string(), + Self::InProgress => "InProgress".to_string(), + Self::Success => "Success".to_string(), + Self::Failure => "Failure".to_string(), + } + } + } + impl std::str::FromStr for FlowStatusModuleType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "WaitingForPriorSteps" => Ok(Self::WaitingForPriorSteps), + "WaitingForEvents" => Ok(Self::WaitingForEvents), + "WaitingForExecutor" => Ok(Self::WaitingForExecutor), + "InProgress" => Ok(Self::InProgress), + "Success" => Ok(Self::Success), + "Failure" => Ok(Self::Failure), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for FlowStatusModuleType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowStatusRetry { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fail_count: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub failed_jobs: Vec, + } + impl From<&FlowStatusRetry> for FlowStatusRetry { + fn from(value: &FlowStatusRetry) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowValue { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub early_return: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub failure_module: Option, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessor_module: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub same_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_expr: Option, + } + impl From<&FlowValue> for FlowValue { + fn from(value: &FlowValue) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FlowVersion { + pub created_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub id: i64, + } + impl From<&FlowVersion> for FlowVersion { + fn from(value: &FlowVersion) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Folder { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + pub extra_perms: std::collections::HashMap, + pub name: String, + pub owners: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&Folder> for Folder { + fn from(value: &Folder) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ForloopFlow { + pub iterator: InputTransform, + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + pub skip_failures: bool, + #[serde(rename = "type")] + pub type_: ForloopFlowType, + } + impl From<&ForloopFlow> for ForloopFlow { + fn from(value: &ForloopFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ForloopFlowType { + #[serde(rename = "forloopflow")] + Forloopflow, + } + impl From<&ForloopFlowType> for ForloopFlowType { + fn from(value: &ForloopFlowType) -> Self { + value.clone() + } + } + impl ToString for ForloopFlowType { + fn to_string(&self) -> String { + match *self { + Self::Forloopflow => "forloopflow".to_string(), + } + } + } + impl std::str::FromStr for ForloopFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "forloopflow" => Ok(Self::Forloopflow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ForloopFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ForloopFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ForloopFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GitRepositorySettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exclude_types_override: Vec, + pub git_repo_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group_by_folder: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub use_individual_branch: Option, + } + impl From<&GitRepositorySettings> for GitRepositorySettings { + fn from(value: &GitRepositorySettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GitRepositorySettingsExcludeTypesOverrideItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "resourcetype")] + Resourcetype, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "user")] + User, + #[serde(rename = "group")] + Group, + } + impl From<&GitRepositorySettingsExcludeTypesOverrideItem> + for GitRepositorySettingsExcludeTypesOverrideItem { + fn from(value: &GitRepositorySettingsExcludeTypesOverrideItem) -> Self { + value.clone() + } + } + impl ToString for GitRepositorySettingsExcludeTypesOverrideItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Folder => "folder".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Resourcetype => "resourcetype".to_string(), + Self::Schedule => "schedule".to_string(), + Self::User => "user".to_string(), + Self::Group => "group".to_string(), + } + } + } + impl std::str::FromStr for GitRepositorySettingsExcludeTypesOverrideItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "folder" => Ok(Self::Folder), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "resourcetype" => Ok(Self::Resourcetype), + "schedule" => Ok(Self::Schedule), + "user" => Ok(Self::User), + "group" => Ok(Self::Group), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> + for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom + for GitRepositorySettingsExcludeTypesOverrideItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalSetting { + pub name: String, + pub value: std::collections::HashMap, + } + impl From<&GlobalSetting> for GlobalSetting { + fn from(value: &GlobalSetting) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GlobalUserInfo { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub company: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub devops: Option, + pub email: String, + pub login_type: GlobalUserInfoLoginType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator_only: Option, + pub super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + pub verified: bool, + } + impl From<&GlobalUserInfo> for GlobalUserInfo { + fn from(value: &GlobalUserInfo) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum GlobalUserInfoLoginType { + #[serde(rename = "password")] + Password, + #[serde(rename = "github")] + Github, + } + impl From<&GlobalUserInfoLoginType> for GlobalUserInfoLoginType { + fn from(value: &GlobalUserInfoLoginType) -> Self { + value.clone() + } + } + impl ToString for GlobalUserInfoLoginType { + fn to_string(&self) -> String { + match *self { + Self::Password => "password".to_string(), + Self::Github => "github".to_string(), + } + } + } + impl std::str::FromStr for GlobalUserInfoLoginType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "password" => Ok(Self::Password), + "github" => Ok(Self::Github), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for GlobalUserInfoLoginType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Group { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub members: Vec, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&Group> for Group { + fn from(value: &Group) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HttpTrigger { + pub http_method: HttpTriggerHttpMethod, + pub is_async: bool, + pub is_static_website: bool, + pub raw_string: bool, + pub requires_auth: bool, + pub route_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + pub workspaced_route: bool, + pub wrap_body: bool, + } + impl From<&HttpTrigger> for HttpTrigger { + fn from(value: &HttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum HttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&HttpTriggerHttpMethod> for HttpTriggerHttpMethod { + fn from(value: &HttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for HttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for HttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for HttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&HttpTriggerStaticAssetConfig> for HttpTriggerStaticAssetConfig { + fn from(value: &HttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HubScriptKind(pub serde_json::Value); + impl std::ops::Deref for HubScriptKind { + type Target = serde_json::Value; + fn deref(&self) -> &serde_json::Value { + &self.0 + } + } + impl From for serde_json::Value { + fn from(value: HubScriptKind) -> Self { + value.0 + } + } + impl From<&HubScriptKind> for HubScriptKind { + fn from(value: &HubScriptKind) -> Self { + value.clone() + } + } + impl From for HubScriptKind { + fn from(value: serde_json::Value) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Identity { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow: Option, + #[serde(rename = "type")] + pub type_: IdentityType, + } + impl From<&Identity> for Identity { + fn from(value: &Identity) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum IdentityType { + #[serde(rename = "identity")] + Identity, + } + impl From<&IdentityType> for IdentityType { + fn from(value: &IdentityType) -> Self { + value.clone() + } + } + impl ToString for IdentityType { + fn to_string(&self) -> String { + match *self { + Self::Identity => "identity".to_string(), + } + } + } + impl std::str::FromStr for IdentityType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "identity" => Ok(Self::Identity), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for IdentityType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for IdentityType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for IdentityType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Input { + pub created_at: chrono::DateTime, + pub created_by: String, + pub id: String, + pub is_public: bool, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub success: Option, + } + impl From<&Input> for Input { + fn from(value: &Input) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum InputTransform { + StaticTransform(StaticTransform), + JavascriptTransform(JavascriptTransform), + } + impl From<&InputTransform> for InputTransform { + fn from(value: &InputTransform) -> Self { + value.clone() + } + } + impl From for InputTransform { + fn from(value: StaticTransform) -> Self { + Self::StaticTransform(value) + } + } + impl From for InputTransform { + fn from(value: JavascriptTransform) -> Self { + Self::JavascriptTransform(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceGroup { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub emails: Vec, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + } + impl From<&InstanceGroup> for InstanceGroup { + fn from(value: &InstanceGroup) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JavascriptTransform { + pub expr: String, + #[serde(rename = "type")] + pub type_: JavascriptTransformType, + } + impl From<&JavascriptTransform> for JavascriptTransform { + fn from(value: &JavascriptTransform) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JavascriptTransformType { + #[serde(rename = "javascript")] + Javascript, + } + impl From<&JavascriptTransformType> for JavascriptTransformType { + fn from(value: &JavascriptTransformType) -> Self { + value.clone() + } + } + impl ToString for JavascriptTransformType { + fn to_string(&self) -> String { + match *self { + Self::Javascript => "javascript".to_string(), + } + } + } + impl std::str::FromStr for JavascriptTransformType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "javascript" => Ok(Self::Javascript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JavascriptTransformType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum Job { + Variant0(JobVariant0), + Variant1(JobVariant1), + } + impl From<&Job> for Job { + fn from(value: &Job) -> Self { + value.clone() + } + } + impl From for Job { + fn from(value: JobVariant0) -> Self { + Self::Variant0(value) + } + } + impl From for Job { + fn from(value: JobVariant1) -> Self { + Self::Variant1(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobSearchHit { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dancer: Option, + } + impl From<&JobSearchHit> for JobSearchHit { + fn from(value: &JobSearchHit) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobVariant0 { + #[serde(flatten)] + pub completed_job: CompletedJob, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&JobVariant0> for JobVariant0 { + fn from(value: &JobVariant0) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JobVariant0Type { + CompletedJob, + } + impl From<&JobVariant0Type> for JobVariant0Type { + fn from(value: &JobVariant0Type) -> Self { + value.clone() + } + } + impl ToString for JobVariant0Type { + fn to_string(&self) -> String { + match *self { + Self::CompletedJob => "CompletedJob".to_string(), + } + } + } + impl std::str::FromStr for JobVariant0Type { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "CompletedJob" => Ok(Self::CompletedJob), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JobVariant0Type { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JobVariant0Type { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JobVariant0Type { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct JobVariant1 { + #[serde(flatten)] + pub queued_job: QueuedJob, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&JobVariant1> for JobVariant1 { + fn from(value: &JobVariant1) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum JobVariant1Type { + QueuedJob, + } + impl From<&JobVariant1Type> for JobVariant1Type { + fn from(value: &JobVariant1Type) -> Self { + value.clone() + } + } + impl ToString for JobVariant1Type { + fn to_string(&self) -> String { + match *self { + Self::QueuedJob => "QueuedJob".to_string(), + } + } + } + impl std::str::FromStr for JobVariant1Type { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "QueuedJob" => Ok(Self::QueuedJob), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for JobVariant1Type { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for JobVariant1Type { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for JobVariant1Type { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct KafkaTrigger { + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub group_id: String, + pub kafka_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub topics: Vec, + } + impl From<&KafkaTrigger> for KafkaTrigger { + fn from(value: &KafkaTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum Language { + Typescript, + } + impl From<&Language> for Language { + fn from(value: &Language) -> Self { + value.clone() + } + } + impl ToString for Language { + fn to_string(&self) -> String { + match *self { + Self::Typescript => "Typescript".to_string(), + } + } + } + impl std::str::FromStr for Language { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Typescript" => Ok(Self::Typescript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for Language { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for Language { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for Language { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LargeFileStorage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub azure_blob_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub public_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub secondary_storage: std::collections::HashMap< + String, + LargeFileStorageSecondaryStorageValue, + >, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&LargeFileStorage> for LargeFileStorage { + fn from(value: &LargeFileStorage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LargeFileStorageSecondaryStorageValue { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub azure_blob_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub public_resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub type_: Option, + } + impl From<&LargeFileStorageSecondaryStorageValue> + for LargeFileStorageSecondaryStorageValue { + fn from(value: &LargeFileStorageSecondaryStorageValue) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum LargeFileStorageSecondaryStorageValueType { + S3Storage, + AzureBlobStorage, + AzureWorkloadIdentity, + S3AwsOidc, + } + impl From<&LargeFileStorageSecondaryStorageValueType> + for LargeFileStorageSecondaryStorageValueType { + fn from(value: &LargeFileStorageSecondaryStorageValueType) -> Self { + value.clone() + } + } + impl ToString for LargeFileStorageSecondaryStorageValueType { + fn to_string(&self) -> String { + match *self { + Self::S3Storage => "S3Storage".to_string(), + Self::AzureBlobStorage => "AzureBlobStorage".to_string(), + Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), + Self::S3AwsOidc => "S3AwsOidc".to_string(), + } + } + } + impl std::str::FromStr for LargeFileStorageSecondaryStorageValueType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "S3Storage" => Ok(Self::S3Storage), + "AzureBlobStorage" => Ok(Self::AzureBlobStorage), + "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), + "S3AwsOidc" => Ok(Self::S3AwsOidc), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for LargeFileStorageSecondaryStorageValueType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum LargeFileStorageType { + S3Storage, + AzureBlobStorage, + AzureWorkloadIdentity, + S3AwsOidc, + } + impl From<&LargeFileStorageType> for LargeFileStorageType { + fn from(value: &LargeFileStorageType) -> Self { + value.clone() + } + } + impl ToString for LargeFileStorageType { + fn to_string(&self) -> String { + match *self { + Self::S3Storage => "S3Storage".to_string(), + Self::AzureBlobStorage => "AzureBlobStorage".to_string(), + Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), + Self::S3AwsOidc => "S3AwsOidc".to_string(), + } + } + } + impl std::str::FromStr for LargeFileStorageType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "S3Storage" => Ok(Self::S3Storage), + "AzureBlobStorage" => Ok(Self::AzureBlobStorage), + "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), + "S3AwsOidc" => Ok(Self::S3AwsOidc), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for LargeFileStorageType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableApp { + pub edited_at: chrono::DateTime, + pub execution_mode: ListableAppExecutionMode, + pub extra_perms: std::collections::HashMap, + pub id: i64, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + pub summary: String, + pub version: i64, + pub workspace_id: String, + } + impl From<&ListableApp> for ListableApp { + fn from(value: &ListableApp) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ListableAppExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&ListableAppExecutionMode> for ListableAppExecutionMode { + fn from(value: &ListableAppExecutionMode) -> Self { + value.clone() + } + } + impl ToString for ListableAppExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for ListableAppExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ListableAppExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableRawApp { + pub edited_at: chrono::DateTime, + pub extra_perms: std::collections::HashMap, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub starred: Option, + pub summary: String, + pub version: f64, + pub workspace_id: String, + } + impl From<&ListableRawApp> for ListableRawApp { + fn from(value: &ListableRawApp) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableResource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_expired: Option, + pub is_linked: bool, + pub is_oauth: bool, + pub is_refreshed: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_error: Option, + pub resource_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&ListableResource> for ListableResource { + fn from(value: &ListableResource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ListableVariable { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub account: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option>, + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_expired: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_linked: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_oauth: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_refreshed: Option, + pub is_secret: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + pub workspace_id: String, + } + impl From<&ListableVariable> for ListableVariable { + fn from(value: &ListableVariable) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct LogSearchHit { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dancer: Option, + } + impl From<&LogSearchHit> for LogSearchHit { + fn from(value: &LogSearchHit) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Login { + pub email: String, + pub password: String, + } + impl From<&Login> for Login { + fn from(value: &Login) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignature { + pub args: Vec, + pub error: String, + pub has_preprocessor: Option, + pub no_main_func: Option, + pub star_args: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub star_kwargs: Option, + #[serde(rename = "type")] + pub type_: MainArgSignatureType, + } + impl From<&MainArgSignature> for MainArgSignature { + fn from(value: &MainArgSignature) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignatureArgsItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_default: Option, + pub name: String, + pub typ: MainArgSignatureArgsItemTyp, + } + impl From<&MainArgSignatureArgsItem> for MainArgSignatureArgsItem { + fn from(value: &MainArgSignatureArgsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTyp { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "resource")] + Resource(Option), + #[serde(rename = "str")] + Str(Option>), + #[serde(rename = "object")] + Object(Vec), + #[serde(rename = "list")] + List(MainArgSignatureArgsItemTypList), + } + impl From<&MainArgSignatureArgsItemTyp> for MainArgSignatureArgsItemTyp { + fn from(value: &MainArgSignatureArgsItemTyp) -> Self { + value.clone() + } + } + impl From> for MainArgSignatureArgsItemTyp { + fn from(value: Option) -> Self { + Self::Resource(value) + } + } + impl From>> for MainArgSignatureArgsItemTyp { + fn from(value: Option>) -> Self { + Self::Str(value) + } + } + impl From> + for MainArgSignatureArgsItemTyp { + fn from(value: Vec) -> Self { + Self::Object(value) + } + } + impl From for MainArgSignatureArgsItemTyp { + fn from(value: MainArgSignatureArgsItemTypList) -> Self { + Self::List(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTypList { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "str")] + Str(serde_json::Value), + } + impl From<&MainArgSignatureArgsItemTypList> for MainArgSignatureArgsItemTypList { + fn from(value: &MainArgSignatureArgsItemTypList) -> Self { + value.clone() + } + } + impl From for MainArgSignatureArgsItemTypList { + fn from(value: serde_json::Value) -> Self { + Self::Str(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MainArgSignatureArgsItemTypObjectItem { + pub key: String, + pub typ: MainArgSignatureArgsItemTypObjectItemTyp, + } + impl From<&MainArgSignatureArgsItemTypObjectItem> + for MainArgSignatureArgsItemTypObjectItem { + fn from(value: &MainArgSignatureArgsItemTypObjectItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum MainArgSignatureArgsItemTypObjectItemTyp { + #[serde(rename = "float")] + Float, + #[serde(rename = "int")] + Int, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "email")] + Email, + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "dict")] + Dict, + #[serde(rename = "datetime")] + Datetime, + #[serde(rename = "sql")] + Sql, + #[serde(rename = "str")] + Str(serde_json::Value), + } + impl From<&MainArgSignatureArgsItemTypObjectItemTyp> + for MainArgSignatureArgsItemTypObjectItemTyp { + fn from(value: &MainArgSignatureArgsItemTypObjectItemTyp) -> Self { + value.clone() + } + } + impl From for MainArgSignatureArgsItemTypObjectItemTyp { + fn from(value: serde_json::Value) -> Self { + Self::Str(value) + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MainArgSignatureType { + Valid, + Invalid, + } + impl From<&MainArgSignatureType> for MainArgSignatureType { + fn from(value: &MainArgSignatureType) -> Self { + value.clone() + } + } + impl ToString for MainArgSignatureType { + fn to_string(&self) -> String { + match *self { + Self::Valid => "Valid".to_string(), + Self::Invalid => "Invalid".to_string(), + } + } + } + impl std::str::FromStr for MainArgSignatureType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Valid" => Ok(Self::Valid), + "Invalid" => Ok(Self::Invalid), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MainArgSignatureType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MetricDataPoint { + pub timestamp: chrono::DateTime, + pub value: f64, + } + impl From<&MetricDataPoint> for MetricDataPoint { + fn from(value: &MetricDataPoint) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MetricMetadata { + pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + impl From<&MetricMetadata> for MetricMetadata { + fn from(value: &MetricMetadata) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MqttClientVersion { + #[serde(rename = "v3")] + V3, + #[serde(rename = "v5")] + V5, + } + impl From<&MqttClientVersion> for MqttClientVersion { + fn from(value: &MqttClientVersion) -> Self { + value.clone() + } + } + impl ToString for MqttClientVersion { + fn to_string(&self) -> String { + match *self { + Self::V3 => "v3".to_string(), + Self::V5 => "v5".to_string(), + } + } + } + impl std::str::FromStr for MqttClientVersion { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "v3" => Ok(Self::V3), + "v5" => Ok(Self::V5), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MqttClientVersion { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MqttClientVersion { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MqttClientVersion { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum MqttQoS { + #[serde(rename = "qos0")] + Qos0, + #[serde(rename = "qos1")] + Qos1, + #[serde(rename = "qos2")] + Qos2, + } + impl From<&MqttQoS> for MqttQoS { + fn from(value: &MqttQoS) -> Self { + value.clone() + } + } + impl ToString for MqttQoS { + fn to_string(&self) -> String { + match *self { + Self::Qos0 => "qos0".to_string(), + Self::Qos1 => "qos1".to_string(), + Self::Qos2 => "qos2".to_string(), + } + } + } + impl std::str::FromStr for MqttQoS { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "qos0" => Ok(Self::Qos0), + "qos1" => Ok(Self::Qos1), + "qos2" => Ok(Self::Qos2), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for MqttQoS { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for MqttQoS { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for MqttQoS { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttSubscribeTopic { + pub qos: MqttQoS, + pub topic: String, + } + impl From<&MqttSubscribeTopic> for MqttSubscribeTopic { + fn from(value: &MqttSubscribeTopic) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub mqtt_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&MqttTrigger> for MqttTrigger { + fn from(value: &MqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttV3Config { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub clean_session: Option, + } + impl From<&MqttV3Config> for MqttV3Config { + fn from(value: &MqttV3Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MqttV5Config { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub clean_start: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub session_expiry_interval: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub topic_alias: Option, + } + impl From<&MqttV5Config> for MqttV5Config { + fn from(value: &MqttV5Config) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub nats_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&NatsTrigger> for NatsTrigger { + fn from(value: &NatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewHttpTrigger { + pub http_method: NewHttpTriggerHttpMethod, + pub is_async: bool, + pub is_flow: bool, + pub is_static_website: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_string: Option, + pub requires_auth: bool, + pub route_path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wrap_body: Option, + } + impl From<&NewHttpTrigger> for NewHttpTrigger { + fn from(value: &NewHttpTrigger) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewHttpTriggerHttpMethod { + #[serde(rename = "get")] + Get, + #[serde(rename = "post")] + Post, + #[serde(rename = "put")] + Put, + #[serde(rename = "delete")] + Delete, + #[serde(rename = "patch")] + Patch, + } + impl From<&NewHttpTriggerHttpMethod> for NewHttpTriggerHttpMethod { + fn from(value: &NewHttpTriggerHttpMethod) -> Self { + value.clone() + } + } + impl ToString for NewHttpTriggerHttpMethod { + fn to_string(&self) -> String { + match *self { + Self::Get => "get".to_string(), + Self::Post => "post".to_string(), + Self::Put => "put".to_string(), + Self::Delete => "delete".to_string(), + Self::Patch => "patch".to_string(), + } + } + } + impl std::str::FromStr for NewHttpTriggerHttpMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "get" => Ok(Self::Get), + "post" => Ok(Self::Post), + "put" => Ok(Self::Put), + "delete" => Ok(Self::Delete), + "patch" => Ok(Self::Patch), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewHttpTriggerHttpMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewHttpTriggerStaticAssetConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&NewHttpTriggerStaticAssetConfig> for NewHttpTriggerStaticAssetConfig { + fn from(value: &NewHttpTriggerStaticAssetConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewKafkaTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub group_id: String, + pub is_flow: bool, + pub kafka_resource_path: String, + pub path: String, + pub script_path: String, + pub topics: Vec, + } + impl From<&NewKafkaTrigger> for NewKafkaTrigger { + fn from(value: &NewKafkaTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewMqttTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + pub mqtt_resource_path: String, + pub path: String, + pub script_path: String, + pub subscribe_topics: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v3_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub v5_config: Option, + } + impl From<&NewMqttTrigger> for NewMqttTrigger { + fn from(value: &NewMqttTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewNatsTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + pub nats_resource_path: String, + pub path: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + pub subjects: Vec, + pub use_jetstream: bool, + } + impl From<&NewNatsTrigger> for NewNatsTrigger { + fn from(value: &NewNatsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewPostgresTrigger { + pub enabled: bool, + pub is_flow: bool, + pub path: String, + pub postgres_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub publication_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub replication_slot_name: Option, + pub script_path: String, + } + impl From<&NewPostgresTrigger> for NewPostgresTrigger { + fn from(value: &NewPostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewSchedule { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&NewSchedule> for NewSchedule { + fn from(value: &NewSchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub codebase: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_message: Option, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub envs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_preprocessor: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_template: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + pub language: ScriptLang, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_hash: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restart_unless_cancelled: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub summary: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&NewScript> for NewScript { + fn from(value: &NewScript) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewScriptKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "failure")] + Failure, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "command")] + Command, + #[serde(rename = "approval")] + Approval, + #[serde(rename = "preprocessor")] + Preprocessor, + } + impl From<&NewScriptKind> for NewScriptKind { + fn from(value: &NewScriptKind) -> Self { + value.clone() + } + } + impl ToString for NewScriptKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Failure => "failure".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Command => "command".to_string(), + Self::Approval => "approval".to_string(), + Self::Preprocessor => "preprocessor".to_string(), + } + } + } + impl std::str::FromStr for NewScriptKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "failure" => Ok(Self::Failure), + "trigger" => Ok(Self::Trigger), + "command" => Ok(Self::Command), + "approval" => Ok(Self::Approval), + "preprocessor" => Ok(Self::Preprocessor), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewScriptKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewScriptKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewScriptKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewScriptWithDraft { + #[serde(flatten)] + pub new_script: NewScript, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft: Option, + pub hash: String, + } + impl From<&NewScriptWithDraft> for NewScriptWithDraft { + fn from(value: &NewScriptWithDraft) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewSqsTrigger { + pub aws_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub path: String, + pub queue_url: String, + pub script_path: String, + } + impl From<&NewSqsTrigger> for NewSqsTrigger { + fn from(value: &NewSqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewToken { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scopes: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&NewToken> for NewToken { + fn from(value: &NewToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewTokenImpersonate { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + pub impersonate_email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&NewTokenImpersonate> for NewTokenImpersonate { + fn from(value: &NewTokenImpersonate) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewWebsocketTrigger { + pub can_return_message: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&NewWebsocketTrigger> for NewWebsocketTrigger { + fn from(value: &NewWebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewWebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&NewWebsocketTriggerFiltersItem> for NewWebsocketTriggerFiltersItem { + fn from(value: &NewWebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ObscuredJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub duration_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub typ: Option, + } + impl From<&ObscuredJob> for ObscuredJob { + fn from(value: &ObscuredJob) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenFlow { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub summary: String, + pub value: FlowValue, + } + impl From<&OpenFlow> for OpenFlow { + fn from(value: &OpenFlow) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenFlowWPath { + #[serde(flatten)] + pub open_flow: OpenFlow, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&OpenFlowWPath> for OpenFlowWPath { + fn from(value: &OpenFlowWPath) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OperatorSettings(pub Option); + impl std::ops::Deref for OperatorSettings { + type Target = Option; + fn deref(&self) -> &Option { + &self.0 + } + } + impl From for Option { + fn from(value: OperatorSettings) -> Self { + value.0 + } + } + impl From<&OperatorSettings> for OperatorSettings { + fn from(value: &OperatorSettings) -> Self { + value.clone() + } + } + impl From> for OperatorSettings { + fn from(value: Option) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OperatorSettingsInner { + ///Whether operators can view audit logs + pub audit_logs: bool, + ///Whether operators can view folders page + pub folders: bool, + ///Whether operators can view groups page + pub groups: bool, + ///Whether operators can view resources + pub resources: bool, + ///Whether operators can view runs + pub runs: bool, + ///Whether operators can view schedules + pub schedules: bool, + ///Whether operators can view triggers + pub triggers: bool, + ///Whether operators can view variables + pub variables: bool, + ///Whether operators can view workers page + pub workers: bool, + } + impl From<&OperatorSettingsInner> for OperatorSettingsInner { + fn from(value: &OperatorSettingsInner) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PathFlow { + pub input_transforms: std::collections::HashMap, + pub path: String, + #[serde(rename = "type")] + pub type_: PathFlowType, + } + impl From<&PathFlow> for PathFlow { + fn from(value: &PathFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PathFlowType { + #[serde(rename = "flow")] + Flow, + } + impl From<&PathFlowType> for PathFlowType { + fn from(value: &PathFlowType) -> Self { + value.clone() + } + } + impl ToString for PathFlowType { + fn to_string(&self) -> String { + match *self { + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for PathFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PathFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PathFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PathFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PathScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + pub input_transforms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_trigger: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag_override: Option, + #[serde(rename = "type")] + pub type_: PathScriptType, + } + impl From<&PathScript> for PathScript { + fn from(value: &PathScript) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PathScriptType { + #[serde(rename = "script")] + Script, + } + impl From<&PathScriptType> for PathScriptType { + fn from(value: &PathScriptType) -> Self { + value.clone() + } + } + impl ToString for PathScriptType { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + } + } + } + impl std::str::FromStr for PathScriptType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PathScriptType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PathScriptType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PathScriptType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolarsClientKwargs { + pub region_name: String, + } + impl From<&PolarsClientKwargs> for PolarsClientKwargs { + fn from(value: &PolarsClientKwargs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Policy { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub allowed_s3_keys: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub s3_inputs: Vec>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub triggerables: std::collections::HashMap< + String, + std::collections::HashMap, + >, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub triggerables_v2: std::collections::HashMap< + String, + std::collections::HashMap, + >, + } + impl From<&Policy> for Policy { + fn from(value: &Policy) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PolicyAllowedS3KeysItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s3_path: Option, + } + impl From<&PolicyAllowedS3KeysItem> for PolicyAllowedS3KeysItem { + fn from(value: &PolicyAllowedS3KeysItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PolicyExecutionMode { + #[serde(rename = "viewer")] + Viewer, + #[serde(rename = "publisher")] + Publisher, + #[serde(rename = "anonymous")] + Anonymous, + } + impl From<&PolicyExecutionMode> for PolicyExecutionMode { + fn from(value: &PolicyExecutionMode) -> Self { + value.clone() + } + } + impl ToString for PolicyExecutionMode { + fn to_string(&self) -> String { + match *self { + Self::Viewer => "viewer".to_string(), + Self::Publisher => "publisher".to_string(), + Self::Anonymous => "anonymous".to_string(), + } + } + } + impl std::str::FromStr for PolicyExecutionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "viewer" => Ok(Self::Viewer), + "publisher" => Ok(Self::Publisher), + "anonymous" => Ok(Self::Anonymous), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PolicyExecutionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PostgresTrigger { + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub postgres_resource_path: String, + pub publication_name: String, + pub replication_slot_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + } + impl From<&PostgresTrigger> for PostgresTrigger { + fn from(value: &PostgresTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Preview { + pub args: ScriptArgs, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + } + impl From<&Preview> for Preview { + fn from(value: &Preview) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum PreviewKind { + #[serde(rename = "code")] + Code, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "http")] + Http, + } + impl From<&PreviewKind> for PreviewKind { + fn from(value: &PreviewKind) -> Self { + value.clone() + } + } + impl ToString for PreviewKind { + fn to_string(&self) -> String { + match *self { + Self::Code => "code".to_string(), + Self::Identity => "identity".to_string(), + Self::Http => "http".to_string(), + } + } + } + impl std::str::FromStr for PreviewKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "code" => Ok(Self::Code), + "identity" => Ok(Self::Identity), + "http" => Ok(Self::Http), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for PreviewKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for PreviewKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for PreviewKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PublicationData { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub table_to_track: Vec, + pub transaction_to_track: Vec, + } + impl From<&PublicationData> for PublicationData { + fn from(value: &PublicationData) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct QueuedJob { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub aggregate_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + pub canceled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub canceled_reason: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + pub email: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_status: Option, + pub id: uuid::Uuid, + pub is_flow_step: bool, + pub job_kind: QueuedJobJobKind, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub language: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub logs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mem_peak: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parent_job: Option, + /**The user (u/userfoo) or group (g/groupfoo) whom +the execution of this script will be permissioned_as and by extension its DT_TOKEN. +*/ + pub permissioned_as: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preprocessed: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_flow: Option, + pub running: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scheduled_for: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub self_wait_time_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub suspend: Option, + pub tag: String, + pub visible_to_owner: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&QueuedJob> for QueuedJob { + fn from(value: &QueuedJob) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum QueuedJobJobKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "preview")] + Preview, + #[serde(rename = "dependencies")] + Dependencies, + #[serde(rename = "flowdependencies")] + Flowdependencies, + #[serde(rename = "appdependencies")] + Appdependencies, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "flowpreview")] + Flowpreview, + #[serde(rename = "script_hub")] + ScriptHub, + #[serde(rename = "identity")] + Identity, + #[serde(rename = "deploymentcallback")] + Deploymentcallback, + #[serde(rename = "singlescriptflow")] + Singlescriptflow, + #[serde(rename = "flowscript")] + Flowscript, + #[serde(rename = "flownode")] + Flownode, + #[serde(rename = "appscript")] + Appscript, + } + impl From<&QueuedJobJobKind> for QueuedJobJobKind { + fn from(value: &QueuedJobJobKind) -> Self { + value.clone() + } + } + impl ToString for QueuedJobJobKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Preview => "preview".to_string(), + Self::Dependencies => "dependencies".to_string(), + Self::Flowdependencies => "flowdependencies".to_string(), + Self::Appdependencies => "appdependencies".to_string(), + Self::Flow => "flow".to_string(), + Self::Flowpreview => "flowpreview".to_string(), + Self::ScriptHub => "script_hub".to_string(), + Self::Identity => "identity".to_string(), + Self::Deploymentcallback => "deploymentcallback".to_string(), + Self::Singlescriptflow => "singlescriptflow".to_string(), + Self::Flowscript => "flowscript".to_string(), + Self::Flownode => "flownode".to_string(), + Self::Appscript => "appscript".to_string(), + } + } + } + impl std::str::FromStr for QueuedJobJobKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "preview" => Ok(Self::Preview), + "dependencies" => Ok(Self::Dependencies), + "flowdependencies" => Ok(Self::Flowdependencies), + "appdependencies" => Ok(Self::Appdependencies), + "flow" => Ok(Self::Flow), + "flowpreview" => Ok(Self::Flowpreview), + "script_hub" => Ok(Self::ScriptHub), + "identity" => Ok(Self::Identity), + "deploymentcallback" => Ok(Self::Deploymentcallback), + "singlescriptflow" => Ok(Self::Singlescriptflow), + "flowscript" => Ok(Self::Flowscript), + "flownode" => Ok(Self::Flownode), + "appscript" => Ok(Self::Appscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for QueuedJobJobKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RawScript { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_concurrency_key: Option, + pub input_transforms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_trigger: Option, + pub language: RawScriptLanguage, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(rename = "type")] + pub type_: RawScriptType, + } + impl From<&RawScript> for RawScript { + fn from(value: &RawScript) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RawScriptForDependencies { + pub language: ScriptLang, + pub path: String, + pub raw_code: String, + } + impl From<&RawScriptForDependencies> for RawScriptForDependencies { + fn from(value: &RawScriptForDependencies) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptLanguage { + #[serde(rename = "deno")] + Deno, + #[serde(rename = "bun")] + Bun, + #[serde(rename = "python3")] + Python3, + #[serde(rename = "go")] + Go, + #[serde(rename = "bash")] + Bash, + #[serde(rename = "powershell")] + Powershell, + #[serde(rename = "postgresql")] + Postgresql, + #[serde(rename = "mysql")] + Mysql, + #[serde(rename = "bigquery")] + Bigquery, + #[serde(rename = "snowflake")] + Snowflake, + #[serde(rename = "mssql")] + Mssql, + #[serde(rename = "oracledb")] + Oracledb, + #[serde(rename = "graphql")] + Graphql, + #[serde(rename = "nativets")] + Nativets, + #[serde(rename = "php")] + Php, + } + impl From<&RawScriptLanguage> for RawScriptLanguage { + fn from(value: &RawScriptLanguage) -> Self { + value.clone() + } + } + impl ToString for RawScriptLanguage { + fn to_string(&self) -> String { + match *self { + Self::Deno => "deno".to_string(), + Self::Bun => "bun".to_string(), + Self::Python3 => "python3".to_string(), + Self::Go => "go".to_string(), + Self::Bash => "bash".to_string(), + Self::Powershell => "powershell".to_string(), + Self::Postgresql => "postgresql".to_string(), + Self::Mysql => "mysql".to_string(), + Self::Bigquery => "bigquery".to_string(), + Self::Snowflake => "snowflake".to_string(), + Self::Mssql => "mssql".to_string(), + Self::Oracledb => "oracledb".to_string(), + Self::Graphql => "graphql".to_string(), + Self::Nativets => "nativets".to_string(), + Self::Php => "php".to_string(), + } + } + } + impl std::str::FromStr for RawScriptLanguage { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "deno" => Ok(Self::Deno), + "bun" => Ok(Self::Bun), + "python3" => Ok(Self::Python3), + "go" => Ok(Self::Go), + "bash" => Ok(Self::Bash), + "powershell" => Ok(Self::Powershell), + "postgresql" => Ok(Self::Postgresql), + "mysql" => Ok(Self::Mysql), + "bigquery" => Ok(Self::Bigquery), + "snowflake" => Ok(Self::Snowflake), + "mssql" => Ok(Self::Mssql), + "oracledb" => Ok(Self::Oracledb), + "graphql" => Ok(Self::Graphql), + "nativets" => Ok(Self::Nativets), + "php" => Ok(Self::Php), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptLanguage { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptType { + #[serde(rename = "rawscript")] + Rawscript, + } + impl From<&RawScriptType> for RawScriptType { + fn from(value: &RawScriptType) -> Self { + value.clone() + } + } + impl ToString for RawScriptType { + fn to_string(&self) -> String { + match *self { + Self::Rawscript => "rawscript".to_string(), + } + } + } + impl std::str::FromStr for RawScriptType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "rawscript" => Ok(Self::Rawscript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Relations { + pub schema_name: String, + pub table_to_track: TableToTrack, + } + impl From<&Relations> for Relations { + fn from(value: &Relations) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Resource { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub extra_perms: std::collections::HashMap, + pub is_oauth: bool, + pub path: String, + pub resource_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&Resource> for Resource { + fn from(value: &Resource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ResourceType { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub edited_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format_extension: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&ResourceType> for ResourceType { + fn from(value: &ResourceType) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RestartedFrom { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branch_or_iteration_n: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flow_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub step_id: Option, + } + impl From<&RestartedFrom> for RestartedFrom { + fn from(value: &RestartedFrom) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Retry { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub constant: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exponential: Option, + } + impl From<&Retry> for Retry { + fn from(value: &Retry) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RetryConstant { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub attempts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seconds: Option, + } + impl From<&RetryConstant> for RetryConstant { + fn from(value: &RetryConstant) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RetryExponential { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub attempts: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub multiplier: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub random_factor: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seconds: Option, + } + impl From<&RetryExponential> for RetryExponential { + fn from(value: &RetryExponential) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RunnableType { + ScriptHash, + ScriptPath, + FlowPath, + } + impl From<&RunnableType> for RunnableType { + fn from(value: &RunnableType) -> Self { + value.clone() + } + } + impl ToString for RunnableType { + fn to_string(&self) -> String { + match *self { + Self::ScriptHash => "ScriptHash".to_string(), + Self::ScriptPath => "ScriptPath".to_string(), + Self::FlowPath => "FlowPath".to_string(), + } + } + } + impl std::str::FromStr for RunnableType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "ScriptHash" => Ok(Self::ScriptHash), + "ScriptPath" => Ok(Self::ScriptPath), + "FlowPath" => Ok(Self::FlowPath), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RunnableType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RunnableType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RunnableType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct S3Resource { + #[serde(rename = "accessKey", default, skip_serializing_if = "Option::is_none")] + pub access_key: Option, + pub bucket: String, + #[serde(rename = "endPoint")] + pub end_point: String, + #[serde(rename = "pathStyle")] + pub path_style: bool, + pub region: String, + #[serde(rename = "secretKey", default, skip_serializing_if = "Option::is_none")] + pub secret_key: Option, + #[serde(rename = "useSSL")] + pub use_ssl: bool, + } + impl From<&S3Resource> for S3Resource { + fn from(value: &S3Resource) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScalarMetric { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metric_id: Option, + pub value: f64, + } + impl From<&ScalarMetric> for ScalarMetric { + fn from(value: &ScalarMetric) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Schedule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cron_version: Option, + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub email: String, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub extra_perms: std::collections::HashMap, + pub is_flow: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_flow_overlap: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_exact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_failure_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_extra_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_recovery_times: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_success_extra_args: Option, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub paused_until: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub schedule: String, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub timezone: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&Schedule> for Schedule { + fn from(value: &Schedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScheduleWJobs { + #[serde(flatten)] + pub schedule: Schedule, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub jobs: Vec, + } + impl From<&ScheduleWJobs> for ScheduleWJobs { + fn from(value: &ScheduleWJobs) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScheduleWJobsJobsItem { + pub duration_ms: f64, + pub id: String, + pub success: bool, + } + impl From<&ScheduleWJobsJobsItem> for ScheduleWJobsJobsItem { + fn from(value: &ScheduleWJobsJobsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Script { + pub archived: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_ttl: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub codebase: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrency_time_window_s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub concurrent_limit: Option, + pub content: String, + pub created_at: chrono::DateTime, + pub created_by: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dedicated_worker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delete_after_use: Option, + pub deleted: bool, + pub description: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub draft_only: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub envs: Vec, + pub extra_perms: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub has_draft: Option, + pub has_preprocessor: bool, + pub hash: String, + pub is_template: bool, + pub kind: ScriptKind, + pub language: ScriptLang, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock_error_logs: Option, + pub no_main_func: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub on_behalf_of_email: Option, + /**The first element is the direct parent of the script, the second is the parent of the first, etc +*/ + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub parent_hashes: Vec, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub restart_unless_cancelled: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub schema: std::collections::HashMap, + pub starred: bool, + pub summary: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visible_to_runner_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, + } + impl From<&Script> for Script { + fn from(value: &Script) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScriptArgs(pub std::collections::HashMap); + impl std::ops::Deref for ScriptArgs { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From for std::collections::HashMap { + fn from(value: ScriptArgs) -> Self { + value.0 + } + } + impl From<&ScriptArgs> for ScriptArgs { + fn from(value: &ScriptArgs) -> Self { + value.clone() + } + } + impl From> for ScriptArgs { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScriptHistory { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_msg: Option, + pub script_hash: String, + } + impl From<&ScriptHistory> for ScriptHistory { + fn from(value: &ScriptHistory) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ScriptKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "failure")] + Failure, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "command")] + Command, + #[serde(rename = "approval")] + Approval, + #[serde(rename = "preprocessor")] + Preprocessor, + } + impl From<&ScriptKind> for ScriptKind { + fn from(value: &ScriptKind) -> Self { + value.clone() + } + } + impl ToString for ScriptKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Failure => "failure".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Command => "command".to_string(), + Self::Approval => "approval".to_string(), + Self::Preprocessor => "preprocessor".to_string(), + } + } + } + impl std::str::FromStr for ScriptKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "failure" => Ok(Self::Failure), + "trigger" => Ok(Self::Trigger), + "command" => Ok(Self::Command), + "approval" => Ok(Self::Approval), + "preprocessor" => Ok(Self::Preprocessor), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ScriptKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ScriptKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ScriptKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ScriptLang { + #[serde(rename = "python3")] + Python3, + #[serde(rename = "deno")] + Deno, + #[serde(rename = "go")] + Go, + #[serde(rename = "bash")] + Bash, + #[serde(rename = "powershell")] + Powershell, + #[serde(rename = "postgresql")] + Postgresql, + #[serde(rename = "mysql")] + Mysql, + #[serde(rename = "bigquery")] + Bigquery, + #[serde(rename = "snowflake")] + Snowflake, + #[serde(rename = "mssql")] + Mssql, + #[serde(rename = "oracledb")] + Oracledb, + #[serde(rename = "graphql")] + Graphql, + #[serde(rename = "nativets")] + Nativets, + #[serde(rename = "bun")] + Bun, + #[serde(rename = "php")] + Php, + #[serde(rename = "rust")] + Rust, + #[serde(rename = "ansible")] + Ansible, + #[serde(rename = "csharp")] + Csharp, + #[serde(rename = "nu")] + Nu, + } + impl From<&ScriptLang> for ScriptLang { + fn from(value: &ScriptLang) -> Self { + value.clone() + } + } + impl ToString for ScriptLang { + fn to_string(&self) -> String { + match *self { + Self::Python3 => "python3".to_string(), + Self::Deno => "deno".to_string(), + Self::Go => "go".to_string(), + Self::Bash => "bash".to_string(), + Self::Powershell => "powershell".to_string(), + Self::Postgresql => "postgresql".to_string(), + Self::Mysql => "mysql".to_string(), + Self::Bigquery => "bigquery".to_string(), + Self::Snowflake => "snowflake".to_string(), + Self::Mssql => "mssql".to_string(), + Self::Oracledb => "oracledb".to_string(), + Self::Graphql => "graphql".to_string(), + Self::Nativets => "nativets".to_string(), + Self::Bun => "bun".to_string(), + Self::Php => "php".to_string(), + Self::Rust => "rust".to_string(), + Self::Ansible => "ansible".to_string(), + Self::Csharp => "csharp".to_string(), + Self::Nu => "nu".to_string(), + } + } + } + impl std::str::FromStr for ScriptLang { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "python3" => Ok(Self::Python3), + "deno" => Ok(Self::Deno), + "go" => Ok(Self::Go), + "bash" => Ok(Self::Bash), + "powershell" => Ok(Self::Powershell), + "postgresql" => Ok(Self::Postgresql), + "mysql" => Ok(Self::Mysql), + "bigquery" => Ok(Self::Bigquery), + "snowflake" => Ok(Self::Snowflake), + "mssql" => Ok(Self::Mssql), + "oracledb" => Ok(Self::Oracledb), + "graphql" => Ok(Self::Graphql), + "nativets" => Ok(Self::Nativets), + "bun" => Ok(Self::Bun), + "php" => Ok(Self::Php), + "rust" => Ok(Self::Rust), + "ansible" => Ok(Self::Ansible), + "csharp" => Ok(Self::Csharp), + "nu" => Ok(Self::Nu), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ScriptLang { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ScriptLang { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ScriptLang { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlackToken { + pub access_token: String, + pub bot: SlackTokenBot, + pub team_id: String, + pub team_name: String, + } + impl From<&SlackToken> for SlackToken { + fn from(value: &SlackToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlackTokenBot { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bot_access_token: Option, + } + impl From<&SlackTokenBot> for SlackTokenBot { + fn from(value: &SlackTokenBot) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Slot { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + impl From<&Slot> for Slot { + fn from(value: &Slot) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SlotList { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub active: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub slot_name: Option, + } + impl From<&SlotList> for SlotList { + fn from(value: &SlotList) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct SqsTrigger { + pub aws_resource_path: String, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub message_attributes: Vec, + pub queue_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + } + impl From<&SqsTrigger> for SqsTrigger { + fn from(value: &SqsTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct StaticTransform { + #[serde(rename = "type")] + pub type_: StaticTransformType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + } + impl From<&StaticTransform> for StaticTransform { + fn from(value: &StaticTransform) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum StaticTransformType { + #[serde(rename = "javascript")] + Javascript, + } + impl From<&StaticTransformType> for StaticTransformType { + fn from(value: &StaticTransformType) -> Self { + value.clone() + } + } + impl ToString for StaticTransformType { + fn to_string(&self) -> String { + match *self { + Self::Javascript => "javascript".to_string(), + } + } + } + impl std::str::FromStr for StaticTransformType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "javascript" => Ok(Self::Javascript), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for StaticTransformType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for StaticTransformType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for StaticTransformType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TableToTrack(pub Vec); + impl std::ops::Deref for TableToTrack { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } + } + impl From for Vec { + fn from(value: TableToTrack) -> Self { + value.0 + } + } + impl From<&TableToTrack> for TableToTrack { + fn from(value: &TableToTrack) -> Self { + value.clone() + } + } + impl From> for TableToTrack { + fn from(value: Vec) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TableToTrackItem { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub columns_name: Vec, + pub table_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub where_clause: Option, + } + impl From<&TableToTrackItem> for TableToTrackItem { + fn from(value: &TableToTrackItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TeamInfo { + ///List of channels within the team + pub channels: Vec, + ///The unique identifier of the Microsoft Teams team + pub team_id: String, + ///The display name of the Microsoft Teams team + pub team_name: String, + } + impl From<&TeamInfo> for TeamInfo { + fn from(value: &TeamInfo) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TemplateScript { + pub language: Language, + pub postgres_resource_path: String, + pub relations: Vec, + } + impl From<&TemplateScript> for TemplateScript { + fn from(value: &TemplateScript) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TimeseriesMetric { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metric_id: Option, + pub values: Vec, + } + impl From<&TimeseriesMetric> for TimeseriesMetric { + fn from(value: &TimeseriesMetric) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TokenResponse { + pub access_token: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_in: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scope: Vec, + } + impl From<&TokenResponse> for TokenResponse { + fn from(value: &TokenResponse) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggerExtraProperty { + pub edited_at: chrono::DateTime, + pub edited_by: String, + pub email: String, + pub extra_perms: std::collections::HashMap, + pub is_flow: bool, + pub path: String, + pub script_path: String, + pub workspace_id: String, + } + impl From<&TriggerExtraProperty> for TriggerExtraProperty { + fn from(value: &TriggerExtraProperty) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggersCount { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub http_routes_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kafka_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mqtt_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nats_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub postgres_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub primary_schedule: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sqs_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub webhook_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub websocket_count: Option, + } + impl From<&TriggersCount> for TriggersCount { + fn from(value: &TriggersCount) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TriggersCountPrimarySchedule { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schedule: Option, + } + impl From<&TriggersCountPrimarySchedule> for TriggersCountPrimarySchedule { + fn from(value: &TriggersCountPrimarySchedule) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TruncatedToken { + pub created_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expiration: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, + pub last_used_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub scopes: Vec, + pub token_prefix: String, + } + impl From<&TruncatedToken> for TruncatedToken { + fn from(value: &TruncatedToken) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UpdateInput { + pub id: String, + pub is_public: bool, + pub name: String, + } + impl From<&UpdateInput> for UpdateInput { + fn from(value: &UpdateInput) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UploadFilePart { + pub part_number: i64, + pub tag: String, + } + impl From<&UploadFilePart> for UploadFilePart { + fn from(value: &UploadFilePart) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct User { + pub created_at: chrono::DateTime, + pub disabled: bool, + pub email: String, + pub folders: Vec, + pub folders_owners: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub groups: Vec, + pub is_admin: bool, + pub is_super_admin: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + pub operator: bool, + pub username: String, + } + impl From<&User> for User { + fn from(value: &User) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserUsage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub executions: Option, + } + impl From<&UserUsage> for UserUsage { + fn from(value: &UserUsage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserWorkspaceList { + pub email: String, + pub workspaces: Vec, + } + impl From<&UserWorkspaceList> for UserWorkspaceList { + fn from(value: &UserWorkspaceList) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserWorkspaceListWorkspacesItem { + pub color: String, + pub id: String, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub operator_settings: Option, + pub username: String, + } + impl From<&UserWorkspaceListWorkspacesItem> for UserWorkspaceListWorkspacesItem { + fn from(value: &UserWorkspaceListWorkspacesItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WebsocketTrigger { + pub can_return_message: bool, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + pub filters: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub initial_messages: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option, + } + impl From<&WebsocketTrigger> for WebsocketTrigger { + fn from(value: &WebsocketTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WebsocketTriggerFiltersItem { + pub key: String, + pub value: serde_json::Value, + } + impl From<&WebsocketTriggerFiltersItem> for WebsocketTriggerFiltersItem { + fn from(value: &WebsocketTriggerFiltersItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum WebsocketTriggerInitialMessage { + #[serde(rename = "raw_message")] + RawMessage(String), + #[serde(rename = "runnable_result")] + RunnableResult { args: ScriptArgs, is_flow: bool, path: String }, + } + impl From<&WebsocketTriggerInitialMessage> for WebsocketTriggerInitialMessage { + fn from(value: &WebsocketTriggerInitialMessage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WhileloopFlow { + pub modules: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallel: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + pub skip_failures: bool, + #[serde(rename = "type")] + pub type_: WhileloopFlowType, + } + impl From<&WhileloopFlow> for WhileloopFlow { + fn from(value: &WhileloopFlow) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WhileloopFlowType { + #[serde(rename = "forloopflow")] + Forloopflow, + } + impl From<&WhileloopFlowType> for WhileloopFlowType { + fn from(value: &WhileloopFlowType) -> Self { + value.clone() + } + } + impl ToString for WhileloopFlowType { + fn to_string(&self) -> String { + match *self { + Self::Forloopflow => "forloopflow".to_string(), + } + } + } + impl std::str::FromStr for WhileloopFlowType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "forloopflow" => Ok(Self::Forloopflow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WhileloopFlowType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillFileMetadata { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_modified: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub size_in_bytes: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version_id: Option, + } + impl From<&WindmillFileMetadata> for WindmillFileMetadata { + fn from(value: &WindmillFileMetadata) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillFilePreview { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content: Option, + pub content_type: WindmillFilePreviewContentType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub msg: Option, + } + impl From<&WindmillFilePreview> for WindmillFilePreview { + fn from(value: &WindmillFilePreview) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WindmillFilePreviewContentType { + RawText, + Csv, + Parquet, + Unknown, + } + impl From<&WindmillFilePreviewContentType> for WindmillFilePreviewContentType { + fn from(value: &WindmillFilePreviewContentType) -> Self { + value.clone() + } + } + impl ToString for WindmillFilePreviewContentType { + fn to_string(&self) -> String { + match *self { + Self::RawText => "RawText".to_string(), + Self::Csv => "Csv".to_string(), + Self::Parquet => "Parquet".to_string(), + Self::Unknown => "Unknown".to_string(), + } + } + } + impl std::str::FromStr for WindmillFilePreviewContentType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "RawText" => Ok(Self::RawText), + "Csv" => Ok(Self::Csv), + "Parquet" => Ok(Self::Parquet), + "Unknown" => Ok(Self::Unknown), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WindmillFilePreviewContentType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WindmillLargeFile { + pub s3: String, + } + impl From<&WindmillLargeFile> for WindmillLargeFile { + fn from(value: &WindmillLargeFile) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkerPing { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub custom_tags: Vec, + pub ip: String, + pub jobs_executed: i64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_job_workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_ping: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory_usage: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_15s: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_30m: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub occupancy_rate_5m: Option, + pub started_at: chrono::DateTime, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub vcpus: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wm_memory_usage: Option, + pub wm_version: String, + pub worker: String, + pub worker_group: String, + pub worker_instance: String, + } + impl From<&WorkerPing> for WorkerPing { + fn from(value: &WorkerPing) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowStatus { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub duration_ms: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scheduled_for: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub started_at: Option>, + } + impl From<&WorkflowStatus> for WorkflowStatus { + fn from(value: &WorkflowStatus) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowStatusRecord( + pub std::collections::HashMap, + ); + impl std::ops::Deref for WorkflowStatusRecord { + type Target = std::collections::HashMap; + fn deref(&self) -> &std::collections::HashMap { + &self.0 + } + } + impl From + for std::collections::HashMap { + fn from(value: WorkflowStatusRecord) -> Self { + value.0 + } + } + impl From<&WorkflowStatusRecord> for WorkflowStatusRecord { + fn from(value: &WorkflowStatusRecord) -> Self { + value.clone() + } + } + impl From> + for WorkflowStatusRecord { + fn from(value: std::collections::HashMap) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkflowTask { + pub args: ScriptArgs, + } + impl From<&WorkflowTask> for WorkflowTask { + fn from(value: &WorkflowTask) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Workspace { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub domain: Option, + pub id: String, + pub name: String, + pub owner: String, + } + impl From<&Workspace> for Workspace { + fn from(value: &Workspace) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceDefaultScripts { + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub default_script_content: std::collections::HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub hidden: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub order: Vec, + } + impl From<&WorkspaceDefaultScripts> for WorkspaceDefaultScripts { + fn from(value: &WorkspaceDefaultScripts) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceDeployUiSettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_type: Vec, + } + impl From<&WorkspaceDeployUiSettings> for WorkspaceDeployUiSettings { + fn from(value: &WorkspaceDeployUiSettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WorkspaceDeployUiSettingsIncludeTypeItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "trigger")] + Trigger, + } + impl From<&WorkspaceDeployUiSettingsIncludeTypeItem> + for WorkspaceDeployUiSettingsIncludeTypeItem { + fn from(value: &WorkspaceDeployUiSettingsIncludeTypeItem) -> Self { + value.clone() + } + } + impl ToString for WorkspaceDeployUiSettingsIncludeTypeItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Trigger => "trigger".to_string(), + } + } + } + impl std::str::FromStr for WorkspaceDeployUiSettingsIncludeTypeItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "trigger" => Ok(Self::Trigger), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WorkspaceDeployUiSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceGitSyncSettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_type: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub repositories: Vec, + } + impl From<&WorkspaceGitSyncSettings> for WorkspaceGitSyncSettings { + fn from(value: &WorkspaceGitSyncSettings) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WorkspaceGitSyncSettingsIncludeTypeItem { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + #[serde(rename = "app")] + App, + #[serde(rename = "folder")] + Folder, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "variable")] + Variable, + #[serde(rename = "secret")] + Secret, + #[serde(rename = "resourcetype")] + Resourcetype, + #[serde(rename = "schedule")] + Schedule, + #[serde(rename = "user")] + User, + #[serde(rename = "group")] + Group, + } + impl From<&WorkspaceGitSyncSettingsIncludeTypeItem> + for WorkspaceGitSyncSettingsIncludeTypeItem { + fn from(value: &WorkspaceGitSyncSettingsIncludeTypeItem) -> Self { + value.clone() + } + } + impl ToString for WorkspaceGitSyncSettingsIncludeTypeItem { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + Self::App => "app".to_string(), + Self::Folder => "folder".to_string(), + Self::Resource => "resource".to_string(), + Self::Variable => "variable".to_string(), + Self::Secret => "secret".to_string(), + Self::Resourcetype => "resourcetype".to_string(), + Self::Schedule => "schedule".to_string(), + Self::User => "user".to_string(), + Self::Group => "group".to_string(), + } + } + } + impl std::str::FromStr for WorkspaceGitSyncSettingsIncludeTypeItem { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + "app" => Ok(Self::App), + "folder" => Ok(Self::Folder), + "resource" => Ok(Self::Resource), + "variable" => Ok(Self::Variable), + "secret" => Ok(Self::Secret), + "resourcetype" => Ok(Self::Resourcetype), + "schedule" => Ok(Self::Schedule), + "user" => Ok(Self::User), + "group" => Ok(Self::Group), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WorkspaceGitSyncSettingsIncludeTypeItem { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceInvite { + pub email: String, + pub is_admin: bool, + pub operator: bool, + pub workspace_id: String, + } + impl From<&WorkspaceInvite> for WorkspaceInvite { + fn from(value: &WorkspaceInvite) -> Self { + value.clone() + } + } +} +#[derive(Clone, Debug)] +/**Client for Windmill API + +Version: 1.478.1*/ +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} +impl Client { + /// Create a new client. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new(baseurl: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] + let client = { + let dur = std::time::Duration::from_secs(15); + reqwest::ClientBuilder::new().connect_timeout(dur).timeout(dur) + }; + #[cfg(target_arch = "wasm32")] + let client = reqwest::ClientBuilder::new(); + Self::new_with_client(baseurl, client.build().unwrap()) + } + /// Construct a new client with an existing `reqwest::Client`, + /// allowing more control over its configuration. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + /// Get the base URL to which requests are made. + pub fn baseurl(&self) -> &String { + &self.baseurl + } + /// Get the internal `reqwest::Client` used to make requests. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + /// Get the version of this API. + /// + /// This string is pulled directly from the source OpenAPI + /// document and may be in any format the API selects. + pub fn api_version(&self) -> &'static str { + "1.478.1" + } +} +impl Client { + /**list all workspaces visible to me + +Sends a `GET` request to `/workspaces/list` + +*/ + pub async fn list_workspaces<'a>( + &'a self, + ) -> Result>, Error<()>> { + let url = format!("{}/workspaces/list", self.baseurl,); + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create script + +Sends a `POST` request to `/w/{workspace}/scripts/create` + +Arguments: +- `workspace` +- `body`: Partially filled script +*/ + pub async fn create_script<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewScript, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/scripts/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**get flow by path + +Sends a `GET` request to `/w/{workspace}/flows/get/{path}` + +*/ + pub async fn get_flow_by_path<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + with_starred_info: Option, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/get/{}", self.baseurl, encode_path(& workspace.to_string()), + encode_path(& path.to_string()), + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &with_starred_info { + query.push(("with_starred_info", v.to_string())); + } + let request = self + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create flow + +Sends a `POST` request to `/w/{workspace}/flows/create` + +Arguments: +- `workspace` +- `body`: Partially filled flow +*/ + pub async fn create_flow<'a>( + &'a self, + workspace: &'a str, + body: &'a types::CreateFlowBody, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/flows/create", self.baseurl, encode_path(& workspace.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**create schedule + +Sends a `POST` request to `/w/{workspace}/schedules/create` + +Arguments: +- `workspace` +- `body`: new schedule +*/ + pub async fn create_schedule<'a>( + &'a self, + workspace: &'a str, + body: &'a types::NewSchedule, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/create", self.baseurl, encode_path(& workspace + .to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } + /**update schedule + +Sends a `POST` request to `/w/{workspace}/schedules/update/{path}` + +Arguments: +- `workspace` +- `path` +- `body`: updated schedule +*/ + pub async fn update_schedule<'a>( + &'a self, + workspace: &'a str, + path: &'a str, + body: &'a types::EditSchedule, + ) -> Result, Error<()>> { + let url = format!( + "{}/w/{}/schedules/update/{}", self.baseurl, encode_path(& workspace + .to_string()), encode_path(& path.to_string()), + ); + let request = self.client.post(url).json(&body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::stream(response)), + _ => Err(Error::UnexpectedResponse(response)), + } + } +} +pub mod prelude { + pub use super::Client; +} diff --git a/backend/windmill-api-client/src/lib.rs b/backend/windmill-api-client/src/lib.rs index 0b7222c5a5..3b879e0d98 100644 --- a/backend/windmill-api-client/src/lib.rs +++ b/backend/windmill-api-client/src/lib.rs @@ -1,4 +1,4 @@ -include!(concat!(env!("OUT_DIR"), "/codegen.rs")); +include!("./codegen.rs"); pub fn create_client(base_url: &str, token: String) -> Client { let mut val = reqwest::header::HeaderValue::from_str(&format!("Bearer {token}")) diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 253a125df7..7a715d7437 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -10,14 +10,16 @@ path = "src/lib.rs" [features] default = [] -enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise"] -stripe = ["dep:async-stripe"] -enterprise_saml = ["dep:samael"] +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 = [] +enterprise_saml = ["dep:samael", "dep:libxml"] 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"] -prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus"] -openidconnect = ["dep:openidconnect"] +parquet = ["dep:datafusion", "dep:object_store", "windmill-common/parquet", "windmill-worker/parquet"] +prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker/prometheus"] +openidconnect = ["dep:openidconnect", "windmill-common/openidconnect"] tantivy = ["dep:windmill-indexer"] kafka = ["dep:rdkafka"] nats = ["dep:async-nats", "dep:nkeys"] @@ -26,22 +28,31 @@ smtp = ["dep:mail-parser", "dep:openssl", "windmill-common/smtp"] license = ["dep:rsa"] zip = ["dep:async_zip"] oauth2 = ["dep:async-oauth2"] -http_trigger = ["dep:matchit"] +http_trigger = ["dep:matchit", "dep:thiserror", "dep:sha1", "dep:constant_time_eq"] static_frontend = ["dep:rust-embed"] postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"] mqtt_trigger = ["dep:thiserror", "dep:rumqttc"] sqs_trigger = ["dep:aws-sdk-sqs", "dep:thiserror", "dep:aws-config"] +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/modelcontextprotocol/rust-sdk", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true } windmill-queue.workspace = true windmill-common = { workspace = true, default-features = false } windmill-audit.workspace = true windmill-parser.workspace = true windmill-parser-ts.workspace = true windmill-parser-py.workspace = true +windmill-parser-py-imports.workspace = true windmill-git-sync.workspace = true windmill-indexer = { workspace = true, optional = true } +windmill-worker.workspace = true tokio.workspace = true +tokio-stream.workspace = true anyhow.workspace = true argon2.workspace = true axum.workspace = true @@ -65,6 +76,7 @@ hex.workspace = true base64.workspace = true base32.workspace = true serde_urlencoded.workspace = true +serde_yml.workspace = true cron.workspace = true mime_guess.workspace = true rust-embed = { workspace = true, optional = true } @@ -83,14 +95,17 @@ tokio-tar.workspace = true hmac.workspace = true cookie.workspace = true sha2.workspace = true +sha1 = { workspace = true, optional = true } +constant_time_eq = { workspace = true, optional = true } urlencoding.workspace = true -async-stripe = { workspace = true, optional = true } lazy_static.workspace = true prometheus = { workspace = true, optional = true } async_zip = { workspace = true, optional = true } regex.workspace = true bytes.workspace = true +url.workspace = true samael = { workspace = true, optional = true } +libxml = { workspace = true, optional = true } async-recursion.workspace = true rsa = { workspace = true, optional = true} uuid.workspace = true @@ -103,7 +118,6 @@ candle-nn = { workspace = true, optional = true} datafusion = { workspace = true, optional = true} object_store = { workspace = true, optional = true} openidconnect = { workspace = true, optional = true} -url = { workspace = true, optional = true} jsonwebtoken = { workspace = true } matchit = { workspace = true, optional = true } tokio-tungstenite = { workspace = true, optional = true} @@ -113,6 +127,7 @@ nkeys = { workspace = true, optional = true } const_format.workspace = true pin-project.workspace = true http.workspace = true +indexmap.workspace = true async-stream.workspace = true ulid.workspace = true rust-postgres = { workspace = true, optional = true } @@ -123,4 +138,13 @@ rust_decimal = { workspace = true, optional = true } rust-postgres-native-tls = { workspace = true, optional = true} rumqttc = { workspace = true, optional = true } aws-sdk-sqs = { workspace = true, optional = true } -aws-config = { workspace = true, optional = true} +aws-config = { workspace = true, optional = true } +aws-sdk-sts = { workspace = true, optional = true } +google-cloud-pubsub = { workspace = true, optional = true } +google-cloud-googleapis = { workspace = true , optional = true } +tonic = { workspace = true, optional = true } +deno_error = { workspace = true, optional = true } +deno_core = { workspace = true, optional = true } + +[build-dependencies] +deno_core = { workspace = true, optional = true } \ No newline at end of file diff --git a/backend/windmill-api/openapi-deref.json b/backend/windmill-api/openapi-deref.json index 91214e99b4..1186be6069 100644 --- a/backend/windmill-api/openapi-deref.json +++ b/backend/windmill-api/openapi-deref.json @@ -1,7 +1,7 @@ { "openapi": "3.0.3", "info": { - "version": "1.423.2", + "version": "1.492.1", "title": "Windmill API", "contact": { "name": "Windmill Team", @@ -197,6 +197,14 @@ }, { "$ref": "#/components/parameters/ActionKind" + }, + { + "name": "all_workspaces", + "in": "query", + "description": "get audit logs for all workspaces", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -618,6 +626,9 @@ "is_super_admin": { "type": "boolean" }, + "is_devops": { + "type": "boolean" + }, "name": { "type": "string" } @@ -873,6 +884,27 @@ } } }, + "/github_app/connected_repositories": { + "get": { + "summary": "get connected repositories", + "operationId": "getGlobalConnectedRepositories", + "tags": [ + "git_sync" + ], + "responses": { + "200": { + "description": "connected repositories", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GithubInstallations" + } + } + } + } + } + } + }, "/workspaces/list": { "get": { "summary": "list all workspaces visible to me", @@ -1329,9 +1361,24 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CriticalAlert" + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CriticalAlert" + } + }, + "total_rows": { + "type": "integer", + "description": "Total number of rows matching the query.", + "example": 100 + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages based on the page size.", + "example": 10 + } } } } @@ -1670,9 +1717,19 @@ "tags": [ "user" ], + "parameters": [ + { + "name": "if_expiring_in_less_than_s", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { - "description": "free usage", + "description": "new token", "content": { "text/plain": { "schema": { @@ -1910,6 +1967,220 @@ } } }, + "/w/{workspace}/github_app/token": { + "post": { + "summary": "get github app token", + "operationId": "getGithubAppToken", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "jwt job token", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_token": { + "type": "string" + } + }, + "required": [ + "job_token" + ] + } + } + } + }, + "responses": { + "200": { + "description": "github app token", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + }, + "required": [ + "token" + ] + } + } + } + } + } + } + }, + "/w/{workspace}/github_app/install_from_workspace": { + "post": { + "tags": [ + "Git Sync" + ], + "summary": "Install a GitHub installation from another workspace", + "operationId": "installFromWorkspace", + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "source_workspace_id": { + "type": "string", + "description": "The ID of the workspace containing the installation to copy" + }, + "installation_id": { + "type": "number", + "description": "The ID of the GitHub installation to copy" + } + }, + "required": [ + "source_workspace_id", + "installation_id" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Installation successfully copied" + } + } + } + }, + "/w/{workspace}/github_app/installation/{installation_id}": { + "delete": { + "summary": "Delete a GitHub installation from a workspace", + "operationId": "deleteFromWorkspace", + "description": "Removes a GitHub installation from the specified workspace. Requires admin privileges.", + "tags": [ + "Git Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "name": "installation_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + }, + "description": "The ID of the GitHub installation to delete" + } + ], + "responses": { + "200": { + "description": "Installation successfully deleted" + } + } + } + }, + "/w/{workspace}/github_app/export/{installationId}": { + "get": { + "summary": "Export GitHub installation JWT token", + "description": "Exports the JWT token for a specific GitHub installation in the workspace", + "operationId": "exportInstallation", + "tags": [ + "Git Sync" + ], + "parameters": [ + { + "name": "workspace", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "installationId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successfully exported the JWT token", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jwt_token": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/w/{workspace}/github_app/import": { + "post": { + "summary": "Import GitHub installation from JWT token", + "description": "Imports a GitHub installation from a JWT token exported from another instance", + "operationId": "importInstallation", + "tags": [ + "Git Sync" + ], + "parameters": [ + { + "name": "workspace", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "jwt_token" + ], + "properties": { + "jwt_token": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully imported the installation" + } + } + } + }, "/users/accept_invite": { "post": { "summary": "accept invite to workspace", @@ -2374,6 +2645,46 @@ } } }, + "/w/{workspace}/workspaces/change_workspace_color": { + "post": { + "summary": "change workspace id", + "operationId": "changeWorkspaceColor", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "color": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/users/whois/{username}": { "get": { "summary": "whois", @@ -2408,6 +2719,43 @@ } } }, + "/w/{workspace}/workspaces/operator_settings": { + "post": { + "operationId": "updateOperatorSettings", + "summary": "Update operator settings for a workspace", + "description": "Updates the operator settings for a specific workspace. Requires workspace admin privileges.", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OperatorSettings" + } + } + } + }, + "responses": { + "200": { + "description": "Operator settings updated successfully", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/users/exists/{email}": { "get": { "summary": "exists email", @@ -2540,6 +2888,15 @@ "slack_command_script": { "type": "string" }, + "teams_team_id": { + "type": "string" + }, + "teams_command_script": { + "type": "string" + }, + "teams_team_name": { + "type": "string" + }, "auto_invite_domain": { "type": "string" }, @@ -2552,9 +2909,6 @@ "plan": { "type": "string" }, - "automatic_billing": { - "type": "boolean" - }, "customer_id": { "type": "string" }, @@ -2564,11 +2918,8 @@ "deploy_to": { "type": "string" }, - "ai_resource": { - "$ref": "#/components/schemas/AiResource" - }, - "code_completion_enabled": { - "type": "boolean" + "ai_config": { + "$ref": "#/components/schemas/AIConfig" }, "error_handler": { "type": "string" @@ -2593,11 +2944,18 @@ }, "default_scripts": { "$ref": "#/components/schemas/WorkspaceDefaultScripts" + }, + "mute_critical_alerts": { + "type": "boolean" + }, + "color": { + "type": "string" + }, + "operator_settings": { + "$ref": "#/components/schemas/OperatorSettings" } }, "required": [ - "code_completion_enabled", - "automatic_billing", "error_handler_muted_on_cancel" ] } @@ -2690,16 +3048,16 @@ "usage": { "type": "number" }, - "seats": { - "type": "number" + "owner": { + "type": "string" }, - "automatic_billing": { - "type": "boolean" + "status": { + "type": "string" } }, "required": [ "premium", - "automatic_billing" + "owner" ] } } @@ -2708,10 +3066,43 @@ } } }, - "/w/{workspace}/workspaces/set_automatic_billing": { + "/w/{workspace}/workspaces/threshold_alert": { + "get": { + "summary": "get threshold alert info", + "operationId": "getThresholdAlert", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "threshold_alert_amount": { + "type": "number" + }, + "last_alert_sent": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, "post": { - "summary": "set automatic billing", - "operationId": "setAutomaticBilling", + "summary": "set threshold alert info", + "operationId": "setThresholdAlert", "tags": [ "workspace" ], @@ -2721,23 +3112,17 @@ } ], "requestBody": { - "description": "automatic billing", + "description": "threshold alert info", "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "automatic_billing": { - "type": "boolean" - }, - "seats": { + "threshold_alert_amount": { "type": "number" } - }, - "required": [ - "automatic_billing" - ] + } } } } @@ -2798,6 +3183,173 @@ } } }, + "/w/{workspace}/workspaces/edit_teams_command": { + "post": { + "summary": "edit teams command", + "operationId": "editTeamsCommand", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "WorkspaceInvite", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slack_command_script": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/available_teams_ids": { + "get": { + "summary": "list available teams ids", + "operationId": "listAvailableTeamsIds", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "team_name": { + "type": "string" + }, + "team_id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/available_teams_channels": { + "get": { + "summary": "list available teams channels", + "operationId": "listAvailableTeamsChannels", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_name": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "service_url": { + "type": "string" + }, + "tenant_id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/connect_teams": { + "post": { + "summary": "connect teams", + "operationId": "connectTeams", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "connect teams", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "team_id": { + "type": "string" + }, + "team_name": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/workspaces/run_slack_message_test_job": { "post": { "summary": "run a job that sends a message to Slack", @@ -2851,6 +3403,59 @@ } } }, + "/w/{workspace}/workspaces/run_teams_message_test_job": { + "post": { + "summary": "run a job that sends a message to Teams", + "operationId": "runTeamsMessageTestJob", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "path to hub script to run and its corresponding args", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "hub_script_path": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "test_msg": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "text/json": { + "schema": { + "type": "object", + "properties": { + "job_uuid": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/w/{workspace}/workspaces/edit_deploy_to": { "post": { "summary": "edit deploy to", @@ -3000,18 +3605,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "required": [ - "code_completion_enabled" - ], - "properties": { - "ai_resource": { - "$ref": "#/components/schemas/AiResource" - }, - "code_completion_enabled": { - "type": "boolean" - } - } + "$ref": "#/components/schemas/AIConfig" } } } @@ -3046,25 +3640,9 @@ "200": { "description": "status", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "object", - "properties": { - "ai_provider": { - "type": "string" - }, - "exists_ai_resource": { - "type": "boolean" - }, - "code_completion_enabled": { - "type": "boolean" - } - }, - "required": [ - "ai_provider", - "exists_ai_resource", - "code_completion_enabled" - ] + "$ref": "#/components/schemas/AIConfig" } } } @@ -3578,11 +4156,35 @@ }, "websocket_used": { "type": "boolean" + }, + "kafka_used": { + "type": "boolean" + }, + "nats_used": { + "type": "boolean" + }, + "postgres_used": { + "type": "boolean" + }, + "mqtt_used": { + "type": "boolean" + }, + "gcp_used": { + "type": "boolean" + }, + "sqs_used": { + "type": "boolean" } }, "required": [ "http_routes_used", - "websocket_used" + "websocket_used", + "kafka_used", + "nats_used", + "postgres_used", + "mqtt_used", + "gcp_used", + "sqs_used" ] } } @@ -4211,6 +4813,186 @@ } } }, + "/w/{workspace}/workspaces/critical_alerts": { + "get": { + "summary": "Get all critical alerts for this workspace", + "operationId": "workspaceGetCriticalAlerts", + "tags": [ + "setting" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1, + "description": "The page number to retrieve (minimum value is 1)" + } + }, + { + "in": "query", + "name": "page_size", + "schema": { + "type": "integer", + "default": 10, + "maximum": 100, + "description": "Number of alerts per page (maximum is 100)" + } + }, + { + "in": "query", + "name": "acknowledged", + "schema": { + "type": "boolean", + "nullable": true, + "description": "Filter by acknowledgment status; true for acknowledged, false for unacknowledged, and omit for all alerts" + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved all critical alerts", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CriticalAlert" + } + }, + "total_rows": { + "type": "integer", + "description": "Total number of rows matching the query.", + "example": 100 + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages based on the page size.", + "example": 10 + } + } + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/critical_alerts/{id}/acknowledge": { + "post": { + "summary": "Acknowledge a critical alert for this workspace", + "operationId": "workspaceAcknowledgeCriticalAlert", + "tags": [ + "setting" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + }, + "description": "The ID of the critical alert to acknowledge" + } + ], + "responses": { + "200": { + "description": "Successfully acknowledged the critical alert", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "Critical alert acknowledged" + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/critical_alerts/acknowledge_all": { + "post": { + "summary": "Acknowledge all unacknowledged critical alerts for this workspace", + "operationId": "workspaceAcknowledgeAllCriticalAlerts", + "tags": [ + "setting" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "Successfully acknowledged all unacknowledged critical alerts.", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "All unacknowledged critical alerts acknowledged" + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/critical_alerts/mute": { + "post": { + "summary": "Mute critical alert UI for this workspace", + "operationId": "workspaceMuteCriticalAlertsUI", + "tags": [ + "setting" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "Boolean flag to mute critical alerts.", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mute_critical_alerts": { + "type": "boolean", + "description": "Whether critical alerts should be muted.", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully updated mute critical alert settings.", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "Updated mute critical alert UI settings for workspace: workspace_id" + } + } + } + } + } + } + }, "/oauth/login_callback/{client_name}": { "post": { "security": [], @@ -4562,6 +5344,32 @@ } } }, + "/w/{workspace}/oauth/disconnect_teams": { + "post": { + "summary": "disconnect teams", + "operationId": "disconnectTeams", + "tags": [ + "oauth" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "disconnected teams", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/oauth/list_logins": { "get": { "summary": "list oauth logins", @@ -4675,6 +5483,78 @@ } } }, + "/teams/sync": { + "post": { + "operationId": "syncTeams", + "summary": "synchronize Microsoft Teams information (teams/channels)", + "tags": [ + "teams" + ], + "responses": { + "200": { + "description": "Teams information successfully synchronized", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamInfo" + } + } + } + } + } + } + } + }, + "/teams/activities": { + "post": { + "summary": "send update to Microsoft Teams activity", + "description": "Respond to a Microsoft Teams activity after a workspace command is run", + "operationId": "sendMessageToConversation", + "tags": [ + "teams" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "conversation_id", + "text" + ], + "properties": { + "conversation_id": { + "type": "string", + "description": "The ID of the Teams conversation/activity" + }, + "success": { + "type": "boolean", + "description": "Used for styling the card conditionally", + "default": true + }, + "text": { + "type": "string", + "description": "The message text to be sent in the Teams card" + }, + "card_block": { + "type": "object", + "description": "The card block to be sent in the Teams card" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Activity processed successfully" + } + } + } + }, "/w/{workspace}/resources/create": { "post": { "summary": "create resource", @@ -5637,6 +6517,44 @@ } } }, + "/apps_u/public_app_by_custom_path/{custom_path}": { + "get": { + "summary": "get public app by custom path", + "operationId": "getPublicAppByCustomPath", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/CustomPath" + } + ], + "responses": { + "200": { + "description": "app details", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AppWithLastVersion" + }, + { + "type": "object", + "properties": { + "workspace_id": { + "type": "string" + } + } + } + ] + } + } + } + } + } + } + }, "/scripts/hub/get/{path}": { "get": { "summary": "get hub script content by path", @@ -5986,7 +6904,7 @@ }, { "name": "last_parent_hash", - "description": "mask to filter scripts whom last parent in the chain has exact hash. \nBeware that each script stores only a limited number of parents. Hence\nthe last parent hash for a script is not necessarily its top-most parent.\nTo find the top-most parent you will have to jump from last to last hash\n until finding the parent\n", + "description": "mask to filter scripts whom last parent in the chain has exact hash.\nBeware that each script stores only a limited number of parents. Hence\nthe last parent hash for a script is not necessarily its top-most parent.\nTo find the top-most parent you will have to jump from last to last hash\n until finding the parent\n", "in": "query", "schema": { "type": "string" @@ -6002,7 +6920,7 @@ }, { "name": "show_archived", - "description": "(default false)\nshow only the archived files.\nwhen multiple archived hash share the same path, only the ones with the latest create_at\nare \ned.\n", + "description": "(default false)\nshow only the archived files.\nwhen multiple archived hash share the same path, only the ones with the latest create_at\nare\ned.\n", "in": "query", "schema": { "type": "boolean" @@ -6289,6 +7207,24 @@ "tags": [ "worker" ], + "parameters": [ + { + "name": "workspace", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "show_workspace_restriction", + "in": "query", + "schema": { + "type": "boolean" + }, + "required": false + } + ], "responses": { "200": { "description": "list of custom tags", @@ -6440,7 +7376,7 @@ }, "/w/{workspace}/scripts/delete/p/{path}": { "post": { - "summary": "delete all scripts at a given path (require admin)", + "summary": "delete script at a given path (require admin)", "operationId": "deleteScriptByPath", "tags": [ "script" @@ -6451,6 +7387,14 @@ }, { "$ref": "#/components/parameters/ScriptPath" + }, + { + "name": "keep_captures", + "description": "keep captures", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -6625,6 +7569,38 @@ } } }, + "/w/{workspace}/scripts/list_paths_from_workspace_runnable/{path}": { + "get": { + "summary": "list script paths using provided script as a relative import", + "operationId": "listScriptPathsFromWorkspaceRunnable", + "tags": [ + "script" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "responses": { + "200": { + "description": "list of script paths", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, "/w/{workspace}/scripts/get_latest_version/{path}": { "get": { "summary": "get scripts's latest version (hash)", @@ -6645,7 +7621,6 @@ "description": "Script version/hash", "content": { "application/json": { - "required": false, "schema": { "$ref": "#/components/schemas/ScriptHistory" } @@ -6895,6 +7870,96 @@ } } }, + "/w/{workspace}/jobs/list_selected_job_groups": { + "post": { + "summary": "list selected jobs script/flow schemas grouped by (kind, path)", + "operationId": "listSelectedJobGroups", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "script args", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + }, + "responses": { + "200": { + "description": "result", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "script", + "flow" + ] + }, + "script_path": { + "type": "string" + }, + "latest_schema": { + "type": "object" + }, + "schemas": { + "type": "array", + "items": { + "type": "object", + "properties": { + "schema": { + "type": "object" + }, + "script_hash": { + "type": "string" + }, + "job_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "schema", + "script_hash", + "job_ids" + ] + } + } + }, + "required": [ + "kind", + "script_path", + "latest_schema", + "schemas" + ] + } + } + } + } + } + } + } + }, "/w/{workspace}/jobs/run/p/{path}": { "post": { "summary": "run script by path", @@ -7491,7 +8556,6 @@ "description": "Flow version", "content": { "application/json": { - "required": false, "schema": { "$ref": "#/components/schemas/FlowVersion" } @@ -7501,6 +8565,41 @@ } } }, + "/w/{workspace}/flows/list_paths_from_workspace_runnable/{runnable_kind}/{path}": { + "get": { + "summary": "list flow paths from workspace runnable", + "operationId": "listFlowPathsFromWorkspaceRunnable", + "tags": [ + "flow" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "responses": { + "200": { + "description": "list of flow paths", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, "/w/{workspace}/flows/get/v/{version}/p/{path}": { "get": { "summary": "get flow version", @@ -7510,7 +8609,6 @@ "$ref": "#/components/parameters/WorkspaceId" }, { - "type": "string", "name": "version", "in": "path", "required": true, @@ -7548,7 +8646,6 @@ "$ref": "#/components/parameters/WorkspaceId" }, { - "type": "string", "name": "version", "in": "path", "required": true, @@ -7632,6 +8729,40 @@ } } }, + "/w/{workspace}/flows/deployment_status/p/{path}": { + "get": { + "summary": "get flow deployment status", + "operationId": "getFlowDeploymentStatus", + "tags": [ + "flow" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "responses": { + "200": { + "description": "flow status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "lock_error_logs": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/w/{workspace}/flows/get_triggers_count/{path}": { "get": { "summary": "get triggers count of flow", @@ -7970,6 +9101,14 @@ }, { "$ref": "#/components/parameters/ScriptPath" + }, + { + "name": "keep_captures", + "description": "keep captures", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -8267,6 +9406,9 @@ }, "deployment_message": { "type": "string" + }, + "custom_path": { + "type": "string" } }, "required": [ @@ -8293,6 +9435,81 @@ } } }, + "/w/{workspace}/apps/create_raw": { + "post": { + "summary": "create app raw", + "operationId": "createAppRaw", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new app", + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "app": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "value": {}, + "summary": { + "type": "string" + }, + "policy": { + "$ref": "#/components/schemas/Policy" + }, + "draft_only": { + "type": "boolean" + }, + "deployment_message": { + "type": "string" + }, + "custom_path": { + "type": "string" + } + }, + "required": [ + "path", + "value", + "summary", + "policy" + ] + }, + "js": { + "type": "string" + }, + "css": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "app created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/apps/exists/{path}": { "get": { "summary": "does an app exisst at path", @@ -8358,6 +9575,35 @@ } } }, + "/w/{workspace}/apps/get/lite/{path}": { + "get": { + "summary": "get app lite by path", + "operationId": "getAppLiteByPath", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "responses": { + "200": { + "description": "app lite details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppWithLastVersion" + } + } + } + } + } + } + }, "/w/{workspace}/apps/get/draft/{path}": { "get": { "summary": "get app by path with draft", @@ -8439,7 +9685,6 @@ "description": "App version", "content": { "application/json": { - "required": false, "schema": { "$ref": "#/components/schemas/AppHistory" } @@ -8449,6 +9694,41 @@ } } }, + "/w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}": { + "get": { + "summary": "list app paths from workspace runnable", + "operationId": "listAppPathsFromWorkspaceRunnable", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "responses": { + "200": { + "description": "list of app paths", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, "/w/{workspace}/apps/history_update/a/{id}/v/{version}": { "post": { "summary": "update app history", @@ -8808,6 +10088,9 @@ }, "deployment_message": { "type": "string" + }, + "custom_path": { + "type": "string" } } } @@ -8828,6 +10111,155 @@ } } }, + "/w/{workspace}/apps/update_raw/{path}": { + "post": { + "summary": "update app", + "operationId": "updateAppRaw", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "requestBody": { + "description": "update app", + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "app": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "value": {}, + "policy": { + "$ref": "#/components/schemas/Policy" + }, + "deployment_message": { + "type": "string" + }, + "custom_path": { + "type": "string" + } + } + }, + "js": { + "type": "string" + }, + "css": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "app updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/apps/custom_path_exists/{custom_path}": { + "get": { + "summary": "check if custom path exists", + "operationId": "customPathExists", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/CustomPath" + } + ], + "responses": { + "200": { + "description": "custom path exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/apps/sign_s3_objects": { + "post": { + "summary": "sign s3 objects, to be used by anonymous users in public apps", + "operationId": "signS3Objects", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "s3 objects to sign", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_objects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/S3Object" + } + } + }, + "required": [ + "s3_objects" + ] + } + } + } + }, + "responses": { + "200": { + "description": "signed s3 objects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/S3Object" + } + } + } + } + } + } + } + }, "/w/{workspace}/apps_u/execute_component/{path}": { "post": { "summary": "executeComponent", @@ -8857,6 +10289,9 @@ "path": { "type": "string" }, + "version": { + "type": "integer" + }, "args": {}, "raw_code": { "type": "object", @@ -8882,6 +10317,9 @@ "language" ] }, + "id": { + "type": "integer" + }, "force_viewer_static_fields": { "type": "object" }, @@ -8917,6 +10355,146 @@ } } }, + "/w/{workspace}/apps_u/upload_s3_file/{path}": { + "post": { + "summary": "upload s3 file from app", + "operationId": "uploadS3FileFromApp", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "name": "file_key", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "file_extension", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "s3_resource_path", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "resource_type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "storage", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "content_type", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "content_disposition", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "File content", + "required": true, + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "file uploaded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "file_key": { + "type": "string" + }, + "delete_token": { + "type": "string" + } + }, + "required": [ + "file_key", + "delete_token" + ] + } + } + } + } + } + } + }, + "/w/{workspace}/apps_u/delete_s3_file": { + "delete": { + "summary": "delete s3 file from app", + "operationId": "deleteS3FileFromApp", + "tags": [ + "app" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "name": "delete_token", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "file deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/jobs/run/f/{path}": { "post": { "summary": "run flow by path", @@ -9003,6 +10581,90 @@ } } }, + "/w/{workspace}/jobs/run/batch_rerun_jobs": { + "post": { + "summary": "re-run multiple jobs", + "operationId": "batchReRunJobs", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "list of job ids to re run and arg tranforms", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "job_ids", + "script_options_by_path", + "flow_options_by_path" + ], + "properties": { + "job_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "script_options_by_path": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "input_transforms": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/InputTransform" + } + }, + "use_latest_version": { + "type": "boolean" + } + } + } + }, + "flow_options_by_path": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "input_transforms": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/InputTransform" + } + }, + "use_latest_version": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "stream of created job uuids separated by \\n. Lines may start with 'Error:'", + "content": { + "text/event-stream": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/jobs/restart/f/{id}/from/{step_id}/{branch_or_iteration_n}": { "post": { "summary": "restart a completed flow at a given step", @@ -9426,6 +11088,9 @@ { "$ref": "#/components/parameters/ParentJob" }, + { + "$ref": "#/components/parameters/Worker" + }, { "$ref": "#/components/parameters/ScriptExactPath" }, @@ -9465,6 +11130,9 @@ { "$ref": "#/components/parameters/ResultFilter" }, + { + "$ref": "#/components/parameters/AllowWildcards" + }, { "$ref": "#/components/parameters/Tag" }, @@ -9587,10 +11255,213 @@ } } }, - "/w/{workspace}/jobs/queue/list_filtered_uuids": { + "/w/{workspace}/jobs/completed/count_jobs": { + "get": { + "summary": "count number of completed jobs with filter", + "operationId": "countCompletedJobs", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "name": "completed_after_s_ago", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "success", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "tags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "all_workspaces", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Count of completed jobs", + "content": { + "application/json": { + "schema": { + "type": "integer" + } + } + } + } + } + } + }, + "/w/{workspace}/jobs/list_filtered_uuids": { "get": { "summary": "get the ids of all jobs matching the given filters", - "operationId": "listFilteredUuids", + "operationId": "listFilteredJobsUuids", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/CreatedBy" + }, + { + "$ref": "#/components/parameters/Label" + }, + { + "$ref": "#/components/parameters/Worker" + }, + { + "$ref": "#/components/parameters/ParentJob" + }, + { + "$ref": "#/components/parameters/ScriptExactPath" + }, + { + "$ref": "#/components/parameters/ScriptStartPath" + }, + { + "$ref": "#/components/parameters/SchedulePath" + }, + { + "$ref": "#/components/parameters/ScriptExactHash" + }, + { + "$ref": "#/components/parameters/StartedBefore" + }, + { + "$ref": "#/components/parameters/StartedAfter" + }, + { + "$ref": "#/components/parameters/CreatedBefore" + }, + { + "$ref": "#/components/parameters/CreatedAfter" + }, + { + "$ref": "#/components/parameters/CreatedOrStartedBefore" + }, + { + "$ref": "#/components/parameters/Running" + }, + { + "$ref": "#/components/parameters/ScheduledForBeforeNow" + }, + { + "$ref": "#/components/parameters/CreatedOrStartedAfter" + }, + { + "$ref": "#/components/parameters/CreatedOrStartedAfterCompletedJob" + }, + { + "$ref": "#/components/parameters/JobKinds" + }, + { + "$ref": "#/components/parameters/Suspended" + }, + { + "$ref": "#/components/parameters/ArgsFilter" + }, + { + "$ref": "#/components/parameters/Tag" + }, + { + "$ref": "#/components/parameters/ResultFilter" + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "is_skipped", + "description": "is the job skipped", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "is_flow_step", + "description": "is the job a flow step", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "has_null_parent", + "description": "has null parent", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "success", + "description": "filter on successful jobs", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "all_workspaces", + "description": "get jobs from all workspaces (only valid if request come from the `admins` workspace)", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "is_not_schedule", + "description": "is not a scheduled job", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "uuids of jobs", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/w/{workspace}/jobs/queue/list_filtered_uuids": { + "get": { + "summary": "get the ids of all queued jobs matching the given filters", + "operationId": "listFilteredQueueUuids", "tags": [ "job" ], @@ -9646,6 +11517,9 @@ { "$ref": "#/components/parameters/ResultFilter" }, + { + "$ref": "#/components/parameters/AllowWildcards" + }, { "$ref": "#/components/parameters/Tag" }, @@ -9760,6 +11634,9 @@ { "$ref": "#/components/parameters/Label" }, + { + "$ref": "#/components/parameters/Worker" + }, { "$ref": "#/components/parameters/ParentJob" }, @@ -9793,6 +11670,9 @@ { "$ref": "#/components/parameters/ResultFilter" }, + { + "$ref": "#/components/parameters/AllowWildcards" + }, { "$ref": "#/components/parameters/Tag" }, @@ -9869,6 +11749,9 @@ { "$ref": "#/components/parameters/Label" }, + { + "$ref": "#/components/parameters/Worker" + }, { "$ref": "#/components/parameters/ParentJob" }, @@ -9926,6 +11809,9 @@ { "$ref": "#/components/parameters/ResultFilter" }, + { + "$ref": "#/components/parameters/AllowWildcards" + }, { "$ref": "#/components/parameters/Page" }, @@ -10294,7 +12180,9 @@ "description": "job log", "content": { "text/plain": { - "type": "string" + "schema": { + "type": "string" + } } } } @@ -10728,6 +12616,158 @@ } } }, + "/w/{workspace}/jobs/slack_approval/{id}": { + "get": { + "summary": "generate interactive slack approval for suspended job", + "operationId": "getSlackApprovalPayload", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/JobId" + }, + { + "name": "approver", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "message", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "slack_resource_path", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "channel_id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "flow_step_id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "default_args_json", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "dynamic_enums_json", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Interactive slack approval message sent successfully" + } + } + } + }, + "/w/{workspace}/jobs/teams_approval/{id}": { + "get": { + "summary": "generate interactive teams approval for suspended job", + "operationId": "getTeamsApprovalPayload", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/JobId" + }, + { + "name": "approver", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "message", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "team_name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "channel_name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "flow_step_id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "default_args_json", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "dynamic_enums_json", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Interactive slack approval message sent successfully" + } + } + } + }, "/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}": { "get": { "summary": "resume a job for a suspended flow", @@ -11172,6 +13212,9 @@ }, "timezone": { "type": "string" + }, + "cron_version": { + "type": "string" } }, "required": [ @@ -11823,10 +13866,15 @@ "delete", "patch" ] + }, + "trigger_path": { + "type": "string" + }, + "workspaced_route": { + "type": "boolean" } }, "required": [ - "kind", "route_path", "http_method" ] @@ -12118,6 +14166,2452 @@ } } }, + "/w/{workspace}/websocket_triggers/test": { + "post": { + "summary": "test websocket connection", + "operationId": "testWebsocketConnection", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test websocket connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "url_runnable_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "can_return_message": { + "type": "boolean" + } + }, + "required": [ + "url", + "can_return_message" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfuly connected to websocket", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/create": { + "post": { + "summary": "create kafka trigger", + "operationId": "createKafkaTrigger", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new kafka trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewKafkaTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "kafka trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/update/{path}": { + "post": { + "summary": "update kafka trigger", + "operationId": "updateKafkaTrigger", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditKafkaTrigger" + } + } + } + }, + "responses": { + "200": { + "description": "kafka trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/delete/{path}": { + "delete": { + "summary": "delete kafka trigger", + "operationId": "deleteKafkaTrigger", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "kafka trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/get/{path}": { + "get": { + "summary": "get kafka trigger", + "operationId": "getKafkaTrigger", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "kafka trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KafkaTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/list": { + "get": { + "summary": "list kafka triggers", + "operationId": "listKafkaTriggers", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "kafka trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KafkaTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/exists/{path}": { + "get": { + "summary": "does kafka trigger exists", + "operationId": "existsKafkaTrigger", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "kafka trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled kafka trigger", + "operationId": "setKafkaTriggerEnabled", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated kafka trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "kafka trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/kafka_triggers/test": { + "post": { + "summary": "test kafka connection", + "operationId": "testKafkaConnection", + "tags": [ + "kafka_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test kafka connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection": { + "type": "object" + } + }, + "required": [ + "connection" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfuly connected to kafka brokers", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/create": { + "post": { + "summary": "create nats trigger", + "operationId": "createNatsTrigger", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new nats trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewNatsTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "nats trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/update/{path}": { + "post": { + "summary": "update nats trigger", + "operationId": "updateNatsTrigger", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditNatsTrigger" + } + } + } + }, + "responses": { + "200": { + "description": "nats trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/delete/{path}": { + "delete": { + "summary": "delete nats trigger", + "operationId": "deleteNatsTrigger", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "nats trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/get/{path}": { + "get": { + "summary": "get nats trigger", + "operationId": "getNatsTrigger", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "nats trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatsTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/list": { + "get": { + "summary": "list nats triggers", + "operationId": "listNatsTriggers", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "nats trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NatsTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/exists/{path}": { + "get": { + "summary": "does nats trigger exists", + "operationId": "existsNatsTrigger", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "nats trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled nats trigger", + "operationId": "setNatsTriggerEnabled", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated nats trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "nats trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/nats_triggers/test": { + "post": { + "summary": "test NATS connection", + "operationId": "testNatsConnection", + "tags": [ + "nats_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test nats connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection": { + "type": "object" + } + }, + "required": [ + "connection" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfuly connected to NATS servers", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/create": { + "post": { + "summary": "create sqs trigger", + "operationId": "createSqsTrigger", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new sqs trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewSqsTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "sqs trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/update/{path}": { + "post": { + "summary": "update sqs trigger", + "operationId": "updateSqsTrigger", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditSqsTrigger" + } + } + } + }, + "responses": { + "200": { + "description": "sqs trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/delete/{path}": { + "delete": { + "summary": "delete sqs trigger", + "operationId": "deleteSqsTrigger", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "sqs trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/get/{path}": { + "get": { + "summary": "get sqs trigger", + "operationId": "getSqsTrigger", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "sqs trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqsTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/list": { + "get": { + "summary": "list sqs triggers", + "operationId": "listSqsTriggers", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "sqs trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SqsTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/exists/{path}": { + "get": { + "summary": "does sqs trigger exists", + "operationId": "existsSqsTrigger", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "sqs trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled sqs trigger", + "operationId": "setSqsTriggerEnabled", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated sqs trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "sqs trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/sqs_triggers/test": { + "post": { + "summary": "test sqs connection", + "operationId": "testSqsConnection", + "tags": [ + "sqs_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test sqs connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection": { + "type": "object" + } + }, + "required": [ + "connection" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfuly connected to sqs", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/create": { + "post": { + "summary": "create mqtt trigger", + "operationId": "createMqttTrigger", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new mqtt trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewMqttTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "mqtt trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/update/{path}": { + "post": { + "summary": "update mqtt trigger", + "operationId": "updateMqttTrigger", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditMqttTrigger" + } + } + } + }, + "responses": { + "200": { + "description": "mqtt trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/delete/{path}": { + "delete": { + "summary": "delete mqtt trigger", + "operationId": "deleteMqttTrigger", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "mqtt trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/get/{path}": { + "get": { + "summary": "get mqtt trigger", + "operationId": "getMqttTrigger", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "mqtt trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MqttTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/list": { + "get": { + "summary": "list mqtt triggers", + "operationId": "listMqttTriggers", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "mqtt trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MqttTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/exists/{path}": { + "get": { + "summary": "does mqtt trigger exists", + "operationId": "existsMqttTrigger", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "mqtt trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled mqtt trigger", + "operationId": "setMqttTriggerEnabled", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated mqtt trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "mqtt trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/mqtt_triggers/test": { + "post": { + "summary": "test mqtt connection", + "operationId": "testMqttConnection", + "tags": [ + "mqtt_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test mqtt connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection": { + "type": "object" + } + }, + "required": [ + "connection" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfully connected to mqtt", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/create": { + "post": { + "summary": "create gcp trigger", + "operationId": "createGcpTrigger", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new gcp trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GcpTriggerData" + } + } + } + }, + "responses": { + "201": { + "description": "gcp trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/update/{path}": { + "post": { + "summary": "update gcp trigger", + "operationId": "updateGcpTrigger", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GcpTriggerData" + } + } + } + }, + "responses": { + "200": { + "description": "gcp trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/delete/{path}": { + "delete": { + "summary": "delete gcp trigger", + "operationId": "deleteGcpTrigger", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "gcp trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/get/{path}": { + "get": { + "summary": "get gcp trigger", + "operationId": "getGcpTrigger", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "gcp trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GcpTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/list": { + "get": { + "summary": "list gcp triggers", + "operationId": "listGcpTriggers", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "gcp trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GcpTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/exists/{path}": { + "get": { + "summary": "does gcp trigger exists", + "operationId": "existsGcpTrigger", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "gcp trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled gcp trigger", + "operationId": "setGcpTriggerEnabled", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated gcp trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "gcp trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/test": { + "post": { + "summary": "test gcp connection", + "operationId": "testGcpConnection", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test gcp connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection": { + "type": "object" + } + }, + "required": [ + "connection" + ] + } + } + } + }, + "responses": { + "200": { + "description": "try to connect to a gcp broker", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/subscriptions/delete/{path}": { + "delete": { + "summary": "delete gcp trigger", + "operationId": "deleteGcpSubscription", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "args to delete subscription from google cloud", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteGcpSubscription" + } + } + } + }, + "responses": { + "200": { + "description": "gcp trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/topics/list/{path}": { + "get": { + "summary": "list all topics of google cloud service", + "operationId": "listGoogleTopics", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "get all google topics", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/w/{workspace}/gcp_triggers/subscriptions/list/{path}": { + "post": { + "summary": "list all subscription of a give topic from google cloud service", + "operationId": "listAllTGoogleTopicSubscriptions", + "tags": [ + "gcp_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "args to get subscription's topic from google cloud", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAllTopicSubscription" + } + } + } + }, + "responses": { + "200": { + "description": "get all google topic subscriptions name", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}": { + "get": { + "summary": "check if postgres configuration is set to logical", + "operationId": "isValidPostgresConfiguration", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "boolean that indicates if postgres is set to logical level or not", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/create_template_script": { + "post": { + "summary": "create template script", + "operationId": "createTemplateScript", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "template script", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateScript" + } + } + } + }, + "responses": { + "200": { + "description": "custom id to retrieve template script", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/get_template_script/{id}": { + "get": { + "summary": "get template script", + "operationId": "getTemplateScript", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "description": "template script", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/slot/list/{path}": { + "get": { + "summary": "list postgres replication slot", + "operationId": "listPostgresReplicationSlot", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "list postgres slot", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SlotList" + } + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/slot/create/{path}": { + "post": { + "summary": "create replication slot for postgres", + "operationId": "createPostgresReplicationSlot", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "new slot for postgres", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Slot" + } + } + } + }, + "responses": { + "201": { + "description": "slot created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/slot/delete/{path}": { + "delete": { + "summary": "delete postgres replication slot", + "operationId": "deletePostgresReplicationSlot", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "replication slot of postgres", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Slot" + } + } + } + }, + "responses": { + "200": { + "description": "postgres replication slot deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/publication/list/{path}": { + "get": { + "summary": "list postgres publication", + "operationId": "listPostgresPublication", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "database publication list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/publication/get/{publication}/{path}": { + "get": { + "summary": "get postgres publication", + "operationId": "getPostgresPublication", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "$ref": "#/components/parameters/PublicationName" + } + ], + "responses": { + "200": { + "description": "postgres publication get", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicationData" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/publication/create/{publication}/{path}": { + "post": { + "summary": "create publication for postgres", + "operationId": "createPostgresPublication", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "$ref": "#/components/parameters/PublicationName" + } + ], + "requestBody": { + "description": "new publication for postgres", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicationData" + } + } + } + }, + "responses": { + "201": { + "description": "publication created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/publication/update/{publication}/{path}": { + "post": { + "summary": "update publication for postgres", + "operationId": "updatePostgresPublication", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "$ref": "#/components/parameters/PublicationName" + } + ], + "requestBody": { + "description": "update publication for postgres", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicationData" + } + } + } + }, + "responses": { + "201": { + "description": "publication updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/publication/delete/{publication}/{path}": { + "delete": { + "summary": "delete postgres publication", + "operationId": "deletePostgresPublication", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "$ref": "#/components/parameters/PublicationName" + } + ], + "responses": { + "200": { + "description": "postgres publication deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/create": { + "post": { + "summary": "create postgres trigger", + "operationId": "createPostgresTrigger", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new postgres trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewPostgresTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "postgres trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/update/{path}": { + "post": { + "summary": "update postgres trigger", + "operationId": "updatePostgresTrigger", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditPostgresTrigger" + } + } + } + }, + "responses": { + "200": { + "description": "postgres trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/delete/{path}": { + "delete": { + "summary": "delete postgres trigger", + "operationId": "deletePostgresTrigger", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "postgres trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/get/{path}": { + "get": { + "summary": "get postgres trigger", + "operationId": "getPostgresTrigger", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "get postgres trigger", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostgresTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/list": { + "get": { + "summary": "list postgres triggers", + "operationId": "listPostgresTriggers", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "postgres trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PostgresTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/exists/{path}": { + "get": { + "summary": "does postgres trigger exists", + "operationId": "existsPostgresTrigger", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "postgres trigger exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled postgres trigger", + "operationId": "setPostgresTriggerEnabled", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated postgres trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "postgres trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/postgres_triggers/test": { + "post": { + "summary": "test postgres connection", + "operationId": "testPostgresConnection", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "test postgres connection", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "database": { + "type": "string" + } + }, + "required": [ + "database" + ] + } + } + } + }, + "responses": { + "200": { + "description": "successfuly connected to postgres", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/groups/list": { "get": { "summary": "list instance groups", @@ -12989,6 +17483,35 @@ } } }, + "/w/{workspace}/folders/exists/{name}": { + "get": { + "summary": "exists folder", + "operationId": "existsFolder", + "tags": [ + "folder" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Name" + } + ], + "responses": { + "200": { + "description": "folder exists", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, "/w/{workspace}/folders/getusage/{name}": { "get": { "summary": "get folder usage", @@ -13343,7 +17866,9 @@ "description": "a config", "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/Configs" + } } } } @@ -13467,6 +17992,81 @@ } } }, + "/configs/list_available_python_versions": { + "get": { + "summary": "Get currently available python versions provided by UV.", + "operationId": "listAvailablePythonVersions", + "tags": [ + "config" + ], + "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", + "operationId": "createAgentToken", + "tags": [ + "agent_workers" + ], + "requestBody": { + "description": "agent token", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "worker_group": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "exp": { + "type": "integer" + } + }, + "required": [ + "worker_group", + "tags", + "exp" + ] + } + } + } + }, + "responses": { + "200": { + "description": "agent token created", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/acls/get/{kind}/{path}": { "get": { "summary": "get granular acls", @@ -13498,7 +18098,13 @@ "app", "raw_app", "http_trigger", - "websocket_trigger" + "websocket_trigger", + "kafka_trigger", + "nats_trigger", + "postgres_trigger", + "mqtt_trigger", + "gcp_trigger", + "sqs_trigger" ] } } @@ -13551,7 +18157,13 @@ "app", "raw_app", "http_trigger", - "websocket_trigger" + "websocket_trigger", + "kafka_trigger", + "nats_trigger", + "postgres_trigger", + "mqtt_trigger", + "gcp_trigger", + "sqs_trigger" ] } } @@ -13623,7 +18235,13 @@ "app", "raw_app", "http_trigger", - "websocket_trigger" + "websocket_trigger", + "kafka_trigger", + "nats_trigger", + "postgres_trigger", + "mqtt_trigger", + "gcp_trigger", + "sqs_trigger" ] } } @@ -13661,32 +18279,66 @@ } } }, - "/w/{workspace}/capture_u/{path}": { + "/w/{workspace}/capture/set_config": { "post": { - "summary": "update flow preview capture", - "operationId": "updateCapture", + "summary": "set capture config", + "operationId": "setCaptureConfig", "tags": [ "capture" ], "parameters": [ { "$ref": "#/components/parameters/WorkspaceId" - }, - { - "$ref": "#/components/parameters/Path" } ], + "requestBody": { + "description": "capture config", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "trigger_kind": { + "$ref": "#/components/schemas/CaptureTriggerKind" + }, + "path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "trigger_config": { + "type": "object" + } + }, + "required": [ + "trigger_kind", + "path", + "is_flow" + ] + } + } + } + }, "responses": { - "204": { - "description": "flow preview captured" + "200": { + "description": "capture config set", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } } } } }, - "/w/{workspace}/capture/{path}": { - "put": { - "summary": "create flow preview capture", - "operationId": "createCapture", + "/w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}": { + "post": { + "summary": "ping capture config", + "operationId": "pingCaptureConfig", "tags": [ "capture" ], @@ -13694,18 +18346,162 @@ { "$ref": "#/components/parameters/WorkspaceId" }, + { + "name": "trigger_kind", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/CaptureTriggerKind" + } + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, { "$ref": "#/components/parameters/Path" } ], "responses": { - "201": { - "description": "flow preview capture created" + "200": { + "description": "capture config pinged" } } - }, + } + }, + "/w/{workspace}/capture/get_configs/{runnable_kind}/{path}": { "get": { - "summary": "get flow preview capture", + "summary": "get capture configs for a script or flow", + "operationId": "getCaptureConfigs", + "tags": [ + "capture" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "capture configs for a script or flow", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptureConfig" + } + } + } + } + } + } + } + }, + "/w/{workspace}/capture/list/{runnable_kind}/{path}": { + "get": { + "summary": "list captures for a script or flow", + "operationId": "listCaptures", + "tags": [ + "capture" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, + { + "$ref": "#/components/parameters/Path" + }, + { + "name": "trigger_kind", + "in": "query", + "schema": { + "$ref": "#/components/schemas/CaptureTriggerKind" + } + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + } + ], + "responses": { + "200": { + "description": "list of captures for a script or flow", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Capture" + } + } + } + } + } + } + } + }, + "/w/{workspace}/capture/move/{runnable_kind}/{path}": { + "post": { + "summary": "move captures and configs for a script or flow", + "operationId": "moveCapturesAndConfigs", + "tags": [ + "capture" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/RunnableKind" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "move captures and configs to a new path", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_path": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "captures and configs moved", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/capture/{id}": { + "get": { + "summary": "get a capture", "operationId": "getCapture", "tags": [ "capture" @@ -13715,20 +18511,49 @@ "$ref": "#/components/parameters/WorkspaceId" }, { - "$ref": "#/components/parameters/Path" + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } } ], "responses": { "200": { - "description": "captured flow preview", + "description": "capture", "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/Capture" + } } } + } + } + }, + "delete": { + "summary": "delete a capture", + "operationId": "deleteCapture", + "tags": [ + "capture" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" }, - "404": { - "description": "capture does not exist for this flow" + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "capture deleted" } } } @@ -13839,6 +18664,16 @@ }, { "$ref": "#/components/parameters/PerPage" + }, + { + "$ref": "#/components/parameters/ArgsFilter" + }, + { + "name": "include_preview", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -14142,6 +18977,9 @@ "properties": { "connection_settings_str": { "type": "string" + }, + "azure_container_path": { + "type": "string" } }, "required": [ @@ -14994,7 +19832,7 @@ }, "/w/{workspace}/job_helpers/download_s3_file": { "get": { - "summary": "Download file to S3 bucket", + "summary": "Download file from S3 bucket", "operationId": "fileDownload", "tags": [ "helpers" @@ -15508,6 +20346,9 @@ { "$ref": "#/components/parameters/ResultFilter" }, + { + "$ref": "#/components/parameters/AllowWildcards" + }, { "$ref": "#/components/parameters/Page" }, @@ -15595,6 +20436,14 @@ "schema": { "type": "string" } + }, + { + "name": "pagination_offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } } ], "responses": { @@ -15609,12 +20458,7 @@ "description": "a list of the terms that couldn't be parsed (and thus ignored)", "type": "array", "items": { - "type": "object", - "properties": { - "dancer": { - "type": "string" - } - } + "type": "string" } }, "hits": { @@ -15623,6 +20467,25 @@ "items": { "$ref": "#/components/schemas/JobSearchHit" } + }, + "hit_count": { + "description": "how many jobs matched in total", + "type": "number" + }, + "index_metadata": { + "description": "Metadata about the index current state", + "type": "object", + "properties": { + "indexed_until": { + "description": "Datetime of the most recently indexed job", + "type": "string", + "format": "date-time" + }, + "lost_lock_ownership": { + "description": "Is the current indexer service being replaced", + "type": "boolean" + } + } } } } @@ -15737,14 +20600,6 @@ "type": "string" } }, - { - "name": "hosts", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, { "name": "min_ts", "in": "query", @@ -15790,6 +20645,41 @@ } } } + }, + "/srch/index/delete/{idx_name}": { + "delete": { + "summary": "Restart container and delete the index to recreate it.", + "operationId": "clearIndex", + "tags": [ + "indexSearch" + ], + "parameters": [ + { + "name": "idx_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "JobIndex", + "ServiceLogIndex" + ] + } + } + ], + "responses": { + "200": { + "description": "idx to be deleted and container restarting", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } } }, "components": { @@ -15805,6 +20695,14 @@ } }, "parameters": { + "Id": { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, "Key": { "name": "key", "in": "path", @@ -15821,6 +20719,14 @@ "type": "string" } }, + "PublicationName": { + "name": "publication", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, "VersionId": { "name": "version", "in": "path", @@ -15886,6 +20792,14 @@ "type": "string" } }, + "CustomPath": { + "name": "custom_path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, "PathId": { "name": "id", "in": "path", @@ -15950,6 +20864,14 @@ "type": "string" } }, + "Worker": { + "name": "worker", + "description": "worker this job was ran on", + "in": "query", + "schema": { + "type": "string" + } + }, "ParentJob": { "name": "parent_job", "description": "The parent job that is at the origin and responsible for the execution of this script if any", @@ -16144,6 +21066,14 @@ "type": "boolean" } }, + "AllowWildcards": { + "name": "allow_wildcards", + "description": "allow wildcards (*) in the filter of label, tag, worker", + "in": "query", + "schema": { + "type": "boolean" + } + }, "ArgsFilter": { "name": "args", "description": "filter on jobs containing those args as a json subset (@> in postgres)", @@ -16259,24 +21189,135 @@ "schema": { "type": "string" } + }, + "RunnableKind": { + "name": "runnable_kind", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "script", + "flow" + ] + } } }, "schemas": { - "AiResource": { + "InputTransform": { + "allOf": [ + { + "$ref": "#/components/schemas/schemas-InputTransform" + } + ] + }, + "AIProvider": { + "type": "string", + "enum": [ + "openai", + "azure_openai", + "anthropic", + "mistral", + "deepseek", + "googleai", + "groq", + "openrouter", + "togetherai", + "customai" + ] + }, + "AIProviderModel": { "type": "object", "properties": { - "path": { + "model": { "type": "string" }, "provider": { - "type": "string" + "$ref": "#/components/schemas/AIProvider" } }, "required": [ - "path", + "model", "provider" ] }, + "AIProviderConfig": { + "type": "object", + "properties": { + "resource_path": { + "type": "string" + }, + "models": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "resource_path", + "models" + ] + }, + "AIConfig": { + "type": "object", + "properties": { + "providers": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AIProviderConfig" + } + }, + "default_model": { + "$ref": "#/components/schemas/AIProviderModel" + }, + "code_completion_model": { + "$ref": "#/components/schemas/AIProviderModel" + } + } + }, + "Alert": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tags_to_monitor": { + "type": "array", + "items": { + "type": "string" + } + }, + "jobs_num_threshold": { + "type": "integer" + }, + "alert_cooldown_seconds": { + "type": "integer" + }, + "alert_time_threshold_seconds": { + "type": "integer" + } + }, + "required": [ + "name", + "tags_to_monitor", + "jobs_num_threshold", + "alert_cooldown_seconds", + "alert_time_threshold_seconds" + ] + }, + "Configs": { + "type": "object", + "nullable": true, + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Alert" + } + } + } + }, "Script": { "type": "object", "properties": { @@ -16337,25 +21378,7 @@ "type": "string" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" }, "kind": { "type": "string", @@ -16364,7 +21387,8 @@ "failure", "trigger", "command", - "approval" + "approval", + "preprocessor" ] }, "starred": { @@ -16426,6 +21450,9 @@ }, "has_preprocessor": { "type": "boolean" + }, + "on_behalf_of_email": { + "type": "string" } }, "required": [ @@ -16475,25 +21502,7 @@ "type": "string" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" }, "kind": { "type": "string", @@ -16502,7 +21511,8 @@ "failure", "trigger", "command", - "approval" + "approval", + "preprocessor" ] }, "tag": { @@ -16561,6 +21571,9 @@ }, "has_preprocessor": { "type": "boolean" + }, + "on_behalf_of_email": { + "type": "string" } }, "required": [ @@ -16758,7 +21771,10 @@ "script_hub", "identity", "deploymentcallback", - "singlescriptflow" + "singlescriptflow", + "flowscript", + "flownode", + "appscript" ] }, "schedule_path": { @@ -16766,7 +21782,7 @@ }, "permissioned_as": { "type": "string", - "description": "The user (u/userfoo) or group (g/groupfoo) whom \nthe execution of this script will be permissioned_as and by extension its DT_TOKEN.\n" + "description": "The user (u/userfoo) or group (g/groupfoo) whom\nthe execution of this script will be permissioned_as and by extension its DT_TOKEN.\n" }, "flow_status": { "$ref": "#/components/schemas/FlowStatus" @@ -16778,25 +21794,7 @@ "type": "boolean" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" }, "email": { "type": "string" @@ -16821,6 +21819,12 @@ }, "suspend": { "type": "number" + }, + "preprocessed": { + "type": "boolean" + }, + "worker": { + "type": "string" } }, "required": [ @@ -16907,7 +21911,10 @@ "script_hub", "identity", "deploymentcallback", - "singlescriptflow" + "singlescriptflow", + "flowscript", + "flownode", + "appscript" ] }, "schedule_path": { @@ -16915,7 +21922,7 @@ }, "permissioned_as": { "type": "string", - "description": "The user (u/userfoo) or group (g/groupfoo) whom \nthe execution of this script will be permissioned_as and by extension its DT_TOKEN.\n" + "description": "The user (u/userfoo) or group (g/groupfoo) whom\nthe execution of this script will be permissioned_as and by extension its DT_TOKEN.\n" }, "flow_status": { "$ref": "#/components/schemas/FlowStatus" @@ -16927,25 +21934,7 @@ "type": "boolean" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" }, "is_skipped": { "type": "boolean" @@ -16976,6 +21965,12 @@ }, "aggregate_wait_time_ms": { "type": "number" + }, + "preprocessed": { + "type": "boolean" + }, + "worker": { + "type": "string" } }, "required": [ @@ -17065,6 +22060,9 @@ "is_admin": { "type": "boolean" }, + "name": { + "type": "string" + }, "is_super_admin": { "type": "boolean" }, @@ -17670,6 +22668,31 @@ "has_preprocessor" ] }, + "ScriptLang": { + "type": "string", + "enum": [ + "python3", + "deno", + "go", + "bash", + "powershell", + "postgresql", + "mysql", + "bigquery", + "snowflake", + "mssql", + "oracledb", + "graphql", + "nativets", + "bun", + "php", + "rust", + "ansible", + "csharp", + "nu", + "java" + ] + }, "Preview": { "type": "object", "properties": { @@ -17679,29 +22702,14 @@ "path": { "type": "string" }, + "script_hash": { + "type": "string" + }, "args": { "$ref": "#/components/schemas/ScriptArgs" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" }, "tag": { "type": "string" @@ -18004,6 +23012,9 @@ "summary": { "type": "string" }, + "description": { + "type": "string" + }, "no_flow_overlap": { "type": "boolean" }, @@ -18013,6 +23024,9 @@ "paused_until": { "type": "string", "format": "date-time" + }, + "cron_version": { + "type": "string" } }, "required": [ @@ -18125,12 +23139,18 @@ "summary": { "type": "string" }, + "description": { + "type": "string" + }, "tag": { "type": "string" }, "paused_until": { "type": "string", "format": "date-time" + }, + "cron_version": { + "type": "string" } }, "required": [ @@ -18193,12 +23213,18 @@ "summary": { "type": "string" }, + "description": { + "type": "string" + }, "tag": { "type": "string" }, "paused_until": { "type": "string", "format": "date-time" + }, + "cron_version": { + "type": "string" } }, "required": [ @@ -18209,12 +23235,27 @@ "args" ] }, - "HttpTrigger": { + "TriggerExtraProperty": { "type": "object", "properties": { "path": { "type": "string" }, + "script_path": { + "type": "string" + }, + "email": { + "type": "string" + }, + "extra_perms": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "workspace_id": { + "type": "string" + }, "edited_by": { "type": "string" }, @@ -18222,9 +23263,40 @@ "type": "string", "format": "date-time" }, - "script_path": { - "type": "string" - }, + "is_flow": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "email", + "extra_perms", + "workspace_id", + "edited_by", + "edited_at", + "is_flow" + ] + }, + "AuthenticationMethod": { + "type": "string", + "enum": [ + "none", + "windmill", + "api_key", + "basic_http", + "custom_script", + "signature" + ] + }, + "HttpTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { "route_path": { "type": "string" }, @@ -18245,21 +23317,6 @@ "s3" ] }, - "is_flow": { - "type": "boolean" - }, - "extra_perms": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, - "email": { - "type": "string" - }, - "workspace_id": { - "type": "string" - }, "http_method": { "type": "string", "enum": [ @@ -18270,26 +23327,37 @@ "patch" ] }, + "authentication_resource_path": { + "type": "string" + }, "is_async": { "type": "boolean" }, - "requires_auth": { + "authentication_method": { + "$ref": "#/components/schemas/AuthenticationMethod" + }, + "is_static_website": { + "type": "boolean" + }, + "workspaced_route": { + "type": "boolean" + }, + "wrap_body": { + "type": "boolean" + }, + "raw_string": { "type": "boolean" } }, "required": [ - "path", - "edited_by", - "edited_at", - "script_path", "route_path", - "extra_perms", - "is_flow", - "email", - "workspace_id", "is_async", - "requires_auth", - "http_method" + "authentication_method", + "http_method", + "is_static_website", + "workspaced_route", + "wrap_body", + "raw_string" ] }, "NewHttpTrigger": { @@ -18304,6 +23372,9 @@ "route_path": { "type": "string" }, + "workspaced_route": { + "type": "boolean" + }, "static_asset_config": { "type": "object", "properties": { @@ -18334,10 +23405,22 @@ "patch" ] }, + "authentication_resource_path": { + "type": "string" + }, "is_async": { "type": "boolean" }, - "requires_auth": { + "authentication_method": { + "$ref": "#/components/schemas/AuthenticationMethod" + }, + "is_static_website": { + "type": "boolean" + }, + "wrap_body": { + "type": "boolean" + }, + "raw_string": { "type": "boolean" } }, @@ -18347,8 +23430,9 @@ "route_path", "is_flow", "is_async", - "requires_auth", - "http_method" + "authentication_method", + "http_method", + "is_static_website" ] }, "EditHttpTrigger": { @@ -18363,6 +23447,9 @@ "route_path": { "type": "string" }, + "workspaced_route": { + "type": "boolean" + }, "static_asset_config": { "type": "object", "properties": { @@ -18380,6 +23467,9 @@ "s3" ] }, + "authentication_resource_path": { + "type": "string" + }, "is_flow": { "type": "boolean" }, @@ -18396,7 +23486,16 @@ "is_async": { "type": "boolean" }, - "requires_auth": { + "authentication_method": { + "$ref": "#/components/schemas/AuthenticationMethod" + }, + "is_static_website": { + "type": "boolean" + }, + "wrap_body": { + "type": "boolean" + }, + "raw_string": { "type": "boolean" } }, @@ -18406,8 +23505,9 @@ "is_flow", "kind", "is_async", - "requires_auth", - "http_method" + "authentication_method", + "http_method", + "is_static_website" ] }, "TriggersCount": { @@ -18435,43 +23535,38 @@ }, "websocket_count": { "type": "number" + }, + "postgres_count": { + "type": "number" + }, + "kafka_count": { + "type": "number" + }, + "nats_count": { + "type": "number" + }, + "mqtt_count": { + "type": "number" + }, + "gcp_count": { + "type": "number" + }, + "sqs_count": { + "type": "number" } } }, "WebsocketTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], "type": "object", "properties": { - "path": { - "type": "string" - }, - "edited_by": { - "type": "string" - }, - "edited_at": { - "type": "string", - "format": "date-time" - }, - "script_path": { - "type": "string" - }, "url": { "type": "string" }, - "is_flow": { - "type": "boolean" - }, - "extra_perms": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, - "email": { - "type": "string" - }, - "workspace_id": { - "type": "string" - }, "server_id": { "type": "string" }, @@ -18509,22 +23604,16 @@ }, "url_runnable_args": { "$ref": "#/components/schemas/ScriptArgs" + }, + "can_return_message": { + "type": "boolean" } }, "required": [ - "path", - "edited_by", - "edited_at", - "script_path", "url", - "extra_perms", - "is_flow", - "email", - "workspace_id", "enabled", "filters", - "initial_messages", - "url_runnable_args" + "can_return_message" ] }, "NewWebsocketTrigger": { @@ -18569,6 +23658,9 @@ }, "url_runnable_args": { "$ref": "#/components/schemas/ScriptArgs" + }, + "can_return_message": { + "type": "boolean" } }, "required": [ @@ -18577,8 +23669,7 @@ "url", "is_flow", "filters", - "initial_messages", - "url_runnable_args" + "can_return_message" ] }, "EditWebsocketTrigger": { @@ -18620,6 +23711,9 @@ }, "url_runnable_args": { "$ref": "#/components/schemas/ScriptArgs" + }, + "can_return_message": { + "type": "boolean" } }, "required": [ @@ -18628,8 +23722,7 @@ "url", "is_flow", "filters", - "initial_messages", - "url_runnable_args" + "can_return_message" ] }, "WebsocketTriggerInitialMessage": { @@ -18674,6 +23767,936 @@ } ] }, + "MqttQoS": { + "type": "string", + "enum": [ + "qos0", + "qos1", + "qos2" + ] + }, + "MqttV3Config": { + "type": "object", + "properties": { + "clean_session": { + "type": "boolean" + } + } + }, + "MqttV5Config": { + "type": "object", + "properties": { + "clean_start": { + "type": "boolean" + }, + "topic_alias": { + "type": "number" + }, + "session_expiry_interval": { + "type": "number" + } + } + }, + "MqttSubscribeTopic": { + "type": "object", + "properties": { + "qos": { + "$ref": "#/components/schemas/MqttQoS" + }, + "topic": { + "type": "string" + } + }, + "required": [ + "qos", + "topic" + ] + }, + "MqttClientVersion": { + "type": "string", + "enum": [ + "v3", + "v5" + ] + }, + "MqttTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "mqtt_resource_path": { + "type": "string" + }, + "subscribe_topics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MqttSubscribeTopic" + } + }, + "v3_config": { + "$ref": "#/components/schemas/MqttV3Config" + }, + "v5_config": { + "$ref": "#/components/schemas/MqttV5Config" + }, + "client_id": { + "type": "string" + }, + "client_version": { + "$ref": "#/components/schemas/MqttClientVersion" + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "subscribe_topics", + "mqtt_resource_path" + ] + }, + "NewMqttTrigger": { + "type": "object", + "properties": { + "mqtt_resource_path": { + "type": "string" + }, + "subscribe_topics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MqttSubscribeTopic" + } + }, + "client_id": { + "type": "string" + }, + "v3_config": { + "$ref": "#/components/schemas/MqttV3Config" + }, + "v5_config": { + "$ref": "#/components/schemas/MqttV5Config" + }, + "client_version": { + "$ref": "#/components/schemas/MqttClientVersion" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "subscribe_topics", + "mqtt_resource_path" + ] + }, + "EditMqttTrigger": { + "type": "object", + "properties": { + "mqtt_resource_path": { + "type": "string" + }, + "subscribe_topics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MqttSubscribeTopic" + } + }, + "client_id": { + "type": "string" + }, + "v3_config": { + "$ref": "#/components/schemas/MqttV3Config" + }, + "v5_config": { + "$ref": "#/components/schemas/MqttV5Config" + }, + "client_version": { + "$ref": "#/components/schemas/MqttClientVersion" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "enabled", + "subscribe_topics", + "mqtt_resource_path" + ] + }, + "DeliveryType": { + "type": "string", + "enum": [ + "push", + "pull" + ] + }, + "PushConfig": { + "type": "object", + "properties": { + "audience": { + "type": "string" + }, + "authenticate": { + "type": "boolean" + } + }, + "required": [ + "authenticate", + "base_endpoint" + ] + }, + "GcpTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "gcp_resource_path": { + "type": "string" + }, + "topic_id": { + "type": "string" + }, + "subscription_id": { + "type": "string" + }, + "server_id": { + "type": "string" + }, + "delivery_type": { + "$ref": "#/components/schemas/DeliveryType" + }, + "delivery_config": { + "$ref": "#/components/schemas/PushConfig" + }, + "subscription_mode": { + "$ref": "#/components/schemas/SubscriptionMode" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "gcp_resource_path", + "topic_id", + "subscription_id", + "enabled", + "delivery_type", + "subscription_mode" + ] + }, + "SubscriptionMode": { + "type": "string", + "enum": [ + "existing", + "create_update" + ], + "description": "The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription." + }, + "GcpTriggerData": { + "type": "object", + "properties": { + "gcp_resource_path": { + "type": "string" + }, + "subscription_mode": { + "$ref": "#/components/schemas/SubscriptionMode" + }, + "topic_id": { + "type": "string" + }, + "subscription_id": { + "type": "string" + }, + "base_endpoint": { + "type": "string" + }, + "delivery_type": { + "$ref": "#/components/schemas/DeliveryType" + }, + "delivery_config": { + "$ref": "#/components/schemas/PushConfig" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "gcp_resource_path", + "topic_id", + "subscription_mode" + ] + }, + "GetAllTopicSubscription": { + "type": "object", + "properties": { + "topic_id": { + "type": "string" + } + }, + "required": [ + "topic_id" + ] + }, + "DeleteGcpSubscription": { + "type": "object", + "properties": { + "subscription_id": { + "type": "string" + } + }, + "required": [ + "subscription_id" + ] + }, + "AwsAuthResourceType": { + "type": "string", + "enum": [ + "oidc", + "credentials" + ] + }, + "SqsTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "queue_url": { + "type": "string" + }, + "aws_auth_resource_type": { + "$ref": "#/components/schemas/AwsAuthResourceType" + }, + "aws_resource_path": { + "type": "string" + }, + "message_attributes": { + "type": "array", + "items": { + "type": "string" + } + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "queue_url", + "aws_resource_path", + "enabled", + "aws_auth_resource_type" + ] + }, + "NewSqsTrigger": { + "type": "object", + "properties": { + "queue_url": { + "type": "string" + }, + "aws_auth_resource_type": { + "$ref": "#/components/schemas/AwsAuthResourceType" + }, + "aws_resource_path": { + "type": "string" + }, + "message_attributes": { + "type": "array", + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "queue_url", + "aws_resource_path", + "path", + "script_path", + "is_flow", + "aws_auth_resource_type" + ] + }, + "EditSqsTrigger": { + "type": "object", + "properties": { + "queue_url": { + "type": "string" + }, + "aws_auth_resource_type": { + "$ref": "#/components/schemas/AwsAuthResourceType" + }, + "aws_resource_path": { + "type": "string" + }, + "message_attributes": { + "type": "array", + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "queue_url", + "aws_resource_path", + "path", + "script_path", + "is_flow", + "enabled", + "aws_auth_resource_type" + ] + }, + "Slot": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "SlotList": { + "type": "object", + "properties": { + "slot_name": { + "type": "string" + }, + "active": { + "type": "boolean" + } + } + }, + "PublicationData": { + "type": "object", + "properties": { + "table_to_track": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relations" + } + }, + "transaction_to_track": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "transaction_to_track" + ] + }, + "TableToTrack": { + "type": "array", + "items": { + "type": "object", + "properties": { + "table_name": { + "type": "string" + }, + "columns_name": { + "type": "array", + "items": { + "type": "string" + } + }, + "where_clause": { + "type": "string" + } + }, + "required": [ + "table_name" + ] + } + }, + "Relations": { + "type": "object", + "properties": { + "schema_name": { + "type": "string" + }, + "table_to_track": { + "$ref": "#/components/schemas/TableToTrack" + } + }, + "required": [ + "schema_name", + "table_to_track" + ] + }, + "Language": { + "type": "string", + "enum": [ + "Typescript" + ] + }, + "TemplateScript": { + "type": "object", + "properties": { + "postgres_resource_path": { + "type": "string" + }, + "relations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relations" + } + }, + "language": { + "$ref": "#/components/schemas/Language" + } + }, + "required": [ + "postgres_resource_path", + "relations", + "language" + ] + }, + "PostgresTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "postgres_resource_path": { + "type": "string" + }, + "publication_name": { + "type": "string" + }, + "server_id": { + "type": "string" + }, + "replication_slot_name": { + "type": "string" + }, + "error": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "enabled", + "postgres_resource_path", + "replication_slot_name", + "publication_name" + ] + }, + "NewPostgresTrigger": { + "type": "object", + "properties": { + "replication_slot_name": { + "type": "string" + }, + "publication_name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "postgres_resource_path": { + "type": "string" + }, + "publication": { + "$ref": "#/components/schemas/PublicationData" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "enabled", + "postgres_resource_path" + ] + }, + "EditPostgresTrigger": { + "type": "object", + "properties": { + "replication_slot_name": { + "type": "string" + }, + "publication_name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "postgres_resource_path": { + "type": "string" + }, + "publication": { + "$ref": "#/components/schemas/PublicationData" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "enabled", + "postgres_resource_path", + "publication_name", + "replication_slot_name" + ] + }, + "KafkaTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "kafka_resource_path": { + "type": "string" + }, + "group_id": { + "type": "string" + }, + "topics": { + "type": "array", + "items": { + "type": "string" + } + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "kafka_resource_path", + "group_id", + "topics", + "enabled" + ] + }, + "NewKafkaTrigger": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "kafka_resource_path": { + "type": "string" + }, + "group_id": { + "type": "string" + }, + "topics": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "kafka_resource_path", + "group_id", + "topics" + ] + }, + "EditKafkaTrigger": { + "type": "object", + "properties": { + "kafka_resource_path": { + "type": "string" + }, + "group_id": { + "type": "string" + }, + "topics": { + "type": "array", + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "kafka_resource_path", + "group_id", + "topics", + "is_flow" + ] + }, + "NatsTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "properties": { + "nats_resource_path": { + "type": "string" + }, + "use_jetstream": { + "type": "boolean" + }, + "stream_name": { + "type": "string" + }, + "consumer_name": { + "type": "string" + }, + "subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "nats_resource_path", + "use_jetstream", + "subjects", + "enabled" + ] + }, + "NewNatsTrigger": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "nats_resource_path": { + "type": "string" + }, + "use_jetstream": { + "type": "boolean" + }, + "stream_name": { + "type": "string" + }, + "consumer_name": { + "type": "string" + }, + "subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "nats_resource_path", + "use_jetstream", + "subjects" + ] + }, + "EditNatsTrigger": { + "type": "object", + "properties": { + "nats_resource_path": { + "type": "string" + }, + "use_jetstream": { + "type": "boolean" + }, + "stream_name": { + "type": "string" + }, + "consumer_name": { + "type": "string" + }, + "subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + } + }, + "required": [ + "path", + "script_path", + "nats_resource_path", + "use_jetstream", + "subjects", + "is_flow" + ] + }, "Group": { "type": "object", "properties": { @@ -18850,12 +24873,19 @@ }, "username": { "type": "string" + }, + "color": { + "type": "string" + }, + "operator_settings": { + "$ref": "#/components/schemas/OperatorSettings" } }, "required": [ "id", "name", - "username" + "username", + "color" ] } } @@ -18876,6 +24906,9 @@ }, "username": { "type": "string" + }, + "color": { + "type": "string" } }, "required": [ @@ -18897,6 +24930,9 @@ }, "domain": { "type": "string" + }, + "color": { + "type": "string" } }, "required": [ @@ -18944,6 +24980,9 @@ "super_admin": { "type": "boolean" }, + "devops": { + "type": "boolean" + }, "verified": { "type": "boolean" }, @@ -18974,6 +25013,14 @@ }, { "$ref": "#/components/schemas/FlowMetadata" + }, + { + "type": "object", + "properties": { + "lock_error_logs": { + "type": "string" + } + } } ] }, @@ -19028,6 +25075,9 @@ }, "visible_to_runner_only": { "type": "boolean" + }, + "on_behalf_of_email": { + "type": "string" } }, "required": [ @@ -19066,6 +25116,9 @@ }, "visible_to_runner_only": { "type": "boolean" + }, + "on_behalf_of_email": { + "type": "string" } }, "required": [ @@ -19135,6 +25188,20 @@ "type": "object" } }, + "allowed_s3_keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "s3_path": { + "type": "string" + }, + "resource": { + "type": "string" + } + } + } + }, "execution_mode": { "type": "string", "enum": [ @@ -19189,6 +25256,9 @@ "publisher", "anonymous" ] + }, + "raw_app": { + "type": "boolean" } }, "required": [ @@ -19287,6 +25357,9 @@ "additionalProperties": { "type": "boolean" } + }, + "custom_path": { + "type": "string" } }, "required": [ @@ -19404,16 +25477,13 @@ ] }, "HubScriptKind": { - "name": "kind", - "schema": { - "type": "string", - "enum": [ - "script", - "failure", - "trigger", - "approval" - ] - } + "type": "string", + "enum": [ + "script", + "failure", + "trigger", + "approval" + ] }, "PolarsClientKwargs": { "type": "object", @@ -19588,7 +25658,8 @@ "resourcetype", "schedule", "user", - "group" + "group", + "trigger" ] } }, @@ -19619,7 +25690,8 @@ "app", "resource", "variable", - "secret" + "secret", + "trigger" ] } } @@ -19677,7 +25749,8 @@ "resourcetype", "schedule", "user", - "group" + "group", + "trigger" ] } } @@ -19775,25 +25848,7 @@ "type": "string" }, "language": { - "type": "string", - "enum": [ - "python3", - "deno", - "go", - "bash", - "powershell", - "postgresql", - "mysql", - "bigquery", - "snowflake", - "mssql", - "graphql", - "nativets", - "bun", - "php", - "rust", - "ansible" - ] + "$ref": "#/components/schemas/ScriptLang" } }, "required": [ @@ -20000,6 +26055,286 @@ "type": "boolean", "nullable": true, "description": "Acknowledgment status of the alert, can be true, false, or null if not set" + }, + "workspace_id": { + "type": "string", + "nullable": true, + "description": "Workspace id if the alert is in the scope of a workspace" + } + } + }, + "CaptureTriggerKind": { + "type": "string", + "enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email", + "nats", + "postgres", + "sqs", + "mqtt", + "gcp" + ] + }, + "Capture": { + "type": "object", + "properties": { + "trigger_kind": { + "$ref": "#/components/schemas/CaptureTriggerKind" + }, + "main_args": {}, + "preprocessor_args": {}, + "id": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "trigger_kind", + "main_args", + "preprocessor_args", + "id", + "created_at" + ] + }, + "CaptureConfig": { + "type": "object", + "properties": { + "trigger_config": {}, + "trigger_kind": { + "$ref": "#/components/schemas/CaptureTriggerKind" + }, + "error": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "trigger_kind" + ] + }, + "OperatorSettings": { + "nullable": true, + "type": "object", + "required": [ + "runs", + "schedules", + "resources", + "variables", + "triggers", + "audit_logs", + "groups", + "folders", + "workers" + ], + "properties": { + "runs": { + "type": "boolean", + "description": "Whether operators can view runs" + }, + "schedules": { + "type": "boolean", + "description": "Whether operators can view schedules" + }, + "resources": { + "type": "boolean", + "description": "Whether operators can view resources" + }, + "variables": { + "type": "boolean", + "description": "Whether operators can view variables" + }, + "audit_logs": { + "type": "boolean", + "description": "Whether operators can view audit logs" + }, + "triggers": { + "type": "boolean", + "description": "Whether operators can view triggers" + }, + "groups": { + "type": "boolean", + "description": "Whether operators can view groups page" + }, + "folders": { + "type": "boolean", + "description": "Whether operators can view folders page" + }, + "workers": { + "type": "boolean", + "description": "Whether operators can view workers page" + } + } + }, + "TeamInfo": { + "type": "object", + "required": [ + "team_id", + "team_name", + "channels" + ], + "properties": { + "team_id": { + "type": "string", + "description": "The unique identifier of the Microsoft Teams team", + "example": "19:abc123def456@thread.tacv2" + }, + "team_name": { + "type": "string", + "description": "The display name of the Microsoft Teams team", + "example": "Engineering Team" + }, + "channels": { + "type": "array", + "description": "List of channels within the team", + "items": { + "$ref": "#/components/schemas/ChannelInfo" + } + } + } + }, + "ChannelInfo": { + "type": "object", + "required": [ + "channel_id", + "channel_name", + "tenant_id", + "service_url" + ], + "properties": { + "channel_id": { + "type": "string", + "description": "The unique identifier of the channel", + "example": "19:channel123@thread.tacv2" + }, + "channel_name": { + "type": "string", + "description": "The display name of the channel", + "example": "General" + }, + "tenant_id": { + "type": "string", + "description": "The Microsoft Teams tenant identifier", + "example": "12345678-1234-1234-1234-123456789012" + }, + "service_url": { + "type": "string", + "description": "The service URL for the channel", + "example": "https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/" + } + } + }, + "GithubInstallations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "workspace_id": { + "type": "string" + }, + "installation_id": { + "type": "number" + }, + "account_id": { + "type": "string" + }, + "repositories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "url" + ] + } + } + }, + "required": [ + "installation_id", + "account_id", + "repositories" + ] + } + }, + "WorkspaceGithubInstallation": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "installation_id": { + "type": "number" + } + }, + "required": [ + "account_id", + "installation_id" + ] + }, + "S3Object": { + "type": "object", + "properties": { + "s3": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "presigned": { + "type": "string" + } + }, + "required": [ + "s3" + ] + }, + "TeamsChannel": { + "type": "object", + "required": [ + "team_id", + "team_name", + "channel_id", + "channel_name" + ], + "properties": { + "team_id": { + "type": "string", + "description": "Microsoft Teams team ID", + "minLength": 1 + }, + "team_name": { + "type": "string", + "description": "Microsoft Teams team name", + "minLength": 1 + }, + "channel_id": { + "type": "string", + "description": "Microsoft Teams channel ID", + "minLength": 1 + }, + "channel_name": { + "type": "string", + "description": "Microsoft Teams channel name", + "minLength": 1 } } }, @@ -20037,7 +26372,7 @@ "type" ] }, - "InputTransform": { + "schemas-InputTransform": { "oneOf": [ { "$ref": "#/components/schemas/StaticTransform" @@ -20060,7 +26395,7 @@ "input_transforms": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" } }, "content": { @@ -20080,6 +26415,7 @@ "bigquery", "snowflake", "mssql", + "oracledb", "graphql", "nativets", "php" @@ -20126,7 +26462,7 @@ "input_transforms": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" } }, "path": { @@ -20160,7 +26496,7 @@ "input_transforms": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" } }, "path": { @@ -20189,32 +26525,10 @@ "$ref": "#/components/schemas/FlowModuleValue" }, "stop_after_if": { - "type": "object", - "properties": { - "skip_if_stopped": { - "type": "boolean" - }, - "expr": { - "type": "string" - } - }, - "required": [ - "expr" - ] + "$ref": "#/components/schemas/StopAfterIf" }, "stop_after_all_iters_if": { - "type": "object", - "properties": { - "skip_if_stopped": { - "type": "boolean" - }, - "expr": { - "type": "string" - } - }, - "required": [ - "expr" - ] + "$ref": "#/components/schemas/StopAfterIf" }, "skip_if": { "type": "object", @@ -20228,7 +26542,7 @@ ] }, "sleep": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" }, "cache_ttl": { "type": "number" @@ -20272,7 +26586,7 @@ "type": "boolean" }, "user_groups_required": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" }, "self_approval_disabled": { "type": "boolean" @@ -20310,7 +26624,7 @@ } }, "iterator": { - "$ref": "#/components/schemas/InputTransform" + "$ref": "#/components/schemas/schemas-InputTransform" }, "skip_failures": { "type": "boolean" @@ -20515,6 +26829,23 @@ } } }, + "StopAfterIf": { + "type": "object", + "properties": { + "skip_if_stopped": { + "type": "boolean" + }, + "expr": { + "type": "string" + }, + "error_message": { + "type": "string" + } + }, + "required": [ + "expr" + ] + }, "Retry": { "type": "object", "properties": { diff --git a/backend/windmill-api/openapi-deref.yaml b/backend/windmill-api/openapi-deref.yaml index c8893f824c..4c13a9c7f8 100644 --- a/backend/windmill-api/openapi-deref.yaml +++ b/backend/windmill-api/openapi-deref.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 1.423.2 + version: 1.492.1 title: Windmill API contact: name: Windmill Team @@ -87,7 +87,7 @@ paths: - name: id in: path required: true - schema: &ref_30 + schema: &ref_34 type: integer responses: '200': @@ -233,24 +233,24 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: &ref_120 + schema: &ref_183 type: string format: date-time - name: after description: filter on created after (exclusive) timestamp in: query - schema: &ref_121 + schema: &ref_184 type: string format: date-time - name: username description: filter on exact username of user in: query - schema: &ref_130 + schema: &ref_189 type: string - name: operation description: filter on exact or prefix name of operation in: query - schema: &ref_131 + schema: &ref_190 type: string - name: operations in: query @@ -265,18 +265,23 @@ paths: - name: resource description: filter on exact or prefix name of resource in: query - schema: &ref_132 + schema: &ref_191 type: string - name: action_kind description: filter on type of operation in: query - schema: &ref_133 + schema: &ref_192 type: string enum: - Create - Update - Delete - Execute + - name: all_workspaces + in: query + description: get audit logs for all workspaces + schema: + type: boolean responses: '200': description: a list of audit logs @@ -302,12 +307,12 @@ paths: application/json: schema: type: object - properties: &ref_145 + properties: &ref_210 email: type: string password: type: string - required: &ref_146 + required: &ref_211 - email - password responses: @@ -375,6 +380,8 @@ paths: type: string is_admin: type: boolean + name: + type: string is_super_admin: type: boolean created_at: @@ -430,7 +437,7 @@ paths: application/json: schema: type: object - properties: &ref_147 + properties: &ref_212 is_admin: type: boolean operator: @@ -458,7 +465,7 @@ paths: - name: path in: path required: true - schema: &ref_23 + schema: &ref_26 type: string responses: '200': @@ -611,6 +618,8 @@ paths: properties: is_super_admin: type: boolean + is_devops: + type: boolean name: type: string responses: @@ -792,6 +801,44 @@ paths: text/plain: schema: type: string + /github_app/connected_repositories: + get: + summary: get connected repositories + operationId: getGlobalConnectedRepositories + tags: + - git_sync + responses: + '200': + description: connected repositories + content: + application/json: + schema: + type: array + items: &ref_331 + type: object + properties: + workspace_id: + type: string + installation_id: + type: number + account_id: + type: string + repositories: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + required: + - name + - url + required: + - installation_id + - account_id + - repositories /workspaces/list: get: summary: list all workspaces visible to me @@ -816,6 +863,8 @@ paths: type: string domain: type: string + color: + type: string required: &ref_8 - id - name @@ -846,7 +895,7 @@ paths: application/json: schema: type: object - properties: &ref_185 + properties: &ref_280 email: type: string workspaces: @@ -860,11 +909,55 @@ paths: type: string username: type: string + color: + type: string + operator_settings: + nullable: true + type: object + required: &ref_12 + - runs + - schedules + - resources + - variables + - triggers + - audit_logs + - groups + - folders + - workers + properties: &ref_13 + runs: + type: boolean + description: Whether operators can view runs + schedules: + type: boolean + description: Whether operators can view schedules + resources: + type: boolean + description: Whether operators can view resources + variables: + type: boolean + description: Whether operators can view variables + audit_logs: + type: boolean + description: Whether operators can view audit logs + triggers: + type: boolean + description: Whether operators can view triggers + groups: + type: boolean + description: Whether operators can view groups page + folders: + type: boolean + description: Whether operators can view folders page + workers: + type: boolean + description: Whether operators can view workers page required: - id - name - username - required: &ref_186 + - color + required: &ref_281 - email - workspaces /workspaces/list_as_superadmin: @@ -906,14 +999,16 @@ paths: application/json: schema: type: object - properties: &ref_187 + properties: &ref_282 id: type: string name: type: string username: type: string - required: &ref_188 + color: + type: string + required: &ref_283 - id - name responses: @@ -1143,29 +1238,46 @@ paths: content: application/json: schema: - type: array - items: - type: object - properties: &ref_230 - id: - type: integer - description: Unique identifier for the alert - alert_type: - type: string - description: Type of alert (e.g., critical_error) - message: - type: string - description: The message content of the alert - created_at: - type: string - format: date-time - description: Time when the alert was created - acknowledged: - type: boolean - nullable: true - description: >- - Acknowledgment status of the alert, can be true, false, - or null if not set + type: object + properties: + alerts: + type: array + items: + type: object + properties: &ref_29 + id: + type: integer + description: Unique identifier for the alert + alert_type: + type: string + description: Type of alert (e.g., critical_error) + message: + type: string + description: The message content of the alert + created_at: + type: string + format: date-time + description: Time when the alert was created + acknowledged: + type: boolean + nullable: true + description: >- + Acknowledgment status of the alert, can be true, + false, or null if not set + workspace_id: + type: string + nullable: true + description: >- + Workspace id if the alert is in the scope of a + workspace + total_rows: + type: integer + description: Total number of rows matching the query. + example: 100 + total_pages: + type: integer + description: Total number of pages based on the page size. + example: 10 /settings/critical_alerts/{id}/acknowledge: post: summary: Acknowledge a critical alert @@ -1356,12 +1468,12 @@ paths: type: array items: type: object - properties: &ref_223 + properties: &ref_318 name: type: string value: type: object - required: &ref_224 + required: &ref_319 - name - value /users/email: @@ -1383,9 +1495,15 @@ paths: operationId: refreshUserToken tags: - user + parameters: + - name: if_expiring_in_less_than_s + in: query + required: false + schema: + type: integer responses: '200': - description: free usage + description: new token content: text/plain: schema: @@ -1502,7 +1620,7 @@ paths: application/json: schema: type: object - properties: &ref_12 + properties: &ref_14 email: type: string login_type: @@ -1512,6 +1630,8 @@ paths: - github super_admin: type: boolean + devops: + type: boolean verified: type: boolean name: @@ -1522,7 +1642,7 @@ paths: type: string operator_only: type: boolean - required: &ref_13 + required: &ref_15 - email - login_type - super_admin @@ -1542,7 +1662,7 @@ paths: type: array items: type: object - properties: &ref_14 + properties: &ref_16 workspace_id: type: string email: @@ -1551,7 +1671,7 @@ paths: type: boolean operator: type: boolean - required: &ref_15 + required: &ref_17 - workspace_id - email - is_admin @@ -1576,6 +1696,154 @@ paths: type: object properties: *ref_10 required: *ref_11 + /w/{workspace}/github_app/token: + post: + summary: get github app token + operationId: getGithubAppToken + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: jwt job token + required: true + content: + application/json: + schema: + type: object + properties: + job_token: + type: string + required: + - job_token + responses: + '200': + description: github app token + content: + application/json: + schema: + type: object + properties: + token: + type: string + required: + - token + /w/{workspace}/github_app/install_from_workspace: + post: + tags: + - Git Sync + summary: Install a GitHub installation from another workspace + operationId: installFromWorkspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + source_workspace_id: + type: string + description: The ID of the workspace containing the installation to copy + installation_id: + type: number + description: The ID of the GitHub installation to copy + required: + - source_workspace_id + - installation_id + responses: + '200': + description: Installation successfully copied + /w/{workspace}/github_app/installation/{installation_id}: + delete: + summary: Delete a GitHub installation from a workspace + operationId: deleteFromWorkspace + description: >- + Removes a GitHub installation from the specified workspace. Requires + admin privileges. + tags: + - Git Sync + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: installation_id + in: path + required: true + schema: + type: integer + format: int64 + description: The ID of the GitHub installation to delete + responses: + '200': + description: Installation successfully deleted + /w/{workspace}/github_app/export/{installationId}: + get: + summary: Export GitHub installation JWT token + description: >- + Exports the JWT token for a specific GitHub installation in the + workspace + operationId: exportInstallation + tags: + - Git Sync + parameters: + - name: workspace + in: path + required: true + schema: + type: string + - name: installationId + in: path + required: true + schema: + type: integer + responses: + '200': + description: Successfully exported the JWT token + content: + application/json: + schema: + type: object + properties: + jwt_token: + type: string + /w/{workspace}/github_app/import: + post: + summary: Import GitHub installation from JWT token + description: >- + Imports a GitHub installation from a JWT token exported from another + instance + operationId: importInstallation + tags: + - Git Sync + parameters: + - name: workspace + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - jwt_token + properties: + jwt_token: + type: string + responses: + '200': + description: Successfully imported the installation /users/accept_invite: post: summary: accept invite to workspace @@ -1886,6 +2154,32 @@ paths: text/plain: schema: type: string + /w/{workspace}/workspaces/change_workspace_color: + post: + summary: change workspace id + operationId: changeWorkspaceColor + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + content: + application/json: + schema: + type: object + properties: + color: + type: string + responses: + '200': + description: status + content: + text/plain: + schema: + type: string /w/{workspace}/users/whois/{username}: get: summary: whois @@ -1911,6 +2205,36 @@ paths: type: object properties: *ref_10 required: *ref_11 + /w/{workspace}/workspaces/operator_settings: + post: + operationId: updateOperatorSettings + summary: Update operator settings for a workspace + description: >- + Updates the operator settings for a specific workspace. Requires + workspace admin privileges. + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + nullable: true + type: object + required: *ref_12 + properties: *ref_13 + responses: + '200': + description: Operator settings updated successfully + content: + text/plain: + schema: + type: string /users/exists/{email}: get: summary: exists email @@ -1959,8 +2283,8 @@ paths: type: array items: type: object - properties: *ref_12 - required: *ref_13 + properties: *ref_14 + required: *ref_15 /w/{workspace}/workspaces/list_pending_invites: get: summary: list pending invites for a workspace @@ -1981,8 +2305,8 @@ paths: type: array items: type: object - properties: *ref_14 - required: *ref_15 + properties: *ref_16 + required: *ref_17 /w/{workspace}/workspaces/get_settings: get: summary: get settings @@ -2010,6 +2334,12 @@ paths: type: string slack_command_script: type: string + teams_team_id: + type: string + teams_command_script: + type: string + teams_team_name: + type: string auto_invite_domain: type: string auto_invite_operator: @@ -2018,36 +2348,64 @@ paths: type: boolean plan: type: string - automatic_billing: - type: boolean customer_id: type: string webhook: type: string deploy_to: type: string - ai_resource: + ai_config: type: object - properties: &ref_16 - path: - type: string - provider: - type: string - required: &ref_17 - - path - - provider - code_completion_enabled: - type: boolean + properties: &ref_20 + providers: + type: object + additionalProperties: + type: object + properties: &ref_197 + resource_path: + type: string + models: + type: array + items: + type: string + required: &ref_198 + - resource_path + - models + default_model: + type: object + properties: &ref_18 + model: + type: string + provider: + type: string + enum: &ref_196 + - openai + - azure_openai + - anthropic + - mistral + - deepseek + - googleai + - groq + - openrouter + - togetherai + - customai + required: &ref_19 + - model + - provider + code_completion_model: + type: object + properties: *ref_18 + required: *ref_19 error_handler: type: string error_handler_extra_args: type: object - additionalProperties: &ref_18 {} + additionalProperties: &ref_21 {} error_handler_muted_on_cancel: type: boolean large_file_storage: type: object - properties: &ref_19 + properties: &ref_22 type: type: string enum: @@ -2081,7 +2439,7 @@ paths: type: boolean git_sync: type: object - properties: &ref_20 + properties: &ref_23 include_path: type: array items: @@ -2102,11 +2460,12 @@ paths: - schedule - user - group + - trigger repositories: type: array items: type: object - properties: &ref_207 + properties: &ref_302 script_path: type: string git_repo_resource_path: @@ -2131,12 +2490,13 @@ paths: - schedule - user - group - required: &ref_208 + - trigger + required: &ref_303 - script_path - git_repo_resource_path deploy_ui: type: object - properties: &ref_21 + properties: &ref_24 include_path: type: array items: @@ -2152,11 +2512,12 @@ paths: - resource - variable - secret + - trigger default_app: type: string default_scripts: type: object - properties: &ref_22 + properties: &ref_25 order: type: array items: @@ -2168,9 +2529,16 @@ paths: default_script_content: additionalProperties: type: string + mute_critical_alerts: + type: boolean + color: + type: string + operator_settings: + nullable: true + type: object + required: *ref_12 + properties: *ref_13 required: - - code_completion_enabled - - automatic_billing - error_handler_muted_on_cancel /w/{workspace}/workspaces/get_deploy_to: get: @@ -2234,17 +2602,40 @@ paths: type: boolean usage: type: number - seats: - type: number - automatic_billing: - type: boolean + owner: + type: string + status: + type: string required: - premium - - automatic_billing - /w/{workspace}/workspaces/set_automatic_billing: + - owner + /w/{workspace}/workspaces/threshold_alert: + get: + summary: get threshold alert info + operationId: getThresholdAlert + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: object + properties: + threshold_alert_amount: + type: number + last_alert_sent: + type: string + format: date-time post: - summary: set automatic billing - operationId: setAutomaticBilling + summary: set threshold alert info + operationId: setThresholdAlert tags: - workspace parameters: @@ -2253,19 +2644,15 @@ paths: required: true schema: *ref_0 requestBody: - description: automatic billing + description: threshold alert info required: true content: application/json: schema: type: object properties: - automatic_billing: - type: boolean - seats: + threshold_alert_amount: type: number - required: - - automatic_billing responses: '200': description: status @@ -2301,6 +2688,118 @@ paths: text/plain: schema: type: string + /w/{workspace}/workspaces/edit_teams_command: + post: + summary: edit teams command + operationId: editTeamsCommand + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: WorkspaceInvite + required: true + content: + application/json: + schema: + type: object + properties: + slack_command_script: + type: string + responses: + '200': + description: status + content: + text/plain: + schema: + type: string + /w/{workspace}/workspaces/available_teams_ids: + get: + summary: list available teams ids + operationId: listAvailableTeamsIds + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: array + items: + type: object + properties: + team_name: + type: string + team_id: + type: string + /w/{workspace}/workspaces/available_teams_channels: + get: + summary: list available teams channels + operationId: listAvailableTeamsChannels + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: array + items: + type: object + properties: + channel_name: + type: string + channel_id: + type: string + service_url: + type: string + tenant_id: + type: string + /w/{workspace}/workspaces/connect_teams: + post: + summary: connect teams + operationId: connectTeams + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: connect teams + required: true + content: + application/json: + schema: + type: object + properties: + team_id: + type: string + team_name: + type: string + responses: + '200': + description: status + content: + text/plain: + schema: + type: string /w/{workspace}/workspaces/run_slack_message_test_job: post: summary: run a job that sends a message to Slack @@ -2336,6 +2835,41 @@ paths: properties: job_uuid: type: string + /w/{workspace}/workspaces/run_teams_message_test_job: + post: + summary: run a job that sends a message to Teams + operationId: runTeamsMessageTestJob + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: path to hub script to run and its corresponding args + required: true + content: + application/json: + schema: + type: object + properties: + hub_script_path: + type: string + channel: + type: string + test_msg: + type: string + responses: + '200': + description: status + content: + text/json: + schema: + type: object + properties: + job_uuid: + type: string /w/{workspace}/workspaces/edit_deploy_to: post: summary: edit deploy to @@ -2441,15 +2975,7 @@ paths: application/json: schema: type: object - required: - - code_completion_enabled - properties: - ai_resource: - type: object - properties: *ref_16 - required: *ref_17 - code_completion_enabled: - type: boolean + properties: *ref_20 responses: '200': description: status @@ -2472,20 +2998,10 @@ paths: '200': description: status content: - text/plain: + application/json: schema: type: object - properties: - ai_provider: - type: string - exists_ai_resource: - type: boolean - code_completion_enabled: - type: boolean - required: - - ai_provider - - exists_ai_resource - - code_completion_enabled + properties: *ref_20 /w/{workspace}/workspaces/edit_error_handler: post: summary: edit error handler @@ -2509,7 +3025,7 @@ paths: type: string error_handler_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 error_handler_muted_on_cancel: type: boolean responses: @@ -2540,7 +3056,7 @@ paths: properties: large_file_storage: type: object - properties: *ref_19 + properties: *ref_22 responses: '200': description: status @@ -2568,7 +3084,7 @@ paths: properties: git_sync_settings: type: object - properties: *ref_20 + properties: *ref_23 responses: '200': description: status @@ -2596,7 +3112,7 @@ paths: properties: deploy_ui_settings: type: object - properties: *ref_21 + properties: *ref_24 responses: '200': description: status @@ -2648,7 +3164,7 @@ paths: application/json: schema: type: object - properties: *ref_22 + properties: *ref_25 responses: '200': description: status @@ -2673,7 +3189,7 @@ paths: application/json: schema: type: object - properties: *ref_22 + properties: *ref_25 /w/{workspace}/workspaces/set_environment_variable: post: summary: set environment variable @@ -2799,7 +3315,7 @@ paths: application/json: schema: type: object - properties: *ref_19 + properties: *ref_22 /w/{workspace}/workspaces/usage: get: summary: get usage @@ -2841,9 +3357,27 @@ paths: type: boolean websocket_used: type: boolean + kafka_used: + type: boolean + nats_used: + type: boolean + postgres_used: + type: boolean + mqtt_used: + type: boolean + gcp_used: + type: boolean + sqs_used: + type: boolean required: - http_routes_used - websocket_used + - kafka_used + - nats_used + - postgres_used + - mqtt_used + - gcp_used + - sqs_used /w/{workspace}/users/list: get: summary: list users @@ -2886,7 +3420,7 @@ paths: type: array items: type: object - properties: &ref_144 + properties: &ref_209 email: type: string executions: @@ -2947,7 +3481,7 @@ paths: application/json: schema: type: object - properties: &ref_148 + properties: &ref_213 label: type: string expiration: @@ -2979,7 +3513,7 @@ paths: application/json: schema: type: object - properties: &ref_149 + properties: &ref_214 label: type: string expiration: @@ -2989,7 +3523,7 @@ paths: type: string workspace_id: type: string - required: &ref_150 + required: &ref_215 - impersonate_email responses: '201': @@ -3045,7 +3579,7 @@ paths: type: array items: type: object - properties: &ref_40 + properties: &ref_47 label: type: string expiration: @@ -3065,7 +3599,7 @@ paths: type: string email: type: string - required: &ref_41 + required: &ref_48 - token_prefix - created_at - last_used_at @@ -3114,7 +3648,7 @@ paths: application/json: schema: type: object - properties: &ref_153 + properties: &ref_218 path: type: string value: @@ -3130,7 +3664,7 @@ paths: expires_at: type: string format: date-time - required: &ref_154 + required: &ref_219 - path - value - is_secret @@ -3181,7 +3715,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: variable deleted @@ -3203,7 +3737,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: already_encrypted in: query schema: @@ -3215,7 +3749,7 @@ paths: application/json: schema: type: object - properties: &ref_155 + properties: &ref_220 path: type: string value: @@ -3245,7 +3779,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: decrypt_secret description: | ask to decrypt secret if this variable is secret @@ -3267,7 +3801,7 @@ paths: application/json: schema: type: object - properties: &ref_24 + properties: &ref_27 workspace_id: type: string path: @@ -3297,7 +3831,7 @@ paths: expires_at: type: string format: date-time - required: &ref_25 + required: &ref_28 - workspace_id - path - is_secret @@ -3316,7 +3850,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: variable @@ -3338,7 +3872,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: variable @@ -3378,8 +3912,8 @@ paths: type: array items: type: object - properties: *ref_24 - required: *ref_25 + properties: *ref_27 + required: *ref_28 /w/{workspace}/variables/list_contextual: get: summary: list contextual variables @@ -3400,7 +3934,7 @@ paths: type: array items: type: object - properties: &ref_151 + properties: &ref_216 name: type: string value: @@ -3409,11 +3943,141 @@ paths: type: string is_custom: type: boolean - required: &ref_152 + required: &ref_217 - name - value - description - is_custom + /w/{workspace}/workspaces/critical_alerts: + get: + summary: Get all critical alerts for this workspace + operationId: workspaceGetCriticalAlerts + tags: + - setting + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - in: query + name: page + schema: + type: integer + default: 1 + description: The page number to retrieve (minimum value is 1) + - in: query + name: page_size + schema: + type: integer + default: 10 + maximum: 100 + description: Number of alerts per page (maximum is 100) + - in: query + name: acknowledged + schema: + type: boolean + nullable: true + description: >- + Filter by acknowledgment status; true for acknowledged, false for + unacknowledged, and omit for all alerts + responses: + '200': + description: Successfully retrieved all critical alerts + content: + application/json: + schema: + type: object + properties: + alerts: + type: array + items: + type: object + properties: *ref_29 + total_rows: + type: integer + description: Total number of rows matching the query. + example: 100 + total_pages: + type: integer + description: Total number of pages based on the page size. + example: 10 + /w/{workspace}/workspaces/critical_alerts/{id}/acknowledge: + post: + summary: Acknowledge a critical alert for this workspace + operationId: workspaceAcknowledgeCriticalAlert + tags: + - setting + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - in: path + name: id + required: true + schema: + type: integer + description: The ID of the critical alert to acknowledge + responses: + '200': + description: Successfully acknowledged the critical alert + content: + application/json: + schema: + type: string + example: Critical alert acknowledged + /w/{workspace}/workspaces/critical_alerts/acknowledge_all: + post: + summary: Acknowledge all unacknowledged critical alerts for this workspace + operationId: workspaceAcknowledgeAllCriticalAlerts + tags: + - setting + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: Successfully acknowledged all unacknowledged critical alerts. + content: + application/json: + schema: + type: string + example: All unacknowledged critical alerts acknowledged + /w/{workspace}/workspaces/critical_alerts/mute: + post: + summary: Mute critical alert UI for this workspace + operationId: workspaceMuteCriticalAlertsUI + tags: + - setting + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: Boolean flag to mute critical alerts. + required: true + content: + application/json: + schema: + type: object + properties: + mute_critical_alerts: + type: boolean + description: Whether critical alerts should be muted. + example: true + responses: + '200': + description: Successfully updated mute critical alert settings. + content: + application/json: + schema: + type: string + example: >- + Updated mute critical alert UI settings for workspace: + workspace_id /oauth/login_callback/{client_name}: post: security: [] @@ -3425,7 +4089,7 @@ paths: - name: client_name in: path required: true - schema: &ref_26 + schema: &ref_30 type: string requestBody: description: Partially filled script @@ -3526,7 +4190,7 @@ paths: - name: client_name in: path required: true - schema: *ref_26 + schema: *ref_30 requestBody: description: code endpoint required: true @@ -3549,7 +4213,7 @@ paths: application/json: schema: type: object - properties: &ref_200 + properties: &ref_295 access_token: type: string expires_in: @@ -3560,7 +4224,7 @@ paths: type: array items: type: string - required: &ref_201 + required: &ref_296 - access_token /w/{workspace}/oauth/create_account: post: @@ -3611,7 +4275,7 @@ paths: - name: id in: path required: true - schema: &ref_27 + schema: &ref_31 type: integer requestBody: description: variable path @@ -3646,7 +4310,7 @@ paths: - name: id in: path required: true - schema: *ref_27 + schema: *ref_31 responses: '200': description: disconnected client @@ -3672,6 +4336,24 @@ paths: text/plain: schema: type: string + /w/{workspace}/oauth/disconnect_teams: + post: + summary: disconnect teams + operationId: disconnectTeams + tags: + - oauth + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: disconnected teams + content: + text/plain: + schema: + type: string /oauth/list_logins: get: summary: list oauth logins @@ -3743,6 +4425,95 @@ paths: type: array items: type: string + /teams/sync: + post: + operationId: syncTeams + summary: synchronize Microsoft Teams information (teams/channels) + tags: + - teams + responses: + '200': + description: Teams information successfully synchronized + content: + application/json: + schema: + type: array + items: + type: object + required: &ref_327 + - team_id + - team_name + - channels + properties: &ref_328 + team_id: + type: string + description: The unique identifier of the Microsoft Teams team + example: 19:abc123def456@thread.tacv2 + team_name: + type: string + description: The display name of the Microsoft Teams team + example: Engineering Team + channels: + type: array + description: List of channels within the team + items: + type: object + required: &ref_329 + - channel_id + - channel_name + - tenant_id + - service_url + properties: &ref_330 + channel_id: + type: string + description: The unique identifier of the channel + example: 19:channel123@thread.tacv2 + channel_name: + type: string + description: The display name of the channel + example: General + tenant_id: + type: string + description: The Microsoft Teams tenant identifier + example: 12345678-1234-1234-1234-123456789012 + service_url: + type: string + description: The service URL for the channel + example: >- + https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/ + /teams/activities: + post: + summary: send update to Microsoft Teams activity + description: Respond to a Microsoft Teams activity after a workspace command is run + operationId: sendMessageToConversation + tags: + - teams + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - conversation_id + - text + properties: + conversation_id: + type: string + description: The ID of the Teams conversation/activity + success: + type: boolean + description: Used for styling the card conditionally + default: true + text: + type: string + description: The message text to be sent in the Teams card + card_block: + type: object + description: The card block to be sent in the Teams card + responses: + '200': + description: Activity processed successfully /w/{workspace}/resources/create: post: summary: create resource @@ -3765,7 +4536,7 @@ paths: application/json: schema: type: object - properties: &ref_162 + properties: &ref_227 path: type: string value: {} @@ -3773,7 +4544,7 @@ paths: type: string resource_type: type: string - required: &ref_163 + required: &ref_228 - path - value - resource_type @@ -3798,7 +4569,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource deleted @@ -3820,7 +4591,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated resource required: true @@ -3828,7 +4599,7 @@ paths: application/json: schema: type: object - properties: &ref_164 + properties: &ref_229 path: type: string description: @@ -3855,7 +4626,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated resource required: true @@ -3886,7 +4657,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource @@ -3894,7 +4665,7 @@ paths: application/json: schema: type: object - properties: &ref_165 + properties: &ref_230 workspace_id: type: string path: @@ -3915,7 +4686,7 @@ paths: edited_at: type: string format: date-time - required: &ref_166 + required: &ref_231 - path - resource_type - is_oauth @@ -3933,7 +4704,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: job_id description: job id in: query @@ -3960,7 +4731,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource value @@ -3981,7 +4752,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: does resource exists @@ -4031,7 +4802,7 @@ paths: type: array items: type: object - properties: &ref_167 + properties: &ref_232 workspace_id: type: string path: @@ -4062,7 +4833,7 @@ paths: edited_at: type: string format: date-time - required: &ref_168 + required: &ref_233 - path - resource_type - is_oauth @@ -4109,7 +4880,7 @@ paths: - name: name in: path required: true - schema: &ref_103 + schema: &ref_163 type: string responses: '200': @@ -4146,7 +4917,7 @@ paths: application/json: schema: type: object - properties: &ref_28 + properties: &ref_32 workspace_id: type: string name: @@ -4161,7 +4932,7 @@ paths: format: date-time format_extension: type: string - required: &ref_29 + required: &ref_33 - name responses: '201': @@ -4201,7 +4972,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource_type deleted @@ -4223,7 +4994,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated resource_type required: true @@ -4231,7 +5002,7 @@ paths: application/json: schema: type: object - properties: &ref_169 + properties: &ref_234 schema: {} description: type: string @@ -4256,7 +5027,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource_type deleted @@ -4264,8 +5035,8 @@ paths: application/json: schema: type: object - properties: *ref_28 - required: *ref_29 + properties: *ref_32 + required: *ref_33 /w/{workspace}/resources/type/exists/{path}: get: summary: does resource_type exists @@ -4280,7 +5051,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: does resource_type exist @@ -4308,8 +5079,8 @@ paths: type: array items: type: object - properties: *ref_28 - required: *ref_29 + properties: *ref_32 + required: *ref_33 /w/{workspace}/resources/type/listnames: get: summary: list resource_types names @@ -4448,7 +5219,7 @@ paths: - name: id in: path required: true - schema: *ref_30 + schema: *ref_34 responses: '200': description: flow @@ -4459,51 +5230,51 @@ paths: properties: flow: type: object - properties: &ref_54 + properties: &ref_61 summary: type: string description: type: string value: type: object - properties: &ref_66 + properties: &ref_78 modules: type: array items: type: object - properties: &ref_33 + properties: &ref_37 id: type: string value: - oneOf: &ref_251 + oneOf: &ref_352 - type: object - properties: &ref_235 + properties: &ref_336 input_transforms: type: object additionalProperties: - oneOf: &ref_31 + oneOf: &ref_35 - type: object - properties: &ref_231 + properties: &ref_332 value: {} type: type: string enum: - javascript - required: &ref_232 + required: &ref_333 - expr - type - type: object - properties: &ref_233 + properties: &ref_334 expr: type: string type: type: string enum: - javascript - required: &ref_234 + required: &ref_335 - expr - type - discriminator: &ref_32 + discriminator: &ref_36 propertyName: type mapping: static: '#/components/schemas/StaticTransform' @@ -4524,6 +5295,7 @@ paths: - bigquery - snowflake - mssql + - oracledb - graphql - nativets - php @@ -4545,18 +5317,18 @@ paths: type: string is_trigger: type: boolean - required: &ref_236 + required: &ref_337 - type - content - language - input_transforms - type: object - properties: &ref_237 + properties: &ref_338 input_transforms: type: object additionalProperties: - oneOf: *ref_31 - discriminator: *ref_32 + oneOf: *ref_35 + discriminator: *ref_36 path: type: string hash: @@ -4569,40 +5341,40 @@ paths: type: string is_trigger: type: boolean - required: &ref_238 + required: &ref_339 - type - path - input_transforms - type: object - properties: &ref_239 + properties: &ref_340 input_transforms: type: object additionalProperties: - oneOf: *ref_31 - discriminator: *ref_32 + oneOf: *ref_35 + discriminator: *ref_36 path: type: string type: type: string enum: - flow - required: &ref_240 + required: &ref_341 - type - path - input_transforms - type: object - properties: &ref_241 + properties: &ref_342 modules: type: array items: type: object - properties: *ref_33 - required: &ref_34 + properties: *ref_37 + required: &ref_38 - value - id iterator: - oneOf: *ref_31 - discriminator: *ref_32 + oneOf: *ref_35 + discriminator: *ref_36 skip_failures: type: boolean type: @@ -4613,19 +5385,19 @@ paths: type: boolean parallelism: type: integer - required: &ref_242 + required: &ref_343 - modules - iterator - skip_failures - type - type: object - properties: &ref_243 + properties: &ref_344 modules: type: array items: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 skip_failures: type: boolean type: @@ -4636,12 +5408,12 @@ paths: type: boolean parallelism: type: integer - required: &ref_244 + required: &ref_345 - modules - skip_failures - type - type: object - properties: &ref_245 + properties: &ref_346 branches: type: array items: @@ -4655,8 +5427,8 @@ paths: type: array items: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 required: - modules - expr @@ -4664,20 +5436,20 @@ paths: type: array items: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 required: - modules type: type: string enum: - branchone - required: &ref_246 + required: &ref_347 - branches - default - type - type: object - properties: &ref_247 + properties: &ref_348 branches: type: array items: @@ -4691,8 +5463,8 @@ paths: type: array items: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 required: - modules - expr @@ -4702,20 +5474,20 @@ paths: - branchall parallel: type: boolean - required: &ref_248 + required: &ref_349 - branches - type - type: object - properties: &ref_249 + properties: &ref_350 type: type: string enum: - identity flow: type: boolean - required: &ref_250 + required: &ref_351 - type - discriminator: &ref_252 + discriminator: &ref_353 propertyName: type mapping: rawscript: '#/components/schemas/RawScript' @@ -4728,22 +5500,19 @@ paths: identity: '#/components/schemas/Identity' stop_after_if: type: object - properties: + properties: &ref_39 skip_if_stopped: type: boolean expr: type: string - required: + error_message: + type: string + required: &ref_40 - expr stop_after_all_iters_if: type: object - properties: - skip_if_stopped: - type: boolean - expr: - type: string - required: - - expr + properties: *ref_39 + required: *ref_40 skip_if: type: object properties: @@ -4752,8 +5521,8 @@ paths: required: - expr sleep: - oneOf: *ref_31 - discriminator: *ref_32 + oneOf: *ref_35 + discriminator: *ref_36 cache_ttl: type: number timeout: @@ -4783,8 +5552,8 @@ paths: user_auth_required: type: boolean user_groups_required: - oneOf: *ref_31 - discriminator: *ref_32 + oneOf: *ref_35 + discriminator: *ref_36 self_approval_disabled: type: boolean hide_cancel: @@ -4797,7 +5566,7 @@ paths: type: boolean retry: type: object - properties: &ref_95 + properties: &ref_114 constant: type: object properties: @@ -4818,15 +5587,15 @@ paths: type: integer minimum: 0 maximum: 100 - required: *ref_34 + required: *ref_38 failure_module: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 preprocessor_module: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 same_worker: type: boolean concurrent_limit: @@ -4843,11 +5612,11 @@ paths: type: number early_return: type: string - required: &ref_67 + required: &ref_79 - modules schema: type: object - required: &ref_55 + required: &ref_62 - summary - value /apps/hub/list: @@ -4900,7 +5669,7 @@ paths: - name: id in: path required: true - schema: *ref_30 + schema: *ref_34 responses: '200': description: app @@ -4920,6 +5689,108 @@ paths: - value required: - app + /apps_u/public_app_by_custom_path/{custom_path}: + get: + summary: get public app by custom path + operationId: getPublicAppByCustomPath + tags: + - app + parameters: + - name: custom_path + in: path + required: true + schema: &ref_74 + type: string + responses: + '200': + description: app details + content: + application/json: + schema: + allOf: + - type: object + properties: &ref_69 + id: + type: integer + workspace_id: + type: string + path: + type: string + summary: + type: string + versions: + type: array + items: + type: integer + created_by: + type: string + created_at: + type: string + format: date-time + value: + type: object + policy: + type: object + properties: &ref_68 + triggerables: + type: object + additionalProperties: + type: object + triggerables_v2: + type: object + additionalProperties: + type: object + s3_inputs: + type: array + items: + type: object + allowed_s3_keys: + type: array + items: + type: object + properties: + s3_path: + type: string + resource: + type: string + execution_mode: + type: string + enum: + - viewer + - publisher + - anonymous + on_behalf_of: + type: string + on_behalf_of_email: + type: string + execution_mode: + type: string + enum: + - viewer + - publisher + - anonymous + extra_perms: + type: object + additionalProperties: + type: boolean + custom_path: + type: string + required: &ref_70 + - id + - workspace_id + - path + - summary + - versions + - created_by + - created_at + - value + - policy + - execution_mode + - extra_perms + - type: object + properties: + workspace_id: + type: string /scripts/hub/get/{path}: get: summary: get hub script content by path @@ -4930,7 +5801,7 @@ paths: - name: path in: path required: true - schema: &ref_35 + schema: &ref_41 type: string responses: '200': @@ -4949,7 +5820,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script details @@ -5019,14 +5890,12 @@ paths: version_id: type: number kind: - name: kind - schema: &ref_36 - type: string - enum: - - script - - failure - - trigger - - approval + type: string + enum: &ref_42 + - script + - failure + - trigger + - approval votes: type: number views: @@ -5092,8 +5961,8 @@ paths: app: type: string kind: - name: kind - schema: *ref_36 + type: string + enum: *ref_42 score: type: number required: @@ -5154,12 +6023,12 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: &ref_52 + schema: &ref_59 type: boolean - name: created_by description: mask to filter exact matching user creator in: query - schema: &ref_53 + schema: &ref_60 type: string - name: path_start description: mask to filter matching starting path @@ -5178,8 +6047,7 @@ paths: type: string - name: last_parent_hash description: > - mask to filter scripts whom last parent in the chain has exact - hash. + mask to filter scripts whom last parent in the chain has exact hash. Beware that each script stores only a limited number of parents. Hence @@ -5214,7 +6082,7 @@ paths: when multiple archived hash share the same path, only the ones with the latest create_at - are + are ed. in: query @@ -5273,7 +6141,7 @@ paths: type: array items: type: object - properties: &ref_37 + properties: &ref_44 workspace_id: type: string hash: @@ -5316,7 +6184,7 @@ paths: type: string language: type: string - enum: + enum: &ref_43 - python3 - deno - go @@ -5327,12 +6195,16 @@ paths: - bigquery - snowflake - mssql + - oracledb - graphql - nativets - bun - php - rust - ansible + - csharp + - nu + - java kind: type: string enum: @@ -5341,6 +6213,7 @@ paths: - trigger - command - approval + - preprocessor starred: type: boolean tag: @@ -5381,7 +6254,9 @@ paths: type: string has_preprocessor: type: boolean - required: &ref_38 + on_behalf_of_email: + type: string + required: &ref_45 - hash - path - summary @@ -5479,7 +6354,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: draft deleted @@ -5505,7 +6380,7 @@ paths: application/json: schema: type: object - properties: &ref_42 + properties: &ref_49 path: type: string parent_hash: @@ -5524,23 +6399,7 @@ paths: type: string language: type: string - enum: - - python3 - - deno - - go - - bash - - powershell - - postgresql - - mysql - - bigquery - - snowflake - - mssql - - graphql - - nativets - - bun - - php - - rust - - ansible + enum: *ref_43 kind: type: string enum: @@ -5549,6 +6408,7 @@ paths: - trigger - command - approval + - preprocessor tag: type: string draft_only: @@ -5587,7 +6447,9 @@ paths: type: string has_preprocessor: type: boolean - required: &ref_43 + on_behalf_of_email: + type: string + required: &ref_50 - path - summary - description @@ -5614,7 +6476,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: Workspace error handler enabled required: true @@ -5640,6 +6502,17 @@ paths: operationId: getCustomTags tags: - worker + parameters: + - name: workspace + in: query + schema: + type: string + required: false + - name: show_workspace_restriction + in: query + schema: + type: boolean + required: false responses: '200': description: list of custom tags @@ -5691,7 +6564,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script archived @@ -5713,7 +6586,7 @@ paths: - name: hash in: path required: true - schema: &ref_39 + schema: &ref_46 type: string responses: '200': @@ -5722,8 +6595,8 @@ paths: application/json: schema: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 /w/{workspace}/scripts/delete/h/{hash}: post: summary: delete script by hash (erase content but keep hash, require admin) @@ -5738,7 +6611,7 @@ paths: - name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 responses: '200': description: script details @@ -5746,11 +6619,11 @@ paths: application/json: schema: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 /w/{workspace}/scripts/delete/p/{path}: post: - summary: delete all scripts at a given path (require admin) + summary: delete script at a given path (require admin) operationId: deleteScriptByPath tags: - script @@ -5762,7 +6635,12 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 + - name: keep_captures + description: keep captures + in: query + schema: + type: boolean responses: '200': description: script path @@ -5784,7 +6662,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: with_starred_info in: query schema: @@ -5796,8 +6674,8 @@ paths: application/json: schema: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 /w/{workspace}/scripts/get_triggers_count/{path}: get: summary: get triggers count of script @@ -5812,7 +6690,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: triggers count @@ -5820,7 +6698,7 @@ paths: application/json: schema: type: object - properties: &ref_59 + properties: &ref_66 primary_schedule: type: object properties: @@ -5836,6 +6714,18 @@ paths: type: number websocket_count: type: number + postgres_count: + type: number + kafka_count: + type: number + nats_count: + type: number + mqtt_count: + type: number + gcp_count: + type: number + sqs_count: + type: number /w/{workspace}/scripts/list_tokens/{path}: get: summary: get tokens with script scope @@ -5850,7 +6740,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: tokens list @@ -5860,8 +6750,8 @@ paths: type: array items: type: object - properties: *ref_40 - required: *ref_41 + properties: *ref_47 + required: *ref_48 /w/{workspace}/scripts/get/draft/{path}: get: summary: get script by path with draft @@ -5876,23 +6766,23 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script details content: application/json: schema: - allOf: &ref_137 + allOf: &ref_202 - type: object - properties: *ref_42 - required: *ref_43 + properties: *ref_49 + required: *ref_50 - type: object properties: draft: type: object - properties: *ref_42 - required: *ref_43 + properties: *ref_49 + required: *ref_50 hash: type: string required: @@ -5911,7 +6801,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script history @@ -5921,13 +6811,37 @@ paths: type: array items: type: object - properties: &ref_44 + properties: &ref_51 script_hash: type: string deployment_msg: type: string - required: &ref_45 + required: &ref_52 - script_hash + /w/{workspace}/scripts/list_paths_from_workspace_runnable/{path}: + get: + summary: list script paths using provided script as a relative import + operationId: listScriptPathsFromWorkspaceRunnable + tags: + - script + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_41 + responses: + '200': + description: list of script paths + content: + application/json: + schema: + type: array + items: + type: string /w/{workspace}/scripts/get_latest_version/{path}: get: summary: get scripts's latest version (hash) @@ -5940,7 +6854,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 tags: - script responses: @@ -5948,11 +6862,10 @@ paths: description: Script version/hash content: application/json: - required: false schema: type: object - properties: *ref_44 - required: *ref_45 + properties: *ref_51 + required: *ref_52 /w/{workspace}/scripts/history_update/h/{hash}/p/{path}: post: summary: update history of a script @@ -5967,11 +6880,11 @@ paths: - name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: Script deployment message required: true @@ -6003,7 +6916,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script content @@ -6027,12 +6940,12 @@ paths: - name: token in: path required: true - schema: &ref_126 + schema: &ref_187 type: string - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script content @@ -6054,7 +6967,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: does it exists @@ -6076,7 +6989,7 @@ paths: - name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 - name: with_starred_info in: query schema: @@ -6088,8 +7001,8 @@ paths: application/json: schema: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 /w/{workspace}/scripts/raw/h/{path}: get: summary: raw script by hash @@ -6104,7 +7017,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: script content @@ -6126,7 +7039,7 @@ paths: - name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 responses: '200': description: script details @@ -6139,6 +7052,68 @@ paths: type: string lock_error_logs: type: string + /w/{workspace}/jobs/list_selected_job_groups: + post: + summary: list selected jobs script/flow schemas grouped by (kind, path) + operationId: listSelectedJobGroups + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: script args + required: true + content: + application/json: + schema: + type: array + items: + type: string + format: uuid + responses: + '200': + description: result + content: + text/plain: + schema: + type: array + items: + type: object + properties: + kind: + type: string + enum: + - script + - flow + script_path: + type: string + latest_schema: + type: object + schemas: + type: array + items: + type: object + properties: + schema: + type: object + script_hash: + type: string + job_ids: + type: array + items: + type: string + required: + - schema + - script_hash + - job_ids + required: + - kind + - script_path + - latest_schema + - schemas /w/{workspace}/jobs/run/p/{path}: post: summary: run script by path @@ -6153,7 +7128,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -6175,20 +7150,20 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: &ref_46 + schema: &ref_53 type: string format: uuid - name: tag description: Override the tag to use in: query - schema: &ref_48 + schema: &ref_55 type: string - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: &ref_49 + schema: &ref_56 type: string - name: job_id description: >- @@ -6197,7 +7172,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: &ref_47 + schema: &ref_54 type: string format: uuid - name: invisible_to_owner @@ -6212,7 +7187,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '201': description: job created @@ -6235,13 +7210,13 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6249,7 +7224,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6258,14 +7233,14 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: &ref_50 + schema: &ref_57 type: string - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: &ref_51 + schema: &ref_58 type: string requestBody: description: script args @@ -6274,7 +7249,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '200': description: job result @@ -6295,23 +7270,23 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_49 + schema: *ref_56 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6319,7 +7294,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6328,13 +7303,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_51 + schema: *ref_58 requestBody: description: script args required: true @@ -6342,7 +7317,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '200': description: job result @@ -6362,23 +7337,23 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_49 + schema: *ref_56 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6386,7 +7361,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6395,13 +7370,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_51 + schema: *ref_58 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -6409,7 +7384,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: &ref_94 + schema: &ref_113 type: string responses: '200': @@ -6431,7 +7406,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6440,13 +7415,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_51 + schema: *ref_58 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6454,7 +7429,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 requestBody: description: script args required: true @@ -6462,7 +7437,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '200': description: job result @@ -6483,7 +7458,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6492,13 +7467,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_51 + schema: *ref_58 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6506,7 +7481,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 requestBody: description: script args required: true @@ -6514,7 +7489,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '200': description: job result @@ -6617,11 +7592,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: path_start description: mask to filter matching starting path in: query @@ -6675,12 +7650,12 @@ paths: type: array items: allOf: - - allOf: &ref_58 + - allOf: &ref_65 - type: object - properties: *ref_54 - required: *ref_55 + properties: *ref_61 + required: *ref_62 - type: object - properties: &ref_190 + properties: &ref_285 workspace_id: type: string path: @@ -6694,7 +7669,7 @@ paths: type: boolean extra_perms: type: object - additionalProperties: &ref_189 + additionalProperties: &ref_284 type: boolean starred: type: boolean @@ -6712,12 +7687,18 @@ paths: type: number visible_to_runner_only: type: boolean - required: &ref_191 + on_behalf_of_email: + type: string + required: &ref_286 - path - edited_by - edited_at - archived - extra_perms + - type: object + properties: + lock_error_logs: + type: string - type: object properties: has_draft: @@ -6736,7 +7717,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 tags: - flow responses: @@ -6748,7 +7729,7 @@ paths: type: array items: type: object - properties: &ref_56 + properties: &ref_63 id: type: integer created_at: @@ -6756,7 +7737,7 @@ paths: format: date-time deployment_msg: type: string - required: &ref_57 + required: &ref_64 - id - created_at /w/{workspace}/flows/get_latest_version/{path}: @@ -6771,7 +7752,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 tags: - flow responses: @@ -6779,11 +7760,42 @@ paths: description: Flow version content: application/json: - required: false schema: type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_63 + required: *ref_64 + /w/{workspace}/flows/list_paths_from_workspace_runnable/{runnable_kind}/{path}: + get: + summary: list flow paths from workspace runnable + operationId: listFlowPathsFromWorkspaceRunnable + tags: + - flow + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: runnable_kind + in: path + required: true + schema: &ref_73 + type: string + enum: + - script + - flow + - name: path + in: path + required: true + schema: *ref_41 + responses: + '200': + description: list of flow paths + content: + application/json: + schema: + type: array + items: + type: string /w/{workspace}/flows/get/v/{version}/p/{path}: get: summary: get flow version @@ -6793,8 +7805,7 @@ paths: in: path required: true schema: *ref_0 - - type: string - name: version + - name: version in: path required: true schema: @@ -6802,7 +7813,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 tags: - flow responses: @@ -6811,7 +7822,7 @@ paths: content: application/json: schema: - allOf: *ref_58 + allOf: *ref_65 /w/{workspace}/flows/history_update/v/{version}/p/{path}: post: summary: update flow history @@ -6821,8 +7832,7 @@ paths: in: path required: true schema: *ref_0 - - type: string - name: version + - name: version in: path required: true schema: @@ -6830,7 +7840,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: Flow deployment message required: true @@ -6866,7 +7876,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: with_starred_info in: query schema: @@ -6877,7 +7887,32 @@ paths: content: application/json: schema: - allOf: *ref_58 + allOf: *ref_65 + /w/{workspace}/flows/deployment_status/p/{path}: + get: + summary: get flow deployment status + operationId: getFlowDeploymentStatus + tags: + - flow + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_41 + responses: + '200': + description: flow status + content: + application/json: + schema: + type: object + properties: + lock_error_logs: + type: string /w/{workspace}/flows/get_triggers_count/{path}: get: summary: get triggers count of flow @@ -6892,7 +7927,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: triggers count @@ -6900,7 +7935,7 @@ paths: application/json: schema: type: object - properties: *ref_59 + properties: *ref_66 /w/{workspace}/flows/list_tokens/{path}: get: summary: get tokens with flow scope @@ -6915,7 +7950,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: tokens list @@ -6925,8 +7960,8 @@ paths: type: array items: type: object - properties: *ref_40 - required: *ref_41 + properties: *ref_47 + required: *ref_48 /w/{workspace}/flows/toggle_workspace_error_handler/{path}: post: summary: Toggle ON and OFF the workspace error handler for a given flow @@ -6941,7 +7976,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: Workspace error handler enabled required: true @@ -6973,7 +8008,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: flow details with draft @@ -6981,11 +8016,11 @@ paths: application/json: schema: allOf: - - allOf: *ref_58 + - allOf: *ref_65 - type: object properties: draft: - allOf: *ref_58 + allOf: *ref_65 /w/{workspace}/flows/exists/{path}: get: summary: exists flow by path @@ -7000,7 +8035,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: flow details @@ -7026,10 +8061,10 @@ paths: application/json: schema: allOf: - - allOf: &ref_60 + - allOf: &ref_67 - type: object - properties: *ref_54 - required: *ref_55 + properties: *ref_61 + required: *ref_62 - type: object properties: path: @@ -7046,6 +8081,8 @@ paths: type: number visible_to_runner_only: type: boolean + on_behalf_of_email: + type: string required: - path - type: object @@ -7075,7 +8112,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: Partially filled flow required: true @@ -7083,7 +8120,7 @@ paths: application/json: schema: allOf: - - allOf: *ref_60 + - allOf: *ref_67 - type: object properties: deployment_message: @@ -7109,7 +8146,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: archiveFlow required: true @@ -7141,7 +8178,12 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 + - name: keep_captures + description: keep captures + in: query + schema: + type: boolean responses: '200': description: flow delete @@ -7171,11 +8213,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: path_start description: mask to filter matching starting path in: query @@ -7202,7 +8244,7 @@ paths: type: array items: type: object - properties: &ref_197 + properties: &ref_292 workspace_id: type: string path: @@ -7220,7 +8262,7 @@ paths: edited_at: type: string format: date-time - required: &ref_198 + required: &ref_293 - workspace_id - path - summary @@ -7241,7 +8283,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app exists @@ -7263,12 +8305,12 @@ paths: - name: version in: path required: true - schema: &ref_125 + schema: &ref_186 type: number - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: app details @@ -7325,11 +8367,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: path_start description: mask to filter matching starting path in: query @@ -7370,7 +8412,7 @@ paths: type: array items: type: object - properties: &ref_195 + properties: &ref_290 id: type: integer workspace_id: @@ -7396,7 +8438,9 @@ paths: - viewer - publisher - anonymous - required: &ref_196 + raw_app: + type: boolean + required: &ref_291 - id - workspace_id - path @@ -7431,33 +8475,13 @@ paths: type: string policy: type: object - properties: &ref_61 - triggerables: - type: object - additionalProperties: - type: object - triggerables_v2: - type: object - additionalProperties: - type: object - s3_inputs: - type: array - items: - type: object - execution_mode: - type: string - enum: - - viewer - - publisher - - anonymous - on_behalf_of: - type: string - on_behalf_of_email: - type: string + properties: *ref_68 draft_only: type: boolean deployment_message: type: string + custom_path: + type: string required: - path - value @@ -7470,6 +8494,58 @@ paths: text/plain: schema: type: string + /w/{workspace}/apps/create_raw: + post: + summary: create app raw + operationId: createAppRaw + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new app + required: true + content: + multipart/form-data: + schema: + type: object + properties: + app: + type: object + properties: + path: + type: string + value: {} + summary: + type: string + policy: + type: object + properties: *ref_68 + draft_only: + type: boolean + deployment_message: + type: string + custom_path: + type: string + required: + - path + - value + - summary + - policy + js: + type: string + css: + type: string + responses: + '201': + description: app created + content: + text/plain: + schema: + type: string /w/{workspace}/apps/exists/{path}: get: summary: does an app exisst at path @@ -7484,7 +8560,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app exists @@ -7506,7 +8582,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: with_starred_info in: query schema: @@ -7518,51 +8594,32 @@ paths: application/json: schema: type: object - properties: &ref_62 - id: - type: integer - workspace_id: - type: string - path: - type: string - summary: - type: string - versions: - type: array - items: - type: integer - created_by: - type: string - created_at: - type: string - format: date-time - value: - type: object - policy: - type: object - properties: *ref_61 - execution_mode: - type: string - enum: - - viewer - - publisher - - anonymous - extra_perms: - type: object - additionalProperties: - type: boolean - required: &ref_63 - - id - - workspace_id - - path - - summary - - versions - - created_by - - created_at - - value - - policy - - execution_mode - - extra_perms + properties: *ref_69 + required: *ref_70 + /w/{workspace}/apps/get/lite/{path}: + get: + summary: get app lite by path + operationId: getAppLiteByPath + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_41 + responses: + '200': + description: app lite details + content: + application/json: + schema: + type: object + properties: *ref_69 + required: *ref_70 /w/{workspace}/apps/get/draft/{path}: get: summary: get app by path with draft @@ -7577,17 +8634,17 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: app details with draft content: application/json: schema: - allOf: &ref_199 + allOf: &ref_294 - type: object - properties: *ref_62 - required: *ref_63 + properties: *ref_69 + required: *ref_70 - type: object properties: draft_only: @@ -7607,7 +8664,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 responses: '200': description: app history @@ -7617,12 +8674,12 @@ paths: type: array items: type: object - properties: &ref_64 + properties: &ref_71 version: type: integer deployment_msg: type: string - required: &ref_65 + required: &ref_72 - version /w/{workspace}/apps/get_latest_version/{path}: get: @@ -7636,7 +8693,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 tags: - app responses: @@ -7644,11 +8701,38 @@ paths: description: App version content: application/json: - required: false schema: type: object - properties: *ref_64 - required: *ref_65 + properties: *ref_71 + required: *ref_72 + /w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}: + get: + summary: list app paths from workspace runnable + operationId: listAppPathsFromWorkspaceRunnable + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: runnable_kind + in: path + required: true + schema: *ref_73 + - name: path + in: path + required: true + schema: *ref_41 + responses: + '200': + description: list of app paths + content: + application/json: + schema: + type: array + items: + type: string /w/{workspace}/apps/history_update/a/{id}/v/{version}: post: summary: update app history @@ -7663,11 +8747,11 @@ paths: - name: id in: path required: true - schema: *ref_30 + schema: *ref_34 - name: version in: path required: true - schema: &ref_127 + schema: &ref_188 type: integer requestBody: description: App deployment message @@ -7700,7 +8784,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app details @@ -7708,8 +8792,8 @@ paths: application/json: schema: type: object - properties: *ref_62 - required: *ref_63 + properties: *ref_69 + required: *ref_70 /w/{workspace}/apps_u/public_resource/{path}: get: summary: get public resource @@ -7724,7 +8808,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: resource value @@ -7745,7 +8829,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app secret @@ -7767,7 +8851,7 @@ paths: - name: id in: path required: true - schema: *ref_30 + schema: *ref_34 responses: '200': description: app details @@ -7775,8 +8859,8 @@ paths: application/json: schema: type: object - properties: *ref_62 - required: *ref_63 + properties: *ref_69 + required: *ref_70 /w/{workspace}/raw_apps/create: post: summary: create raw app @@ -7827,7 +8911,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: updateraw app required: true @@ -7863,7 +8947,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app deleted @@ -7885,7 +8969,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: app deleted @@ -7907,7 +8991,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: update app required: true @@ -7923,9 +9007,11 @@ paths: value: {} policy: type: object - properties: *ref_61 + properties: *ref_68 deployment_message: type: string + custom_path: + type: string responses: '200': description: app updated @@ -7933,6 +9019,124 @@ paths: text/plain: schema: type: string + /w/{workspace}/apps/update_raw/{path}: + post: + summary: update app + operationId: updateAppRaw + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_41 + requestBody: + description: update app + required: true + content: + multipart/form-data: + schema: + type: object + properties: + app: + type: object + properties: + path: + type: string + summary: + type: string + value: {} + policy: + type: object + properties: *ref_68 + deployment_message: + type: string + custom_path: + type: string + js: + type: string + css: + type: string + responses: + '200': + description: app updated + content: + text/plain: + schema: + type: string + /w/{workspace}/apps/custom_path_exists/{custom_path}: + get: + summary: check if custom path exists + operationId: customPathExists + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: custom_path + in: path + required: true + schema: *ref_74 + responses: + '200': + description: custom path exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/apps/sign_s3_objects: + post: + summary: sign s3 objects, to be used by anonymous users in public apps + operationId: signS3Objects + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: s3 objects to sign + required: true + content: + application/json: + schema: + type: object + properties: + s3_objects: + type: array + items: + type: object + properties: &ref_75 + s3: + type: string + filename: + type: string + storage: + type: string + presigned: + type: string + required: &ref_76 + - s3 + required: + - s3_objects + responses: + '200': + description: signed s3 objects + content: + application/json: + schema: + type: array + items: + type: object + properties: *ref_75 + required: *ref_76 /w/{workspace}/apps_u/execute_component/{path}: post: summary: executeComponent @@ -7947,7 +9151,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 requestBody: description: update app required: true @@ -7960,6 +9164,8 @@ paths: type: string path: type: string + version: + type: integer args: {} raw_code: type: object @@ -7977,6 +9183,8 @@ paths: required: - content - language + id: + type: integer force_viewer_static_fields: type: object force_viewer_one_of_fields: @@ -7995,6 +9203,99 @@ paths: text/plain: schema: type: string + /w/{workspace}/apps_u/upload_s3_file/{path}: + post: + summary: upload s3 file from app + operationId: uploadS3FileFromApp + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + - name: file_key + in: query + required: false + schema: + type: string + - name: file_extension + in: query + required: false + schema: + type: string + - name: s3_resource_path + in: query + required: false + schema: + type: string + - name: resource_type + in: query + required: false + schema: + type: string + - name: storage + in: query + schema: + type: string + - name: content_type + in: query + schema: + type: string + - name: content_disposition + in: query + schema: + type: string + requestBody: + description: File content + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: file uploaded + content: + application/json: + schema: + type: object + properties: + file_key: + type: string + delete_token: + type: string + required: + - file_key + - delete_token + /w/{workspace}/apps_u/delete_s3_file: + delete: + summary: delete s3 file from app + operationId: deleteS3FileFromApp + tags: + - app + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: delete_token + in: query + required: true + schema: + type: string + responses: + '200': + description: file deleted + content: + text/plain: + schema: + type: string /w/{workspace}/jobs/run/f/{path}: post: summary: run flow by path @@ -8009,7 +9310,7 @@ paths: - name: path in: path required: true - schema: *ref_35 + schema: *ref_41 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -8031,11 +9332,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -8043,7 +9344,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -8052,7 +9353,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -8065,7 +9366,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '201': description: job created @@ -8074,6 +9375,66 @@ paths: schema: type: string format: uuid + /w/{workspace}/jobs/run/batch_rerun_jobs: + post: + summary: re-run multiple jobs + operationId: batchReRunJobs + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: list of job ids to re run and arg tranforms + required: true + content: + application/json: + schema: + type: object + required: + - job_ids + - script_options_by_path + - flow_options_by_path + properties: + job_ids: + type: array + items: + type: string + script_options_by_path: + type: object + additionalProperties: + type: object + properties: + input_transforms: + type: object + additionalProperties: + allOf: &ref_77 + - oneOf: *ref_35 + discriminator: *ref_36 + use_latest_version: + type: boolean + flow_options_by_path: + type: object + additionalProperties: + type: object + properties: + input_transforms: + type: object + additionalProperties: + allOf: *ref_77 + use_latest_version: + type: boolean + responses: + '201': + description: >- + stream of created job uuids separated by \n. Lines may start with + 'Error:' + content: + text/event-stream: + schema: + type: string /w/{workspace}/jobs/restart/f/{id}/from/{step_id}/{branch_or_iteration_n}: post: summary: restart a completed flow at a given step @@ -8088,7 +9449,7 @@ paths: - name: id in: path required: true - schema: &ref_91 + schema: &ref_110 type: string format: uuid - name: step_id @@ -8121,11 +9482,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -8133,7 +9494,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -8142,7 +9503,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -8155,7 +9516,7 @@ paths: application/json: schema: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 responses: '201': description: job created @@ -8178,7 +9539,7 @@ paths: - name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -8200,17 +9561,17 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_49 + schema: *ref_56 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -8218,7 +9579,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -8227,7 +9588,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -8267,7 +9628,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -8280,7 +9641,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 requestBody: description: preview required: true @@ -8288,33 +9649,19 @@ paths: application/json: schema: type: object - properties: &ref_156 + properties: &ref_221 content: type: string path: type: string + script_hash: + type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 language: type: string - enum: - - python3 - - deno - - go - - bash - - powershell - - postgresql - - mysql - - bigquery - - snowflake - - mssql - - graphql - - nativets - - bun - - php - - rust - - ansible + enum: *ref_43 tag: type: string kind: @@ -8327,7 +9674,7 @@ paths: type: boolean lock: type: string - required: &ref_157 + required: &ref_222 - args responses: '201': @@ -8365,11 +9712,11 @@ paths: application/json: schema: type: object - properties: &ref_158 + properties: &ref_223 args: type: object - additionalProperties: *ref_18 - required: &ref_159 + additionalProperties: *ref_21 + required: &ref_224 - args responses: '201': @@ -8402,31 +9749,15 @@ paths: type: array items: type: object - properties: &ref_217 + properties: &ref_312 raw_code: type: string path: type: string language: type: string - enum: - - python3 - - deno - - go - - bash - - powershell - - postgresql - - mysql - - bigquery - - snowflake - - mssql - - graphql - - nativets - - bun - - php - - rust - - ansible - required: &ref_218 + enum: *ref_43 + required: &ref_313 - raw_code - path - language @@ -8466,7 +9797,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -8479,7 +9810,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 requestBody: description: preview required: true @@ -8487,21 +9818,21 @@ paths: application/json: schema: type: object - properties: &ref_192 + properties: &ref_287 value: type: object - properties: *ref_66 - required: *ref_67 + properties: *ref_78 + required: *ref_79 path: type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 tag: type: string restarted_from: type: object - properties: &ref_194 + properties: &ref_289 flow_job_id: type: string format: uuid @@ -8509,7 +9840,7 @@ paths: type: string branch_or_iteration_n: type: integer - required: &ref_193 + required: &ref_288 - value - content - args @@ -8535,94 +9866,104 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 + - name: worker + description: worker this job was ran on + in: query + schema: &ref_82 + type: string - name: script_path_exact description: mask to filter exact matching path in: query - schema: &ref_70 + schema: &ref_83 type: string - name: script_path_start description: mask to filter matching starting path in: query - schema: &ref_71 + schema: &ref_84 type: string - name: schedule_path description: mask to filter by schedule path in: query - schema: &ref_72 + schema: &ref_85 type: string - name: script_hash description: mask to filter exact matching path in: query - schema: &ref_73 + schema: &ref_86 type: string - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: &ref_74 + schema: &ref_87 type: string format: date-time - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: &ref_75 + schema: &ref_88 type: string format: date-time - name: success description: filter on successful jobs in: query - schema: &ref_76 + schema: &ref_96 type: boolean - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: &ref_77 + schema: &ref_90 type: boolean - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: &ref_78 + schema: &ref_91 type: string - name: suspended description: filter on suspended jobs in: query - schema: &ref_79 + schema: &ref_92 type: boolean - name: running description: filter on running jobs in: query - schema: &ref_80 + schema: &ref_89 type: boolean - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: &ref_81 + schema: &ref_93 type: string - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: &ref_82 + schema: &ref_95 type: string + - name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: &ref_97 + type: boolean - name: tag description: filter on jobs with a given tag/worker group in: query - schema: &ref_83 + schema: &ref_94 type: string - name: page description: which page to return (start at 1, default 1) @@ -8653,7 +9994,7 @@ paths: type: array items: type: object - properties: &ref_89 + properties: &ref_108 workspace_id: type: string id: @@ -8681,7 +10022,7 @@ paths: type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 logs: type: string raw_code: @@ -8717,20 +10058,20 @@ paths: permissioned_as: type: string description: > - The user (u/userfoo) or group (g/groupfoo) whom + The user (u/userfoo) or group (g/groupfoo) whom the execution of this script will be permissioned_as and by extension its DT_TOKEN. flow_status: type: object - properties: &ref_84 + properties: &ref_99 step: type: integer modules: type: array items: type: object - properties: &ref_68 + properties: &ref_80 type: type: string enum: @@ -8807,20 +10148,20 @@ paths: format: uuid skipped: type: boolean - required: &ref_69 + required: &ref_81 - type user_states: additionalProperties: true preprocessor_module: allOf: - type: object - properties: *ref_68 - required: *ref_69 + properties: *ref_80 + required: *ref_81 failure_module: allOf: - type: object - properties: *ref_68 - required: *ref_69 + properties: *ref_80 + required: *ref_81 - type: object properties: parent_module: @@ -8835,35 +10176,19 @@ paths: items: type: string format: uuid - required: &ref_85 + required: &ref_100 - step - modules - failure_module raw_flow: type: object - properties: *ref_66 - required: *ref_67 + properties: *ref_78 + required: *ref_79 is_flow_step: type: boolean language: type: string - enum: - - python3 - - deno - - go - - bash - - powershell - - postgresql - - mysql - - bigquery - - snowflake - - mssql - - graphql - - nativets - - bun - - php - - rust - - ansible + enum: *ref_43 email: type: string visible_to_owner: @@ -8880,7 +10205,11 @@ paths: type: number suspend: type: number - required: &ref_90 + preprocessed: + type: boolean + worker: + type: string + required: &ref_109 - id - running - canceled @@ -8945,10 +10274,221 @@ paths: type: integer required: - database_length - /w/{workspace}/jobs/queue/list_filtered_uuids: + /w/{workspace}/jobs/completed/count_jobs: + get: + summary: count number of completed jobs with filter + operationId: countCompletedJobs + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: completed_after_s_ago + in: query + schema: + type: integer + - name: success + in: query + schema: + type: boolean + - name: tags + in: query + schema: + type: string + - name: all_workspaces + in: query + schema: + type: boolean + responses: + '200': + description: Count of completed jobs + content: + application/json: + schema: + type: integer + /w/{workspace}/jobs/list_filtered_uuids: get: summary: get the ids of all jobs matching the given filters - operationId: listFilteredUuids + operationId: listFilteredJobsUuids + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: created_by + description: mask to filter exact matching user creator + in: query + schema: *ref_60 + - name: label + description: >- + mask to filter exact matching job's label (job labels are completed + jobs with as a result an object containing a string in the array at + key 'wm_labels') + in: query + schema: &ref_98 + type: string + - name: worker + description: worker this job was ran on + in: query + schema: *ref_82 + - name: parent_job + description: >- + The parent job that is at the origin and responsible for the + execution of this script if any + in: query + schema: *ref_53 + - name: script_path_exact + description: mask to filter exact matching path + in: query + schema: *ref_83 + - name: script_path_start + description: mask to filter matching starting path + in: query + schema: *ref_84 + - name: schedule_path + description: mask to filter by schedule path + in: query + schema: *ref_85 + - name: script_hash + description: mask to filter exact matching path + in: query + schema: *ref_86 + - name: started_before + description: filter on started before (inclusive) timestamp + in: query + schema: *ref_87 + - name: started_after + description: filter on started after (exclusive) timestamp + in: query + schema: *ref_88 + - name: created_before + description: filter on created before (inclusive) timestamp + in: query + schema: &ref_101 + type: string + format: date-time + - name: created_after + description: filter on created after (exclusive) timestamp + in: query + schema: &ref_102 + type: string + format: date-time + - name: created_or_started_before + description: >- + filter on created_at for non non started job and started_at + otherwise before (inclusive) timestamp + in: query + schema: &ref_103 + type: string + format: date-time + - name: running + description: filter on running jobs + in: query + schema: *ref_89 + - name: scheduled_for_before_now + description: filter on jobs scheduled_for before now (hence waitinf for a worker) + in: query + schema: *ref_90 + - name: created_or_started_after + description: >- + filter on created_at for non non started job and started_at + otherwise after (exclusive) timestamp + in: query + schema: &ref_104 + type: string + format: date-time + - name: created_or_started_after_completed_jobs + description: >- + filter on created_at for non non started job and started_at + otherwise after (exclusive) timestamp but only for the completed + jobs + in: query + schema: &ref_105 + type: string + format: date-time + - name: job_kinds + description: >- + filter on job kind (values 'preview', 'script', 'dependencies', + 'flow') separated by, + in: query + schema: *ref_91 + - name: suspended + description: filter on suspended jobs + in: query + schema: *ref_92 + - name: args + description: >- + filter on jobs containing those args as a json subset (@> in + postgres) + in: query + schema: *ref_93 + - name: tag + description: filter on jobs with a given tag/worker group + in: query + schema: *ref_94 + - name: result + description: >- + filter on jobs containing those result as a json subset (@> in + postgres) + in: query + schema: *ref_95 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: is_skipped + description: is the job skipped + in: query + schema: + type: boolean + - name: is_flow_step + description: is the job a flow step + in: query + schema: + type: boolean + - name: has_null_parent + description: has null parent + in: query + schema: + type: boolean + - name: success + description: filter on successful jobs + in: query + schema: + type: boolean + - name: all_workspaces + description: >- + get jobs from all workspaces (only valid if request come from the + `admins` workspace) + in: query + schema: + type: boolean + - name: is_not_schedule + description: is not a scheduled job + in: query + schema: + type: boolean + responses: + '200': + description: uuids of jobs + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/jobs/queue/list_filtered_uuids: + get: + summary: get the ids of all queued jobs matching the given filters + operationId: listFilteredQueueUuids tags: - job parameters: @@ -8959,79 +10499,83 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_70 + schema: *ref_83 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_71 + schema: *ref_84 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_72 + schema: *ref_85 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_73 + schema: *ref_86 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_74 + schema: *ref_87 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_75 + schema: *ref_88 - name: success description: filter on successful jobs in: query - schema: *ref_76 + schema: *ref_96 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_77 + schema: *ref_90 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_78 + schema: *ref_91 - name: suspended description: filter on suspended jobs in: query - schema: *ref_79 + schema: *ref_92 - name: running description: filter on running jobs in: query - schema: *ref_80 + schema: *ref_89 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_82 + schema: *ref_95 + - name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: *ref_97 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_83 + schema: *ref_94 - name: page description: which page to return (start at 1, default 1) in: query @@ -9109,75 +10653,82 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: &ref_86 - type: string + schema: *ref_98 + - name: worker + description: worker this job was ran on + in: query + schema: *ref_82 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_70 + schema: *ref_83 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_71 + schema: *ref_84 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_72 + schema: *ref_85 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_73 + schema: *ref_86 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_74 + schema: *ref_87 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_75 + schema: *ref_88 - name: success description: filter on successful jobs in: query - schema: *ref_76 + schema: *ref_96 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_78 + schema: *ref_91 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_82 + schema: *ref_95 + - name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: *ref_97 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_83 + schema: *ref_94 - name: page description: which page to return (start at 1, default 1) in: query @@ -9215,7 +10766,7 @@ paths: type: array items: type: object - properties: &ref_87 + properties: &ref_106 workspace_id: type: string id: @@ -9242,7 +10793,7 @@ paths: type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 result: {} logs: type: string @@ -9278,39 +10829,23 @@ paths: permissioned_as: type: string description: > - The user (u/userfoo) or group (g/groupfoo) whom + The user (u/userfoo) or group (g/groupfoo) whom the execution of this script will be permissioned_as and by extension its DT_TOKEN. flow_status: type: object - properties: *ref_84 - required: *ref_85 + properties: *ref_99 + required: *ref_100 raw_flow: type: object - properties: *ref_66 - required: *ref_67 + properties: *ref_78 + required: *ref_79 is_flow_step: type: boolean language: type: string - enum: - - python3 - - deno - - go - - bash - - powershell - - postgresql - - mysql - - bigquery - - snowflake - - mssql - - graphql - - nativets - - bun - - php - - rust - - ansible + enum: *ref_43 is_skipped: type: boolean email: @@ -9331,7 +10866,11 @@ paths: type: number aggregate_wait_time_ms: type: number - required: &ref_88 + preprocessed: + type: boolean + worker: + type: string + required: &ref_107 - id - created_by - duration_ms @@ -9360,115 +10899,113 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_86 + schema: *ref_98 + - name: worker + description: worker this job was ran on + in: query + schema: *ref_82 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_70 + schema: *ref_83 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_71 + schema: *ref_84 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_72 + schema: *ref_85 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_73 + schema: *ref_86 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_74 + schema: *ref_87 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_75 + schema: *ref_88 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: &ref_128 - type: string - format: date-time + schema: *ref_101 - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: &ref_129 - type: string - format: date-time + schema: *ref_102 - name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: &ref_122 - type: string - format: date-time + schema: *ref_103 - name: running description: filter on running jobs in: query - schema: *ref_80 + schema: *ref_89 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_77 + schema: *ref_90 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: &ref_123 - type: string - format: date-time + schema: *ref_104 - name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: &ref_124 - type: string - format: date-time + schema: *ref_105 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_78 + schema: *ref_91 - name: suspended description: filter on suspended jobs in: query - schema: *ref_79 + schema: *ref_92 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_83 + schema: *ref_94 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_82 + schema: *ref_95 + - name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: *ref_97 - name: page description: which page to return (start at 1, default 1) in: query @@ -9517,11 +11054,11 @@ paths: schema: type: array items: - oneOf: &ref_92 + oneOf: &ref_111 - allOf: - type: object - properties: *ref_87 - required: *ref_88 + properties: *ref_106 + required: *ref_107 - type: object properties: type: @@ -9530,15 +11067,15 @@ paths: - CompletedJob - allOf: - type: object - properties: *ref_89 - required: *ref_90 + properties: *ref_108 + required: *ref_109 - type: object properties: type: type: string enum: - QueuedJob - discriminator: &ref_93 + discriminator: &ref_112 propertyName: type /jobs/db_clock: get: @@ -9605,7 +11142,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: no_logs in: query schema: @@ -9616,8 +11153,8 @@ paths: content: application/json: schema: - oneOf: *ref_92 - discriminator: *ref_93 + oneOf: *ref_111 + discriminator: *ref_112 /w/{workspace}/jobs_u/get_root_job_id/{id}: get: summary: get root job id @@ -9632,7 +11169,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: get root job id @@ -9654,7 +11191,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: job details @@ -9676,7 +11213,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: job args @@ -9697,7 +11234,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: running in: query schema: @@ -9732,9 +11269,9 @@ paths: type: integer flow_status: type: object - additionalProperties: &ref_160 + additionalProperties: &ref_225 type: object - properties: &ref_161 + properties: &ref_226 scheduled_for: type: string format: date-time @@ -9766,7 +11303,8 @@ paths: description: job log content: text/plain: - type: string + schema: + type: string /w/{workspace}/jobs_u/get_flow_debug_info/{id}: get: summary: get flow debug info @@ -9781,7 +11319,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: flow debug info details @@ -9802,7 +11340,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: job details @@ -9810,8 +11348,8 @@ paths: application/json: schema: type: object - properties: *ref_87 - required: *ref_88 + properties: *ref_106 + required: *ref_107 /w/{workspace}/jobs_u/completed/get_result/{id}: get: summary: get completed job result @@ -9826,7 +11364,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: suspended_job in: query schema: @@ -9863,10 +11401,10 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: get_started in: query - schema: &ref_135 + schema: &ref_194 type: boolean responses: '200': @@ -9900,7 +11438,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: job details @@ -9908,8 +11446,8 @@ paths: application/json: schema: type: object - properties: *ref_87 - required: *ref_88 + properties: *ref_106 + required: *ref_107 /w/{workspace}/jobs_u/queue/cancel/{id}: post: summary: cancel queued or running job @@ -9924,7 +11462,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 requestBody: description: reason required: true @@ -9956,7 +11494,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: reason required: true @@ -9988,7 +11526,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 requestBody: description: reason required: true @@ -10020,7 +11558,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10051,7 +11589,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10079,6 +11617,108 @@ paths: - approvalPage - resume - cancel + /w/{workspace}/jobs/slack_approval/{id}: + get: + summary: generate interactive slack approval for suspended job + operationId: getSlackApprovalPayload + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: id + in: path + required: true + schema: *ref_110 + - name: approver + in: query + schema: + type: string + - name: message + in: query + schema: + type: string + - name: slack_resource_path + in: query + required: true + schema: + type: string + - name: channel_id + in: query + required: true + schema: + type: string + - name: flow_step_id + in: query + required: true + schema: + type: string + - name: default_args_json + in: query + required: false + schema: + type: string + - name: dynamic_enums_json + in: query + required: false + schema: + type: string + responses: + '200': + description: Interactive slack approval message sent successfully + /w/{workspace}/jobs/teams_approval/{id}: + get: + summary: generate interactive teams approval for suspended job + operationId: getTeamsApprovalPayload + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: id + in: path + required: true + schema: *ref_110 + - name: approver + in: query + schema: + type: string + - name: message + in: query + schema: + type: string + - name: team_name + in: query + required: true + schema: + type: string + - name: channel_name + in: query + required: true + schema: + type: string + - name: flow_step_id + in: query + required: true + schema: + type: string + - name: default_args_json + in: query + required: false + schema: + type: string + - name: dynamic_enums_json + in: query + required: false + schema: + type: string + responses: + '200': + description: Interactive slack approval message sent successfully /w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}: get: summary: resume a job for a suspended flow @@ -10093,7 +11733,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -10101,7 +11741,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_94 + schema: *ref_113 - name: resume_id in: path required: true @@ -10136,7 +11776,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10178,7 +11818,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: key in: path required: true @@ -10210,7 +11850,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: key in: path required: true @@ -10236,7 +11876,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 requestBody: required: true content: @@ -10264,7 +11904,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10299,7 +11939,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10341,7 +11981,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 - name: resume_id in: path required: true @@ -10365,8 +12005,8 @@ paths: type: object properties: job: - oneOf: *ref_92 - discriminator: *ref_93 + oneOf: *ref_111 + discriminator: *ref_112 approvers: type: array items: @@ -10400,6 +12040,8 @@ paths: type: string timezone: type: string + cron_version: + type: string required: - schedule - timezone @@ -10431,7 +12073,7 @@ paths: application/json: schema: type: object - properties: &ref_171 + properties: &ref_236 path: type: string schedule: @@ -10444,7 +12086,7 @@ paths: type: boolean args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 enabled: type: boolean on_failure: @@ -10455,34 +12097,38 @@ paths: type: boolean on_failure_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_recovery: type: string on_recovery_times: type: number on_recovery_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_success: type: string on_success_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 ws_error_handler_muted: type: boolean retry: type: object - properties: *ref_95 + properties: *ref_114 no_flow_overlap: type: boolean summary: type: string + description: + type: string tag: type: string paused_until: type: string format: date-time - required: &ref_172 + cron_version: + type: string + required: &ref_237 - path - schedule - timezone @@ -10510,7 +12156,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated schedule required: true @@ -10518,14 +12164,14 @@ paths: application/json: schema: type: object - properties: &ref_173 + properties: &ref_238 schedule: type: string timezone: type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_failure: type: string on_failure_times: @@ -10534,34 +12180,38 @@ paths: type: boolean on_failure_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_recovery: type: string on_recovery_times: type: number on_recovery_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_success: type: string on_success_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 ws_error_handler_muted: type: boolean retry: type: object - properties: *ref_95 + properties: *ref_114 no_flow_overlap: type: boolean summary: type: string + description: + type: string tag: type: string paused_until: type: string format: date-time - required: &ref_174 + cron_version: + type: string + required: &ref_239 - schedule - timezone - script_path @@ -10588,7 +12238,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated schedule enable required: true @@ -10622,7 +12272,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: schedule deleted @@ -10644,7 +12294,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: schedule deleted @@ -10652,7 +12302,7 @@ paths: application/json: schema: type: object - properties: &ref_96 + properties: &ref_115 path: type: string edited_by: @@ -10672,7 +12322,7 @@ paths: type: boolean args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 extra_perms: type: object additionalProperties: @@ -10689,26 +12339,28 @@ paths: type: boolean on_failure_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_recovery: type: string on_recovery_times: type: number on_recovery_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 on_success: type: string on_success_extra_args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 ws_error_handler_muted: type: boolean retry: type: object - properties: *ref_95 + properties: *ref_114 summary: type: string + description: + type: string no_flow_overlap: type: boolean tag: @@ -10716,7 +12368,9 @@ paths: paused_until: type: string format: date-time - required: &ref_97 + cron_version: + type: string + required: &ref_116 - path - edited_by - edited_at @@ -10741,7 +12395,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: schedule exists @@ -10773,7 +12427,7 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 - name: path description: filter by path in: query @@ -10796,8 +12450,8 @@ paths: type: array items: type: object - properties: *ref_96 - required: *ref_97 + properties: *ref_115 + required: *ref_116 /w/{workspace}/schedules/list_with_jobs: get: summary: list schedules with last 20 jobs @@ -10825,10 +12479,10 @@ paths: schema: type: array items: - allOf: &ref_170 + allOf: &ref_235 - type: object - properties: *ref_96 - required: *ref_97 + properties: *ref_115 + required: *ref_116 - type: object properties: jobs: @@ -10907,13 +12561,15 @@ paths: application/json: schema: type: object - properties: &ref_175 + properties: &ref_240 path: type: string script_path: type: string route_path: type: string + workspaced_route: + type: boolean static_asset_config: type: object properties: @@ -10935,18 +12591,34 @@ paths: - put - delete - patch + authentication_resource_path: + type: string is_async: type: boolean - requires_auth: + authentication_method: + type: string + enum: &ref_117 + - none + - windmill + - api_key + - basic_http + - custom_script + - signature + is_static_website: type: boolean - required: &ref_176 + wrap_body: + type: boolean + raw_string: + type: boolean + required: &ref_241 - path - script_path - route_path - is_flow - is_async - - requires_auth + - authentication_method - http_method + - is_static_website responses: '201': description: http trigger created @@ -10968,7 +12640,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated trigger required: true @@ -10976,13 +12648,15 @@ paths: application/json: schema: type: object - properties: &ref_177 + properties: &ref_242 path: type: string script_path: type: string route_path: type: string + workspaced_route: + type: boolean static_asset_config: type: object properties: @@ -10994,6 +12668,8 @@ paths: type: string required: - s3 + authentication_resource_path: + type: string is_flow: type: boolean http_method: @@ -11006,16 +12682,24 @@ paths: - patch is_async: type: boolean - requires_auth: + authentication_method: + type: string + enum: *ref_117 + is_static_website: type: boolean - required: &ref_178 + wrap_body: + type: boolean + raw_string: + type: boolean + required: &ref_243 - path - script_path - is_flow - kind - is_async - - requires_auth + - authentication_method - http_method + - is_static_website responses: '200': description: http trigger updated @@ -11037,7 +12721,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: http trigger deleted @@ -11059,24 +12743,46 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: http trigger deleted content: application/json: schema: + allOf: &ref_118 + - type: object + properties: &ref_122 + path: + type: string + script_path: + type: string + email: + type: string + extra_perms: + type: object + additionalProperties: + type: boolean + workspace_id: + type: string + edited_by: + type: string + edited_at: + type: string + format: date-time + is_flow: + type: boolean + required: &ref_123 + - path + - script_path + - email + - extra_perms + - workspace_id + - edited_by + - edited_at + - is_flow type: object - properties: &ref_98 - path: - type: string - edited_by: - type: string - edited_at: - type: string - format: date-time - script_path: - type: string + properties: &ref_119 route_path: type: string static_asset_config: @@ -11090,16 +12796,6 @@ paths: type: string required: - s3 - is_flow: - type: boolean - extra_perms: - type: object - additionalProperties: - type: boolean - email: - type: string - workspace_id: - type: string http_method: type: string enum: @@ -11108,23 +12804,30 @@ paths: - put - delete - patch + authentication_resource_path: + type: string is_async: type: boolean - requires_auth: + authentication_method: + type: string + enum: *ref_117 + is_static_website: type: boolean - required: &ref_99 - - path - - edited_by - - edited_at - - script_path + workspaced_route: + type: boolean + wrap_body: + type: boolean + raw_string: + type: boolean + required: &ref_120 - route_path - - extra_perms - - is_flow - - email - - workspace_id - is_async - - requires_auth + - authentication_method - http_method + - is_static_website + - workspaced_route + - wrap_body + - raw_string /w/{workspace}/http_triggers/list: get: summary: list http triggers @@ -11165,9 +12868,10 @@ paths: schema: type: array items: + allOf: *ref_118 type: object - properties: *ref_98 - required: *ref_99 + properties: *ref_119 + required: *ref_120 /w/{workspace}/http_triggers/exists/{path}: get: summary: does http trigger exists @@ -11182,7 +12886,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: http trigger exists @@ -11219,8 +12923,11 @@ paths: - put - delete - patch + trigger_path: + type: string + workspaced_route: + type: boolean required: - - kind - route_path - http_method responses: @@ -11248,7 +12955,7 @@ paths: application/json: schema: type: object - properties: &ref_179 + properties: &ref_244 path: type: string script_path: @@ -11273,7 +12980,7 @@ paths: initial_messages: type: array items: - anyOf: &ref_100 + anyOf: &ref_121 - type: object properties: raw_message: @@ -11289,7 +12996,7 @@ paths: type: string args: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 is_flow: type: boolean required: @@ -11300,15 +13007,16 @@ paths: - runnable_result url_runnable_args: type: object - additionalProperties: *ref_18 - required: &ref_180 + additionalProperties: *ref_21 + can_return_message: + type: boolean + required: &ref_245 - path - script_path - url - is_flow - filters - - initial_messages - - url_runnable_args + - can_return_message responses: '201': description: websocket trigger created @@ -11330,7 +13038,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated trigger required: true @@ -11338,7 +13046,7 @@ paths: application/json: schema: type: object - properties: &ref_181 + properties: &ref_246 url: type: string path: @@ -11361,18 +13069,19 @@ paths: initial_messages: type: array items: - anyOf: *ref_100 + anyOf: *ref_121 url_runnable_args: type: object - additionalProperties: *ref_18 - required: &ref_182 + additionalProperties: *ref_21 + can_return_message: + type: boolean + required: &ref_247 - path - script_path - url - is_flow - filters - - initial_messages - - url_runnable_args + - can_return_message responses: '200': description: websocket trigger updated @@ -11394,7 +13103,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: websocket trigger deleted @@ -11416,36 +13125,21 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: websocket trigger deleted content: application/json: schema: + allOf: &ref_124 + - type: object + properties: *ref_122 + required: *ref_123 type: object - properties: &ref_101 - path: - type: string - edited_by: - type: string - edited_at: - type: string - format: date-time - script_path: - type: string + properties: &ref_125 url: type: string - is_flow: - type: boolean - extra_perms: - type: object - additionalProperties: - type: boolean - email: - type: string - workspace_id: - type: string server_id: type: string last_server_ping: @@ -11469,24 +13163,17 @@ paths: initial_messages: type: array items: - anyOf: *ref_100 + anyOf: *ref_121 url_runnable_args: type: object - additionalProperties: *ref_18 - required: &ref_102 - - path - - edited_by - - edited_at - - script_path + additionalProperties: *ref_21 + can_return_message: + type: boolean + required: &ref_126 - url - - extra_perms - - is_flow - - email - - workspace_id - enabled - filters - - initial_messages - - url_runnable_args + - can_return_message /w/{workspace}/websocket_triggers/list: get: summary: list websocket triggers @@ -11527,9 +13214,10 @@ paths: schema: type: array items: + allOf: *ref_124 type: object - properties: *ref_101 - required: *ref_102 + properties: *ref_125 + required: *ref_126 /w/{workspace}/websocket_triggers/exists/{path}: get: summary: does websocket trigger exists @@ -11544,7 +13232,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: websocket trigger exists @@ -11566,7 +13254,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 requestBody: description: updated websocket trigger enable required: true @@ -11586,6 +13274,2400 @@ paths: text/plain: schema: type: string + /w/{workspace}/websocket_triggers/test: + post: + summary: test websocket connection + operationId: testWebsocketConnection + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test websocket connection + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + url_runnable_args: + type: object + additionalProperties: *ref_21 + can_return_message: + type: boolean + required: + - url + - can_return_message + responses: + '200': + description: successfuly connected to websocket + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/create: + post: + summary: create kafka trigger + operationId: createKafkaTrigger + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new kafka trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_270 + path: + type: string + script_path: + type: string + is_flow: + type: boolean + kafka_resource_path: + type: string + group_id: + type: string + topics: + type: array + items: + type: string + enabled: + type: boolean + required: &ref_271 + - path + - script_path + - is_flow + - kafka_resource_path + - group_id + - topics + responses: + '201': + description: kafka trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/update/{path}: + post: + summary: update kafka trigger + operationId: updateKafkaTrigger + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_272 + kafka_resource_path: + type: string + group_id: + type: string + topics: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + required: &ref_273 + - path + - script_path + - kafka_resource_path + - group_id + - topics + - is_flow + responses: + '200': + description: kafka trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/delete/{path}: + delete: + summary: delete kafka trigger + operationId: deleteKafkaTrigger + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: kafka trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/get/{path}: + get: + summary: get kafka trigger + operationId: getKafkaTrigger + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: kafka trigger deleted + content: + application/json: + schema: + allOf: &ref_127 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_128 + kafka_resource_path: + type: string + group_id: + type: string + topics: + type: array + items: + type: string + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: &ref_129 + - kafka_resource_path + - group_id + - topics + - enabled + /w/{workspace}/kafka_triggers/list: + get: + summary: list kafka triggers + operationId: listKafkaTriggers + tags: + - kafka_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: kafka trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_127 + type: object + properties: *ref_128 + required: *ref_129 + /w/{workspace}/kafka_triggers/exists/{path}: + get: + summary: does kafka trigger exists + operationId: existsKafkaTrigger + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: kafka trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/kafka_triggers/setenabled/{path}: + post: + summary: set enabled kafka trigger + operationId: setKafkaTriggerEnabled + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated kafka trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: kafka trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/test: + post: + summary: test kafka connection + operationId: testKafkaConnection + tags: + - kafka_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test kafka connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + '200': + description: successfuly connected to kafka brokers + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/create: + post: + summary: create nats trigger + operationId: createNatsTrigger + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new nats trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_274 + path: + type: string + script_path: + type: string + is_flow: + type: boolean + nats_resource_path: + type: string + use_jetstream: + type: boolean + stream_name: + type: string + consumer_name: + type: string + subjects: + type: array + items: + type: string + enabled: + type: boolean + required: &ref_275 + - path + - script_path + - is_flow + - nats_resource_path + - use_jetstream + - subjects + responses: + '201': + description: nats trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/update/{path}: + post: + summary: update nats trigger + operationId: updateNatsTrigger + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_276 + nats_resource_path: + type: string + use_jetstream: + type: boolean + stream_name: + type: string + consumer_name: + type: string + subjects: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + required: &ref_277 + - path + - script_path + - nats_resource_path + - use_jetstream + - subjects + - is_flow + responses: + '200': + description: nats trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/delete/{path}: + delete: + summary: delete nats trigger + operationId: deleteNatsTrigger + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: nats trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/get/{path}: + get: + summary: get nats trigger + operationId: getNatsTrigger + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: nats trigger deleted + content: + application/json: + schema: + allOf: &ref_130 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_131 + nats_resource_path: + type: string + use_jetstream: + type: boolean + stream_name: + type: string + consumer_name: + type: string + subjects: + type: array + items: + type: string + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: &ref_132 + - nats_resource_path + - use_jetstream + - subjects + - enabled + /w/{workspace}/nats_triggers/list: + get: + summary: list nats triggers + operationId: listNatsTriggers + tags: + - nats_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: nats trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_130 + type: object + properties: *ref_131 + required: *ref_132 + /w/{workspace}/nats_triggers/exists/{path}: + get: + summary: does nats trigger exists + operationId: existsNatsTrigger + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: nats trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/nats_triggers/setenabled/{path}: + post: + summary: set enabled nats trigger + operationId: setNatsTriggerEnabled + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated nats trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: nats trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/test: + post: + summary: test NATS connection + operationId: testNatsConnection + tags: + - nats_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test nats connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + '200': + description: successfuly connected to NATS servers + content: + text/plain: + schema: + type: string + /w/{workspace}/sqs_triggers/create: + post: + summary: create sqs trigger + operationId: createSqsTrigger + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new sqs trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_257 + queue_url: + type: string + aws_auth_resource_type: + type: string + enum: &ref_133 + - oidc + - credentials + aws_resource_path: + type: string + message_attributes: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: &ref_258 + - queue_url + - aws_resource_path + - path + - script_path + - is_flow + - aws_auth_resource_type + responses: + '201': + description: sqs trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/sqs_triggers/update/{path}: + post: + summary: update sqs trigger + operationId: updateSqsTrigger + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_259 + queue_url: + type: string + aws_auth_resource_type: + type: string + enum: *ref_133 + aws_resource_path: + type: string + message_attributes: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: &ref_260 + - queue_url + - aws_resource_path + - path + - script_path + - is_flow + - enabled + - aws_auth_resource_type + responses: + '200': + description: sqs trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/sqs_triggers/delete/{path}: + delete: + summary: delete sqs trigger + operationId: deleteSqsTrigger + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: sqs trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/sqs_triggers/get/{path}: + get: + summary: get sqs trigger + operationId: getSqsTrigger + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: sqs trigger deleted + content: + application/json: + schema: + allOf: &ref_134 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_135 + queue_url: + type: string + aws_auth_resource_type: + type: string + enum: *ref_133 + aws_resource_path: + type: string + message_attributes: + type: array + items: + type: string + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: &ref_136 + - queue_url + - aws_resource_path + - enabled + - aws_auth_resource_type + /w/{workspace}/sqs_triggers/list: + get: + summary: list sqs triggers + operationId: listSqsTriggers + tags: + - sqs_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: sqs trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_134 + type: object + properties: *ref_135 + required: *ref_136 + /w/{workspace}/sqs_triggers/exists/{path}: + get: + summary: does sqs trigger exists + operationId: existsSqsTrigger + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: sqs trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/sqs_triggers/setenabled/{path}: + post: + summary: set enabled sqs trigger + operationId: setSqsTriggerEnabled + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated sqs trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: sqs trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/sqs_triggers/test: + post: + summary: test sqs connection + operationId: testSqsConnection + tags: + - sqs_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test sqs connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + '200': + description: successfuly connected to sqs + content: + text/plain: + schema: + type: string + /w/{workspace}/mqtt_triggers/create: + post: + summary: create mqtt trigger + operationId: createMqttTrigger + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new mqtt trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_249 + mqtt_resource_path: + type: string + subscribe_topics: + type: array + items: + type: object + properties: &ref_137 + qos: + type: string + enum: &ref_248 + - qos0 + - qos1 + - qos2 + topic: + type: string + required: &ref_138 + - qos + - topic + client_id: + type: string + v3_config: + type: object + properties: &ref_139 + clean_session: + type: boolean + v5_config: + type: object + properties: &ref_140 + clean_start: + type: boolean + topic_alias: + type: number + session_expiry_interval: + type: number + client_version: + type: string + enum: &ref_141 + - v3 + - v5 + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: &ref_250 + - path + - script_path + - is_flow + - subscribe_topics + - mqtt_resource_path + responses: + '201': + description: mqtt trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/mqtt_triggers/update/{path}: + post: + summary: update mqtt trigger + operationId: updateMqttTrigger + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_251 + mqtt_resource_path: + type: string + subscribe_topics: + type: array + items: + type: object + properties: *ref_137 + required: *ref_138 + client_id: + type: string + v3_config: + type: object + properties: *ref_139 + v5_config: + type: object + properties: *ref_140 + client_version: + type: string + enum: *ref_141 + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: &ref_252 + - path + - script_path + - is_flow + - enabled + - subscribe_topics + - mqtt_resource_path + responses: + '200': + description: mqtt trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/mqtt_triggers/delete/{path}: + delete: + summary: delete mqtt trigger + operationId: deleteMqttTrigger + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: mqtt trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/mqtt_triggers/get/{path}: + get: + summary: get mqtt trigger + operationId: getMqttTrigger + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: mqtt trigger deleted + content: + application/json: + schema: + allOf: &ref_142 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_143 + mqtt_resource_path: + type: string + subscribe_topics: + type: array + items: + type: object + properties: *ref_137 + required: *ref_138 + v3_config: + type: object + properties: *ref_139 + v5_config: + type: object + properties: *ref_140 + client_id: + type: string + client_version: + type: string + enum: *ref_141 + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: &ref_144 + - enabled + - subscribe_topics + - mqtt_resource_path + /w/{workspace}/mqtt_triggers/list: + get: + summary: list mqtt triggers + operationId: listMqttTriggers + tags: + - mqtt_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: mqtt trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_142 + type: object + properties: *ref_143 + required: *ref_144 + /w/{workspace}/mqtt_triggers/exists/{path}: + get: + summary: does mqtt trigger exists + operationId: existsMqttTrigger + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: mqtt trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/mqtt_triggers/setenabled/{path}: + post: + summary: set enabled mqtt trigger + operationId: setMqttTriggerEnabled + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated mqtt trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: mqtt trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/mqtt_triggers/test: + post: + summary: test mqtt connection + operationId: testMqttConnection + tags: + - mqtt_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test mqtt connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + '200': + description: successfully connected to mqtt + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/create: + post: + summary: create gcp trigger + operationId: createGcpTrigger + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new gcp trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_145 + gcp_resource_path: + type: string + subscription_mode: + type: string + enum: &ref_150 + - existing + - create_update + description: >- + The mode of subscription. 'existing' means using an existing + GCP subscription, while 'create_update' involves creating or + updating a new subscription. + topic_id: + type: string + subscription_id: + type: string + base_endpoint: + type: string + delivery_type: + type: string + enum: &ref_147 + - push + - pull + delivery_config: + type: object + properties: &ref_148 + audience: + type: string + authenticate: + type: boolean + required: &ref_149 + - authenticate + - base_endpoint + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: &ref_146 + - path + - script_path + - is_flow + - gcp_resource_path + - topic_id + - subscription_mode + responses: + '201': + description: gcp trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/update/{path}: + post: + summary: update gcp trigger + operationId: updateGcpTrigger + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: *ref_145 + required: *ref_146 + responses: + '200': + description: gcp trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/delete/{path}: + delete: + summary: delete gcp trigger + operationId: deleteGcpTrigger + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: gcp trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/get/{path}: + get: + summary: get gcp trigger + operationId: getGcpTrigger + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: gcp trigger deleted + content: + application/json: + schema: + allOf: &ref_151 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_152 + gcp_resource_path: + type: string + topic_id: + type: string + subscription_id: + type: string + server_id: + type: string + delivery_type: + type: string + enum: *ref_147 + delivery_config: + type: object + properties: *ref_148 + required: *ref_149 + subscription_mode: + type: string + enum: *ref_150 + description: >- + The mode of subscription. 'existing' means using an + existing GCP subscription, while 'create_update' involves + creating or updating a new subscription. + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: &ref_153 + - gcp_resource_path + - topic_id + - subscription_id + - enabled + - delivery_type + - subscription_mode + /w/{workspace}/gcp_triggers/list: + get: + summary: list gcp triggers + operationId: listGcpTriggers + tags: + - gcp_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: gcp trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_151 + type: object + properties: *ref_152 + required: *ref_153 + /w/{workspace}/gcp_triggers/exists/{path}: + get: + summary: does gcp trigger exists + operationId: existsGcpTrigger + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: gcp trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/gcp_triggers/setenabled/{path}: + post: + summary: set enabled gcp trigger + operationId: setGcpTriggerEnabled + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated gcp trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: gcp trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/test: + post: + summary: test gcp connection + operationId: testGcpConnection + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test gcp connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + '200': + description: try to connect to a gcp broker + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/subscriptions/delete/{path}: + delete: + summary: delete gcp trigger + operationId: deleteGcpSubscription + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: args to delete subscription from google cloud + required: true + content: + application/json: + schema: + type: object + properties: &ref_255 + subscription_id: + type: string + required: &ref_256 + - subscription_id + responses: + '200': + description: gcp trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/topics/list/{path}: + get: + summary: list all topics of google cloud service + operationId: listGoogleTopics + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: get all google topics + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/gcp_triggers/subscriptions/list/{path}: + post: + summary: list all subscription of a give topic from google cloud service + operationId: listAllTGoogleTopicSubscriptions + tags: + - gcp_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: args to get subscription's topic from google cloud + required: true + content: + application/json: + schema: + type: object + properties: &ref_253 + topic_id: + type: string + required: &ref_254 + - topic_id + responses: + '200': + description: get all google topic subscriptions name + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}: + get: + summary: check if postgres configuration is set to logical + operationId: isValidPostgresConfiguration + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: boolean that indicates if postgres is set to logical level or not + content: + application/json: + schema: + type: boolean + /w/{workspace}/postgres_triggers/create_template_script: + post: + summary: create template script + operationId: createTemplateScript + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: template script + required: true + content: + application/json: + schema: + type: object + properties: &ref_264 + postgres_resource_path: + type: string + relations: + type: array + items: + type: object + properties: &ref_155 + schema_name: + type: string + table_to_track: + type: array + items: &ref_262 + type: object + properties: + table_name: + type: string + columns_name: + type: array + items: + type: string + where_clause: + type: string + required: + - table_name + required: &ref_156 + - schema_name + - table_to_track + language: + type: string + enum: &ref_263 + - Typescript + required: &ref_265 + - postgres_resource_path + - relations + - language + responses: + '200': + description: custom id to retrieve template script + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/get_template_script/{id}: + get: + summary: get template script + operationId: getTemplateScript + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: id + in: path + required: true + schema: &ref_185 + type: string + responses: + '200': + description: template script + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/slot/list/{path}: + get: + summary: list postgres replication slot + operationId: listPostgresReplicationSlot + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: list postgres slot + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_261 + slot_name: + type: string + active: + type: boolean + /w/{workspace}/postgres_triggers/slot/create/{path}: + post: + summary: create replication slot for postgres + operationId: createPostgresReplicationSlot + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: new slot for postgres + required: true + content: + application/json: + schema: + type: object + properties: &ref_154 + name: + type: string + responses: + '201': + description: slot created + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/slot/delete/{path}: + delete: + summary: delete postgres replication slot + operationId: deletePostgresReplicationSlot + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: replication slot of postgres + required: true + content: + application/json: + schema: + type: object + properties: *ref_154 + responses: + '200': + description: postgres replication slot deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/publication/list/{path}: + get: + summary: list postgres publication + operationId: listPostgresPublication + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: database publication list + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/postgres_triggers/publication/get/{publication}/{path}: + get: + summary: get postgres publication + operationId: getPostgresPublication + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + - name: publication + in: path + required: true + schema: &ref_157 + type: string + responses: + '200': + description: postgres publication get + content: + application/json: + schema: + type: object + properties: &ref_158 + table_to_track: + type: array + items: + type: object + properties: *ref_155 + required: *ref_156 + transaction_to_track: + type: array + items: + type: string + required: &ref_159 + - transaction_to_track + /w/{workspace}/postgres_triggers/publication/create/{publication}/{path}: + post: + summary: create publication for postgres + operationId: createPostgresPublication + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + - name: publication + in: path + required: true + schema: *ref_157 + requestBody: + description: new publication for postgres + required: true + content: + application/json: + schema: + type: object + properties: *ref_158 + required: *ref_159 + responses: + '201': + description: publication created + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/publication/update/{publication}/{path}: + post: + summary: update publication for postgres + operationId: updatePostgresPublication + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + - name: publication + in: path + required: true + schema: *ref_157 + requestBody: + description: update publication for postgres + required: true + content: + application/json: + schema: + type: object + properties: *ref_158 + required: *ref_159 + responses: + '201': + description: publication updated + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/publication/delete/{publication}/{path}: + delete: + summary: delete postgres publication + operationId: deletePostgresPublication + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + - name: publication + in: path + required: true + schema: *ref_157 + responses: + '200': + description: postgres publication deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/create: + post: + summary: create postgres trigger + operationId: createPostgresTrigger + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new postgres trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_266 + replication_slot_name: + type: string + publication_name: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + postgres_resource_path: + type: string + publication: + type: object + properties: *ref_158 + required: *ref_159 + required: &ref_267 + - path + - script_path + - is_flow + - enabled + - postgres_resource_path + responses: + '201': + description: postgres trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/update/{path}: + post: + summary: update postgres trigger + operationId: updatePostgresTrigger + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_268 + replication_slot_name: + type: string + publication_name: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + postgres_resource_path: + type: string + publication: + type: object + properties: *ref_158 + required: *ref_159 + required: &ref_269 + - path + - script_path + - is_flow + - enabled + - postgres_resource_path + - publication_name + - replication_slot_name + responses: + '200': + description: postgres trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/delete/{path}: + delete: + summary: delete postgres trigger + operationId: deletePostgresTrigger + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: postgres trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/get/{path}: + get: + summary: get postgres trigger + operationId: getPostgresTrigger + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: get postgres trigger + content: + application/json: + schema: + allOf: &ref_160 + - type: object + properties: *ref_122 + required: *ref_123 + type: object + properties: &ref_161 + enabled: + type: boolean + postgres_resource_path: + type: string + publication_name: + type: string + server_id: + type: string + replication_slot_name: + type: string + error: + type: string + last_server_ping: + type: string + format: date-time + required: &ref_162 + - enabled + - postgres_resource_path + - replication_slot_name + - publication_name + /w/{workspace}/postgres_triggers/list: + get: + summary: list postgres triggers + operationId: listPostgresTriggers + tags: + - postgres_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: postgres trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_160 + type: object + properties: *ref_161 + required: *ref_162 + /w/{workspace}/postgres_triggers/exists/{path}: + get: + summary: does postgres trigger exists + operationId: existsPostgresTrigger + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: postgres trigger exists + content: + application/json: + schema: + type: boolean + /w/{workspace}/postgres_triggers/setenabled/{path}: + post: + summary: set enabled postgres trigger + operationId: setPostgresTriggerEnabled + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: updated postgres trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: postgres trigger enabled set + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/test: + post: + summary: test postgres connection + operationId: testPostgresConnection + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: test postgres connection + required: true + content: + application/json: + schema: + type: object + properties: + database: + type: string + required: + - database + responses: + '200': + description: successfuly connected to postgres + content: + text/plain: + schema: + type: string /groups/list: get: summary: list instance groups @@ -11601,7 +15683,7 @@ paths: type: array items: type: object - properties: &ref_104 + properties: &ref_164 name: type: string summary: @@ -11610,7 +15692,7 @@ paths: type: array items: type: string - required: &ref_105 + required: &ref_165 - name /groups/get/{name}: get: @@ -11622,7 +15704,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: instance group @@ -11630,8 +15712,8 @@ paths: application/json: schema: type: object - properties: *ref_104 - required: *ref_105 + properties: *ref_164 + required: *ref_165 /groups/create: post: summary: create instance group @@ -11669,7 +15751,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: update instance group required: true @@ -11699,7 +15781,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: instance group deleted @@ -11717,7 +15799,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: user to add to instance group required: true @@ -11747,7 +15829,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: user to remove from instance group required: true @@ -11782,7 +15864,7 @@ paths: type: array items: type: object - properties: &ref_106 + properties: &ref_166 name: type: string summary: @@ -11797,7 +15879,7 @@ paths: type: string external_id: type: string - required: &ref_107 + required: &ref_167 - name /groups/overwrite: post: @@ -11814,8 +15896,8 @@ paths: type: array items: type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_166 + required: *ref_167 responses: '200': description: success message @@ -11851,7 +15933,7 @@ paths: type: array items: type: object - properties: &ref_108 + properties: &ref_168 name: type: string summary: @@ -11864,7 +15946,7 @@ paths: type: object additionalProperties: type: boolean - required: &ref_109 + required: &ref_169 - name /w/{workspace}/groups/listnames: get: @@ -11937,7 +16019,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: updated group required: true @@ -11969,7 +16051,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: group deleted @@ -11991,7 +16073,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: group @@ -11999,8 +16081,8 @@ paths: application/json: schema: type: object - properties: *ref_108 - required: *ref_109 + properties: *ref_168 + required: *ref_169 /w/{workspace}/groups/adduser/{name}: post: summary: add user to group @@ -12015,7 +16097,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: added user to group required: true @@ -12047,7 +16129,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: added user to group required: true @@ -12093,7 +16175,7 @@ paths: type: array items: type: object - properties: &ref_110 + properties: &ref_170 name: type: string owners: @@ -12111,7 +16193,7 @@ paths: edited_at: type: string format: date-time - required: &ref_111 + required: &ref_171 - name - owners - extra_perms @@ -12193,7 +16275,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: update folder required: true @@ -12232,7 +16314,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: folder deleted @@ -12254,7 +16336,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: folder @@ -12262,8 +16344,30 @@ paths: application/json: schema: type: object - properties: *ref_110 - required: *ref_111 + properties: *ref_170 + required: *ref_171 + /w/{workspace}/folders/exists/{name}: + get: + summary: exists folder + operationId: existsFolder + tags: + - folder + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: name + in: path + required: true + schema: *ref_163 + responses: + '200': + description: folder exists + content: + application/json: + schema: + type: boolean /w/{workspace}/folders/getusage/{name}: get: summary: get folder usage @@ -12278,7 +16382,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: folder @@ -12320,7 +16424,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: owner user to folder required: true @@ -12354,7 +16458,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: added owner to folder required: true @@ -12408,7 +16512,7 @@ paths: type: array items: type: object - properties: &ref_183 + properties: &ref_278 worker: type: string worker_instance: @@ -12450,7 +16554,7 @@ paths: type: number wm_memory_usage: type: number - required: &ref_184 + required: &ref_279 - worker - worker_instance - ping_at @@ -12558,13 +16662,39 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: a config content: application/json: - schema: {} + schema: + type: object + nullable: true + properties: &ref_201 + alerts: + type: array + items: + type: object + properties: &ref_199 + name: + type: string + tags_to_monitor: + type: array + items: + type: string + jobs_num_threshold: + type: integer + alert_cooldown_seconds: + type: integer + alert_time_threshold_seconds: + type: integer + required: &ref_200 + - name + - tags_to_monitor + - jobs_num_threshold + - alert_cooldown_seconds + - alert_time_threshold_seconds /configs/update/{name}: post: summary: Update config @@ -12575,7 +16705,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 requestBody: description: worker group required: true @@ -12598,7 +16728,7 @@ paths: - name: name in: path required: true - schema: *ref_103 + schema: *ref_163 responses: '200': description: Delete config @@ -12621,12 +16751,12 @@ paths: type: array items: type: object - properties: &ref_225 + properties: &ref_320 name: type: string config: type: object - required: &ref_226 + required: &ref_321 - name /configs/list_autoscaling_events/{worker_group}: get: @@ -12649,7 +16779,7 @@ paths: type: array items: type: object - properties: &ref_229 + properties: &ref_324 id: type: integer format: int64 @@ -12664,6 +16794,54 @@ paths: applied_at: type: string format: date-time + /configs/list_available_python_versions: + get: + summary: Get currently available python versions provided by UV. + operationId: listAvailablePythonVersions + tags: + - config + 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 + operationId: createAgentToken + tags: + - agent_workers + requestBody: + description: agent token + required: true + content: + application/json: + schema: + type: object + properties: + worker_group: + type: string + tags: + type: array + items: + type: string + exp: + type: integer + required: + - worker_group + - tags + - exp + responses: + '200': + description: agent token created + content: + application/json: + schema: + type: string /w/{workspace}/acls/get/{kind}/{path}: get: summary: get granular acls @@ -12678,7 +16856,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: kind in: path required: true @@ -12696,6 +16874,12 @@ paths: - raw_app - http_trigger - websocket_trigger + - kafka_trigger + - nats_trigger + - postgres_trigger + - mqtt_trigger + - gcp_trigger + - sqs_trigger responses: '200': description: acls @@ -12719,7 +16903,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: kind in: path required: true @@ -12737,6 +16921,12 @@ paths: - raw_app - http_trigger - websocket_trigger + - kafka_trigger + - nats_trigger + - postgres_trigger + - mqtt_trigger + - gcp_trigger + - sqs_trigger requestBody: description: acl to add required: true @@ -12772,7 +16962,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: kind in: path required: true @@ -12790,6 +16980,12 @@ paths: - raw_app - http_trigger - websocket_trigger + - kafka_trigger + - nats_trigger + - postgres_trigger + - mqtt_trigger + - gcp_trigger + - sqs_trigger requestBody: description: acl to add required: true @@ -12809,10 +17005,10 @@ paths: text/plain: schema: type: string - /w/{workspace}/capture_u/{path}: + /w/{workspace}/capture/set_config: post: - summary: update flow preview capture - operationId: updateCapture + summary: set capture config + operationId: setCaptureConfig tags: - capture parameters: @@ -12820,17 +17016,48 @@ paths: in: path required: true schema: *ref_0 - - name: path - in: path - required: true - schema: *ref_23 + requestBody: + description: capture config + required: true + content: + application/json: + schema: + type: object + properties: + trigger_kind: + type: string + enum: &ref_172 + - webhook + - http + - websocket + - kafka + - email + - nats + - postgres + - sqs + - mqtt + - gcp + path: + type: string + is_flow: + type: boolean + trigger_config: + type: object + required: + - trigger_kind + - path + - is_flow responses: - '204': - description: flow preview captured - /w/{workspace}/capture/{path}: - put: - summary: create flow preview capture - operationId: createCapture + '200': + description: capture config set + content: + application/json: + schema: + type: object + /w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}: + post: + summary: ping capture config + operationId: pingCaptureConfig tags: - capture parameters: @@ -12838,15 +17065,160 @@ paths: in: path required: true schema: *ref_0 + - name: trigger_kind + in: path + required: true + schema: + type: string + enum: *ref_172 + - name: runnable_kind + in: path + required: true + schema: *ref_73 - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: - '201': - description: flow preview capture created + '200': + description: capture config pinged + /w/{workspace}/capture/get_configs/{runnable_kind}/{path}: get: - summary: get flow preview capture + summary: get capture configs for a script or flow + operationId: getCaptureConfigs + tags: + - capture + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: runnable_kind + in: path + required: true + schema: *ref_73 + - name: path + in: path + required: true + schema: *ref_26 + responses: + '200': + description: capture configs for a script or flow + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_325 + trigger_config: {} + trigger_kind: + type: string + enum: *ref_172 + error: + type: string + last_server_ping: + type: string + format: date-time + required: &ref_326 + - trigger_kind + /w/{workspace}/capture/list/{runnable_kind}/{path}: + get: + summary: list captures for a script or flow + operationId: listCaptures + tags: + - capture + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: runnable_kind + in: path + required: true + schema: *ref_73 + - name: path + in: path + required: true + schema: *ref_26 + - name: trigger_kind + in: query + schema: + type: string + enum: *ref_172 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + responses: + '200': + description: list of captures for a script or flow + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_173 + trigger_kind: + type: string + enum: *ref_172 + main_args: {} + preprocessor_args: {} + id: + type: integer + created_at: + type: string + format: date-time + required: &ref_174 + - trigger_kind + - main_args + - preprocessor_args + - id + - created_at + /w/{workspace}/capture/move/{runnable_kind}/{path}: + post: + summary: move captures and configs for a script or flow + operationId: moveCapturesAndConfigs + tags: + - capture + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: runnable_kind + in: path + required: true + schema: *ref_73 + - name: path + in: path + required: true + schema: *ref_26 + requestBody: + description: move captures and configs to a new path + required: true + content: + application/json: + schema: + type: object + properties: + new_path: + type: string + responses: + '200': + description: captures and configs moved + content: + text/plain: + schema: + type: string + /w/{workspace}/capture/{id}: + get: + summary: get a capture operationId: getCapture tags: - capture @@ -12855,18 +17227,38 @@ paths: in: path required: true schema: *ref_0 - - name: path + - name: id in: path required: true - schema: *ref_23 + schema: + type: integer responses: '200': - description: captured flow preview + description: capture content: application/json: - schema: {} - '404': - description: capture does not exist for this flow + schema: + type: object + properties: *ref_173 + required: *ref_174 + delete: + summary: delete a capture + operationId: deleteCapture + tags: + - capture + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: capture deleted /w/{workspace}/favorites/star: post: summary: star item @@ -12938,13 +17330,13 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: &ref_112 + schema: &ref_175 type: string - name: runnable_type in: query - schema: &ref_113 + schema: &ref_176 type: string - enum: &ref_142 + enum: &ref_207 - ScriptHash - ScriptPath - FlowPath @@ -12956,6 +17348,16 @@ paths: description: number of items to return for a given page (default 30, max 100) in: query schema: *ref_6 + - name: args + description: >- + filter on jobs containing those args as a json subset (@> in + postgres) + in: query + schema: *ref_93 + - name: include_preview + in: query + schema: + type: boolean responses: '200': description: Input history for completed jobs @@ -12965,7 +17367,7 @@ paths: type: array items: type: object - properties: &ref_114 + properties: &ref_177 id: type: string name: @@ -12979,7 +17381,7 @@ paths: type: boolean success: type: boolean - required: &ref_115 + required: &ref_178 - id - name - args @@ -13029,10 +17431,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_112 + schema: *ref_175 - name: runnable_type in: query - schema: *ref_113 + schema: *ref_176 - name: page description: which page to return (start at 1, default 1) in: query @@ -13050,8 +17452,8 @@ paths: type: array items: type: object - properties: *ref_114 - required: *ref_115 + properties: *ref_177 + required: *ref_178 /w/{workspace}/inputs/create: post: summary: Create an Input for future use in a script or flow @@ -13065,10 +17467,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_112 + schema: *ref_175 - name: runnable_type in: query - schema: *ref_113 + schema: *ref_176 requestBody: description: Input required: true @@ -13076,12 +17478,12 @@ paths: application/json: schema: type: object - properties: &ref_138 + properties: &ref_203 name: type: string args: type: object - required: &ref_139 + required: &ref_204 - name - args - created_by @@ -13111,14 +17513,14 @@ paths: application/json: schema: type: object - properties: &ref_140 + properties: &ref_205 id: type: string name: type: string is_public: type: boolean - required: &ref_141 + required: &ref_206 - id - name - is_public @@ -13144,7 +17546,7 @@ paths: - name: input in: path required: true - schema: &ref_134 + schema: &ref_193 type: string responses: '200': @@ -13177,7 +17579,7 @@ paths: properties: s3_resource: type: object - properties: &ref_116 + properties: &ref_179 bucket: type: string region: @@ -13192,7 +17594,7 @@ paths: type: string pathStyle: type: boolean - required: &ref_117 + required: &ref_180 - bucket - region - endPoint @@ -13243,6 +17645,8 @@ paths: properties: connection_settings_str: type: string + azure_container_path: + type: string required: - connection_settings_str /w/{workspace}/job_helpers/polars_connection_settings: @@ -13268,8 +17672,8 @@ paths: properties: s3_resource: type: object - properties: *ref_116 - required: *ref_117 + properties: *ref_179 + required: *ref_180 responses: '200': description: Connection settings @@ -13290,10 +17694,10 @@ paths: type: boolean client_kwargs: type: object - properties: &ref_118 + properties: &ref_181 region_name: type: string - required: &ref_119 + required: &ref_182 - region_name required: - endpoint_url @@ -13348,8 +17752,8 @@ paths: type: boolean client_kwargs: type: object - properties: *ref_118 - required: *ref_119 + properties: *ref_181 + required: *ref_182 required: - endpoint_url - use_ssl @@ -13407,8 +17811,8 @@ paths: application/json: schema: type: object - properties: *ref_116 - required: *ref_117 + properties: *ref_179 + required: *ref_180 /w/{workspace}/job_helpers/test_connection: get: summary: Test connection to the workspace object storage @@ -13472,10 +17876,10 @@ paths: type: array items: type: object - properties: &ref_202 + properties: &ref_297 s3: type: string - required: &ref_203 + required: &ref_298 - s3 restricted_access: type: boolean @@ -13508,7 +17912,7 @@ paths: application/json: schema: type: object - properties: &ref_204 + properties: &ref_299 mime_type: type: string size_in_bytes: @@ -13572,7 +17976,7 @@ paths: application/json: schema: type: object - properties: &ref_205 + properties: &ref_300 msg: type: string content: @@ -13584,7 +17988,7 @@ paths: - Csv - Parquet - Unknown - required: &ref_206 + required: &ref_301 - content_type /w/{workspace}/job_helpers/load_parquet_preview/{path}: get: @@ -13600,7 +18004,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: offset in: query schema: @@ -13649,7 +18053,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: search_col in: query schema: @@ -13686,7 +18090,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 - name: offset in: query schema: @@ -13847,7 +18251,7 @@ paths: - file_key /w/{workspace}/job_helpers/download_s3_file: get: - summary: Download file to S3 bucket + summary: Download file from S3 bucket operationId: fileDownload tags: - helpers @@ -13930,7 +18334,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 requestBody: description: parameters for statistics retrieval required: true @@ -13959,46 +18363,46 @@ paths: type: array items: type: object - properties: &ref_209 + properties: &ref_304 id: type: string name: type: string - required: &ref_210 + required: &ref_305 - id scalar_metrics: type: array items: type: object - properties: &ref_211 + properties: &ref_306 metric_id: type: string value: type: number - required: &ref_212 + required: &ref_307 - id - value timeseries_metrics: type: array items: type: object - properties: &ref_213 + properties: &ref_308 metric_id: type: string values: type: array items: type: object - properties: &ref_215 + properties: &ref_310 timestamp: type: string format: date-time value: type: number - required: &ref_216 + required: &ref_311 - timestamp - value - required: &ref_214 + required: &ref_309 - id - values /w/{workspace}/job_metrics/set_progress/{id}: @@ -14015,7 +18419,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 requestBody: description: parameters for statistics retrieval required: true @@ -14049,7 +18453,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: job progress between 0 and 99 @@ -14067,11 +18471,11 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_120 + schema: *ref_183 - name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_121 + schema: *ref_184 - name: with_error in: query required: false @@ -14120,7 +18524,7 @@ paths: - name: path in: path required: true - schema: *ref_23 + schema: *ref_26 responses: '200': description: log stream @@ -14143,12 +18547,12 @@ paths: type: array items: type: object - properties: &ref_219 + properties: &ref_314 concurrency_key: type: string total_running: type: number - required: &ref_220 + required: &ref_315 - concurrency_key - total_running /concurrency_groups/prune/{concurrency_id}: @@ -14161,7 +18565,7 @@ paths: - name: concurrency_id in: path required: true - schema: &ref_136 + schema: &ref_195 type: string responses: '200': @@ -14181,7 +18585,7 @@ paths: - name: id in: path required: true - schema: *ref_91 + schema: *ref_110 responses: '200': description: concurrency key for given job @@ -14214,93 +18618,97 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_86 + schema: *ref_98 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_70 + schema: *ref_83 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_71 + schema: *ref_84 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_72 + schema: *ref_85 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_73 + schema: *ref_86 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_74 + schema: *ref_87 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_75 + schema: *ref_88 - name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_122 + schema: *ref_103 - name: running description: filter on running jobs in: query - schema: *ref_80 + schema: *ref_89 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_77 + schema: *ref_90 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_123 + schema: *ref_104 - name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_124 + schema: *ref_105 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_78 + schema: *ref_91 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_83 + schema: *ref_94 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_82 + schema: *ref_95 + - name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: *ref_97 - name: page description: which page to return (start at 1, default 1) in: query @@ -14348,17 +18756,17 @@ paths: application/json: schema: type: object - properties: &ref_221 + properties: &ref_316 jobs: type: array items: - oneOf: *ref_92 - discriminator: *ref_93 + oneOf: *ref_111 + discriminator: *ref_112 obscured_jobs: type: array items: type: object - properties: &ref_143 + properties: &ref_208 typ: type: string started_at: @@ -14371,7 +18779,7 @@ paths: Obscured jobs omitted for security because of too specific filtering type: boolean - required: &ref_222 + required: &ref_317 - jobs - obscured_jobs /srch/w/{workspace}/index/search/job: @@ -14390,6 +18798,11 @@ paths: required: true schema: type: string + - name: pagination_offset + in: query + required: false + schema: + type: integer responses: '200': description: search results @@ -14404,18 +18817,29 @@ paths: ignored) type: array items: - type: object - properties: - dancer: - type: string + type: string hits: description: the jobs that matched the query type: array items: type: object - properties: &ref_227 + properties: &ref_322 dancer: type: string + hit_count: + description: how many jobs matched in total + type: number + index_metadata: + description: Metadata about the index current state + type: object + properties: + indexed_until: + description: Datetime of the most recently indexed job + type: string + format: date-time + lost_lock_ownership: + description: Is the current indexer service being replaced + type: boolean /srch/index/search/service_logs: get: summary: Search through service logs with a string query @@ -14475,7 +18899,7 @@ paths: type: array items: type: object - properties: &ref_228 + properties: &ref_323 dancer: type: string /srch/index/search/count_service_logs: @@ -14490,11 +18914,6 @@ paths: required: true schema: type: string - - name: hosts - in: query - required: true - schema: - type: string - name: min_ts in: query required: false @@ -14525,6 +18944,28 @@ paths: count_per_host: description: count of log lines that matched the query per hostname type: object + /srch/index/delete/{idx_name}: + delete: + summary: Restart container and delete the index to recreate it. + operationId: clearIndex + tags: + - indexSearch + parameters: + - name: idx_name + in: path + required: true + schema: + type: string + enum: + - JobIndex + - ServiceLogIndex + responses: + '200': + description: idx to be deleted and container restarting + content: + text/plain: + schema: + type: string components: securitySchemes: bearerAuth: @@ -14535,6 +18976,11 @@ components: in: cookie name: token parameters: + Id: + name: id + in: path + required: true + schema: *ref_185 Key: name: key in: path @@ -14545,61 +18991,71 @@ components: in: path required: true schema: *ref_0 + PublicationName: + name: publication + in: path + required: true + schema: *ref_157 VersionId: name: version in: path required: true - schema: *ref_125 + schema: *ref_186 Token: name: token in: path required: true - schema: *ref_126 + schema: *ref_187 AccountId: name: id in: path required: true - schema: *ref_27 + schema: *ref_31 ClientName: name: client_name in: path required: true - schema: *ref_26 + schema: *ref_30 ScriptPath: name: path in: path required: true - schema: *ref_35 + schema: *ref_41 ScriptHash: name: hash in: path required: true - schema: *ref_39 + schema: *ref_46 JobId: name: id in: path required: true - schema: *ref_91 + schema: *ref_110 Path: name: path in: path required: true - schema: *ref_23 + schema: *ref_26 + CustomPath: + name: custom_path + in: path + required: true + schema: *ref_74 PathId: name: id in: path required: true - schema: *ref_30 + schema: *ref_34 PathVersion: name: version in: path required: true - schema: *ref_127 + schema: *ref_188 Name: name: name in: path required: true - schema: *ref_103 + schema: *ref_163 Page: name: page description: which page to return (start at 1, default 1) @@ -14614,12 +19070,12 @@ components: name: order_desc description: order by desc order (default true) in: query - schema: *ref_52 + schema: *ref_59 CreatedBy: name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_53 + schema: *ref_60 Label: name: label description: >- @@ -14627,26 +19083,31 @@ components: with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_86 + schema: *ref_98 + Worker: + name: worker + description: worker this job was ran on + in: query + schema: *ref_82 ParentJob: name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_46 + schema: *ref_53 WorkerTag: name: tag description: Override the tag to use in: query - schema: *ref_48 + schema: *ref_55 CacheTtl: name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_49 + schema: *ref_56 NewJobId: name: job_id description: >- @@ -14654,7 +19115,7 @@ components: randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_47 + schema: *ref_54 IncludeHeader: name: include_header description: > @@ -14664,14 +19125,14 @@ components: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_50 + schema: *ref_57 QueueLimit: name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_51 + schema: *ref_58 Payload: name: payload description: > @@ -14680,253 +19141,283 @@ components: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_94 + schema: *ref_113 ScriptStartPath: name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_71 + schema: *ref_84 SchedulePath: name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_72 + schema: *ref_85 ScriptExactPath: name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_70 + schema: *ref_83 ScriptExactHash: name: script_hash description: mask to filter exact matching path in: query - schema: *ref_73 + schema: *ref_86 CreatedBefore: name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_128 + schema: *ref_101 CreatedAfter: name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_129 + schema: *ref_102 StartedBefore: name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_74 + schema: *ref_87 StartedAfter: name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_75 + schema: *ref_88 Before: name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_120 + schema: *ref_183 CreatedOrStartedAfter: name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_123 + schema: *ref_104 CreatedOrStartedAfterCompletedJob: name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_124 + schema: *ref_105 CreatedOrStartedBefore: name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_122 + schema: *ref_103 Success: name: success description: filter on successful jobs in: query - schema: *ref_76 + schema: *ref_96 ScheduledForBeforeNow: name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_77 + schema: *ref_90 Suspended: name: suspended description: filter on suspended jobs in: query - schema: *ref_79 + schema: *ref_92 Running: name: running description: filter on running jobs in: query - schema: *ref_80 + schema: *ref_89 + AllowWildcards: + name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: *ref_97 ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_81 + schema: *ref_93 Tag: name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_83 + schema: *ref_94 ResultFilter: name: result description: filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_82 + schema: *ref_95 After: name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_121 + schema: *ref_184 Username: name: username description: filter on exact username of user in: query - schema: *ref_130 + schema: *ref_189 Operation: name: operation description: filter on exact or prefix name of operation in: query - schema: *ref_131 + schema: *ref_190 ResourceName: name: resource description: filter on exact or prefix name of resource in: query - schema: *ref_132 + schema: *ref_191 ActionKind: name: action_kind description: filter on type of operation in: query - schema: *ref_133 + schema: *ref_192 JobKinds: name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_78 + schema: *ref_91 RunnableId: name: runnable_id in: query - schema: *ref_112 + schema: *ref_175 RunnableTypeQuery: name: runnable_type in: query - schema: *ref_113 + schema: *ref_176 InputId: name: input in: path required: true - schema: *ref_134 + schema: *ref_193 GetStarted: name: get_started in: query - schema: *ref_135 + schema: *ref_194 ConcurrencyId: name: concurrency_id in: path required: true - schema: *ref_136 + schema: *ref_195 + RunnableKind: + name: runnable_kind + in: path + required: true + schema: *ref_73 schemas: - AiResource: + InputTransform: + allOf: *ref_77 + AIProvider: + type: string + enum: *ref_196 + AIProviderModel: type: object - properties: *ref_16 - required: *ref_17 + properties: *ref_18 + required: *ref_19 + AIProviderConfig: + type: object + properties: *ref_197 + required: *ref_198 + AIConfig: + type: object + properties: *ref_20 + Alert: + type: object + properties: *ref_199 + required: *ref_200 + Configs: + type: object + nullable: true + properties: *ref_201 Script: - type: object - properties: *ref_37 - required: *ref_38 - NewScript: - type: object - properties: *ref_42 - required: *ref_43 - NewScriptWithDraft: - allOf: *ref_137 - ScriptHistory: type: object properties: *ref_44 required: *ref_45 + NewScript: + type: object + properties: *ref_49 + required: *ref_50 + NewScriptWithDraft: + allOf: *ref_202 + ScriptHistory: + type: object + properties: *ref_51 + required: *ref_52 ScriptArgs: type: object - additionalProperties: *ref_18 + additionalProperties: *ref_21 Input: type: object - properties: *ref_114 - required: *ref_115 + properties: *ref_177 + required: *ref_178 CreateInput: type: object - properties: *ref_138 - required: *ref_139 + properties: *ref_203 + required: *ref_204 UpdateInput: type: object - properties: *ref_140 - required: *ref_141 + properties: *ref_205 + required: *ref_206 RunnableType: type: string - enum: *ref_142 + enum: *ref_207 QueuedJob: type: object - properties: *ref_89 - required: *ref_90 + properties: *ref_108 + required: *ref_109 CompletedJob: type: object - properties: *ref_87 - required: *ref_88 + properties: *ref_106 + required: *ref_107 ObscuredJob: type: object - properties: *ref_143 + properties: *ref_208 Job: - oneOf: *ref_92 - discriminator: *ref_93 + oneOf: *ref_111 + discriminator: *ref_112 User: type: object properties: *ref_10 required: *ref_11 UserUsage: type: object - properties: *ref_144 + properties: *ref_209 Login: type: object - properties: *ref_145 - required: *ref_146 + properties: *ref_210 + required: *ref_211 EditWorkspaceUser: type: object - properties: *ref_147 + properties: *ref_212 TruncatedToken: type: object - properties: *ref_40 - required: *ref_41 + properties: *ref_47 + required: *ref_48 NewToken: type: object - properties: *ref_148 + properties: *ref_213 NewTokenImpersonate: type: object - properties: *ref_149 - required: *ref_150 + properties: *ref_214 + required: *ref_215 ListableVariable: type: object - properties: *ref_24 - required: *ref_25 + properties: *ref_27 + required: *ref_28 ContextualVariable: type: object - properties: *ref_151 - required: *ref_152 + properties: *ref_216 + required: *ref_217 CreateVariable: type: object - properties: *ref_153 - required: *ref_154 + properties: *ref_218 + required: *ref_219 EditVariable: type: object - properties: *ref_155 + properties: *ref_220 AuditLog: type: object properties: *ref_1 @@ -15056,164 +19547,315 @@ components: - error - no_main_func - has_preprocessor + ScriptLang: + type: string + enum: *ref_43 Preview: type: object - properties: *ref_156 - required: *ref_157 + properties: *ref_221 + required: *ref_222 WorkflowTask: + type: object + properties: *ref_223 + required: *ref_224 + WorkflowStatusRecord: + type: object + additionalProperties: *ref_225 + WorkflowStatus: + type: object + properties: *ref_226 + CreateResource: + type: object + properties: *ref_227 + required: *ref_228 + EditResource: + type: object + properties: *ref_229 + Resource: + type: object + properties: *ref_230 + required: *ref_231 + ListableResource: + type: object + properties: *ref_232 + required: *ref_233 + ResourceType: + type: object + properties: *ref_32 + required: *ref_33 + EditResourceType: + type: object + properties: *ref_234 + Schedule: + type: object + properties: *ref_115 + required: *ref_116 + ScheduleWJobs: + allOf: *ref_235 + NewSchedule: + type: object + properties: *ref_236 + required: *ref_237 + EditSchedule: + type: object + properties: *ref_238 + required: *ref_239 + TriggerExtraProperty: + type: object + properties: *ref_122 + required: *ref_123 + AuthenticationMethod: + type: string + enum: *ref_117 + HttpTrigger: + allOf: *ref_118 + type: object + properties: *ref_119 + required: *ref_120 + NewHttpTrigger: + type: object + properties: *ref_240 + required: *ref_241 + EditHttpTrigger: + type: object + properties: *ref_242 + required: *ref_243 + TriggersCount: + type: object + properties: *ref_66 + WebsocketTrigger: + allOf: *ref_124 + type: object + properties: *ref_125 + required: *ref_126 + NewWebsocketTrigger: + type: object + properties: *ref_244 + required: *ref_245 + EditWebsocketTrigger: + type: object + properties: *ref_246 + required: *ref_247 + WebsocketTriggerInitialMessage: + anyOf: *ref_121 + MqttQoS: + type: string + enum: *ref_248 + MqttV3Config: + type: object + properties: *ref_139 + MqttV5Config: + type: object + properties: *ref_140 + MqttSubscribeTopic: + type: object + properties: *ref_137 + required: *ref_138 + MqttClientVersion: + type: string + enum: *ref_141 + MqttTrigger: + allOf: *ref_142 + type: object + properties: *ref_143 + required: *ref_144 + NewMqttTrigger: + type: object + properties: *ref_249 + required: *ref_250 + EditMqttTrigger: + type: object + properties: *ref_251 + required: *ref_252 + DeliveryType: + type: string + enum: *ref_147 + PushConfig: + type: object + properties: *ref_148 + required: *ref_149 + GcpTrigger: + allOf: *ref_151 + type: object + properties: *ref_152 + required: *ref_153 + SubscriptionMode: + type: string + enum: *ref_150 + description: >- + The mode of subscription. 'existing' means using an existing GCP + subscription, while 'create_update' involves creating or updating a new + subscription. + GcpTriggerData: + type: object + properties: *ref_145 + required: *ref_146 + GetAllTopicSubscription: + type: object + properties: *ref_253 + required: *ref_254 + DeleteGcpSubscription: + type: object + properties: *ref_255 + required: *ref_256 + AwsAuthResourceType: + type: string + enum: *ref_133 + SqsTrigger: + allOf: *ref_134 + type: object + properties: *ref_135 + required: *ref_136 + NewSqsTrigger: + type: object + properties: *ref_257 + required: *ref_258 + EditSqsTrigger: + type: object + properties: *ref_259 + required: *ref_260 + Slot: + type: object + properties: *ref_154 + SlotList: + type: object + properties: *ref_261 + PublicationData: type: object properties: *ref_158 required: *ref_159 - WorkflowStatusRecord: + TableToTrack: + type: array + items: *ref_262 + Relations: type: object - additionalProperties: *ref_160 - WorkflowStatus: + properties: *ref_155 + required: *ref_156 + Language: + type: string + enum: *ref_263 + TemplateScript: + type: object + properties: *ref_264 + required: *ref_265 + PostgresTrigger: + allOf: *ref_160 type: object properties: *ref_161 - CreateResource: + required: *ref_162 + NewPostgresTrigger: type: object - properties: *ref_162 - required: *ref_163 - EditResource: + properties: *ref_266 + required: *ref_267 + EditPostgresTrigger: type: object - properties: *ref_164 - Resource: + properties: *ref_268 + required: *ref_269 + KafkaTrigger: + allOf: *ref_127 type: object - properties: *ref_165 - required: *ref_166 - ListableResource: + properties: *ref_128 + required: *ref_129 + NewKafkaTrigger: type: object - properties: *ref_167 - required: *ref_168 - ResourceType: + properties: *ref_270 + required: *ref_271 + EditKafkaTrigger: type: object - properties: *ref_28 - required: *ref_29 - EditResourceType: + properties: *ref_272 + required: *ref_273 + NatsTrigger: + allOf: *ref_130 type: object - properties: *ref_169 - Schedule: + properties: *ref_131 + required: *ref_132 + NewNatsTrigger: type: object - properties: *ref_96 - required: *ref_97 - ScheduleWJobs: - allOf: *ref_170 - NewSchedule: + properties: *ref_274 + required: *ref_275 + EditNatsTrigger: type: object - properties: *ref_171 - required: *ref_172 - EditSchedule: - type: object - properties: *ref_173 - required: *ref_174 - HttpTrigger: - type: object - properties: *ref_98 - required: *ref_99 - NewHttpTrigger: - type: object - properties: *ref_175 - required: *ref_176 - EditHttpTrigger: - type: object - properties: *ref_177 - required: *ref_178 - TriggersCount: - type: object - properties: *ref_59 - WebsocketTrigger: - type: object - properties: *ref_101 - required: *ref_102 - NewWebsocketTrigger: - type: object - properties: *ref_179 - required: *ref_180 - EditWebsocketTrigger: - type: object - properties: *ref_181 - required: *ref_182 - WebsocketTriggerInitialMessage: - anyOf: *ref_100 + properties: *ref_276 + required: *ref_277 Group: type: object - properties: *ref_108 - required: *ref_109 + properties: *ref_168 + required: *ref_169 InstanceGroup: type: object - properties: *ref_104 - required: *ref_105 + properties: *ref_164 + required: *ref_165 Folder: type: object - properties: *ref_110 - required: *ref_111 + properties: *ref_170 + required: *ref_171 WorkerPing: type: object - properties: *ref_183 - required: *ref_184 + properties: *ref_278 + required: *ref_279 UserWorkspaceList: type: object - properties: *ref_185 - required: *ref_186 + properties: *ref_280 + required: *ref_281 CreateWorkspace: type: object - properties: *ref_187 - required: *ref_188 + properties: *ref_282 + required: *ref_283 Workspace: type: object properties: *ref_7 required: *ref_8 WorkspaceInvite: type: object - properties: *ref_14 - required: *ref_15 + properties: *ref_16 + required: *ref_17 GlobalUserInfo: type: object - properties: *ref_12 - required: *ref_13 + properties: *ref_14 + required: *ref_15 Flow: - allOf: *ref_58 + allOf: *ref_65 ExtraPerms: type: object - additionalProperties: *ref_189 + additionalProperties: *ref_284 FlowMetadata: type: object - properties: *ref_190 - required: *ref_191 + properties: *ref_285 + required: *ref_286 OpenFlowWPath: - allOf: *ref_60 + allOf: *ref_67 FlowPreview: type: object - properties: *ref_192 - required: *ref_193 + properties: *ref_287 + required: *ref_288 RestartedFrom: type: object - properties: *ref_194 + properties: *ref_289 Policy: type: object - properties: *ref_61 + properties: *ref_68 ListableApp: type: object - properties: *ref_195 - required: *ref_196 + properties: *ref_290 + required: *ref_291 ListableRawApp: type: object - properties: *ref_197 - required: *ref_198 + properties: *ref_292 + required: *ref_293 AppWithLastVersion: type: object - properties: *ref_62 - required: *ref_63 + properties: *ref_69 + required: *ref_70 AppWithLastVersionWDraft: - allOf: *ref_199 + allOf: *ref_294 AppHistory: type: object - properties: *ref_64 - required: *ref_65 + properties: *ref_71 + required: *ref_72 FlowVersion: type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_63 + required: *ref_64 SlackToken: type: object properties: @@ -15235,46 +19877,46 @@ components: - bot TokenResponse: type: object - properties: *ref_200 - required: *ref_201 + properties: *ref_295 + required: *ref_296 HubScriptKind: - name: kind - schema: *ref_36 + type: string + enum: *ref_42 PolarsClientKwargs: type: object - properties: *ref_118 - required: *ref_119 + properties: *ref_181 + required: *ref_182 LargeFileStorage: type: object - properties: *ref_19 + properties: *ref_22 WindmillLargeFile: type: object - properties: *ref_202 - required: *ref_203 + properties: *ref_297 + required: *ref_298 WindmillFileMetadata: type: object - properties: *ref_204 + properties: *ref_299 WindmillFilePreview: type: object - properties: *ref_205 - required: *ref_206 + properties: *ref_300 + required: *ref_301 S3Resource: type: object - properties: *ref_116 - required: *ref_117 + properties: *ref_179 + required: *ref_180 WorkspaceGitSyncSettings: type: object - properties: *ref_20 + properties: *ref_23 WorkspaceDeployUISettings: type: object - properties: *ref_21 + properties: *ref_24 WorkspaceDefaultScripts: type: object - properties: *ref_22 + properties: *ref_25 GitRepositorySettings: type: object - properties: *ref_207 - required: *ref_208 + properties: *ref_302 + required: *ref_303 UploadFilePart: type: object properties: @@ -15287,126 +19929,195 @@ components: - tag MetricMetadata: type: object - properties: *ref_209 - required: *ref_210 + properties: *ref_304 + required: *ref_305 ScalarMetric: type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_306 + required: *ref_307 TimeseriesMetric: type: object - properties: *ref_213 - required: *ref_214 + properties: *ref_308 + required: *ref_309 MetricDataPoint: type: object - properties: *ref_215 - required: *ref_216 + properties: *ref_310 + required: *ref_311 RawScriptForDependencies: type: object - properties: *ref_217 - required: *ref_218 + properties: *ref_312 + required: *ref_313 ConcurrencyGroup: type: object - properties: *ref_219 - required: *ref_220 + properties: *ref_314 + required: *ref_315 ExtendedJobs: type: object - properties: *ref_221 - required: *ref_222 + properties: *ref_316 + required: *ref_317 ExportedUser: type: object properties: *ref_3 required: *ref_4 GlobalSetting: type: object - properties: *ref_223 - required: *ref_224 + properties: *ref_318 + required: *ref_319 Config: type: object - properties: *ref_225 - required: *ref_226 + properties: *ref_320 + required: *ref_321 ExportedInstanceGroup: type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_166 + required: *ref_167 JobSearchHit: type: object - properties: *ref_227 + properties: *ref_322 LogSearchHit: type: object - properties: *ref_228 + properties: *ref_323 AutoscalingEvent: type: object - properties: *ref_229 + properties: *ref_324 CriticalAlert: type: object - properties: *ref_230 + properties: *ref_29 + CaptureTriggerKind: + type: string + enum: *ref_172 + Capture: + type: object + properties: *ref_173 + required: *ref_174 + CaptureConfig: + type: object + properties: *ref_325 + required: *ref_326 + OperatorSettings: + nullable: true + type: object + required: *ref_12 + properties: *ref_13 + TeamInfo: + type: object + required: *ref_327 + properties: *ref_328 + ChannelInfo: + type: object + required: *ref_329 + properties: *ref_330 + GithubInstallations: + type: array + items: *ref_331 + WorkspaceGithubInstallation: + type: object + properties: + account_id: + type: string + installation_id: + type: number + required: + - account_id + - installation_id + S3Object: + type: object + properties: *ref_75 + required: *ref_76 + TeamsChannel: + type: object + required: + - team_id + - team_name + - channel_id + - channel_name + properties: + team_id: + type: string + description: Microsoft Teams team ID + minLength: 1 + team_name: + type: string + description: Microsoft Teams team name + minLength: 1 + channel_id: + type: string + description: Microsoft Teams channel ID + minLength: 1 + channel_name: + type: string + description: Microsoft Teams channel name + minLength: 1 StaticTransform: type: object - properties: *ref_231 - required: *ref_232 + properties: *ref_332 + required: *ref_333 JavascriptTransform: type: object - properties: *ref_233 - required: *ref_234 - InputTransform: - oneOf: *ref_31 - discriminator: *ref_32 + properties: *ref_334 + required: *ref_335 + schemas-InputTransform: + oneOf: *ref_35 + discriminator: *ref_36 RawScript: type: object - properties: *ref_235 - required: *ref_236 + properties: *ref_336 + required: *ref_337 PathScript: type: object - properties: *ref_237 - required: *ref_238 + properties: *ref_338 + required: *ref_339 PathFlow: type: object - properties: *ref_239 - required: *ref_240 + properties: *ref_340 + required: *ref_341 FlowModule: type: object - properties: *ref_33 - required: *ref_34 + properties: *ref_37 + required: *ref_38 ForloopFlow: type: object - properties: *ref_241 - required: *ref_242 + properties: *ref_342 + required: *ref_343 WhileloopFlow: type: object - properties: *ref_243 - required: *ref_244 + properties: *ref_344 + required: *ref_345 BranchOne: type: object - properties: *ref_245 - required: *ref_246 + properties: *ref_346 + required: *ref_347 BranchAll: type: object - properties: *ref_247 - required: *ref_248 + properties: *ref_348 + required: *ref_349 Identity: type: object - properties: *ref_249 - required: *ref_250 + properties: *ref_350 + required: *ref_351 FlowModuleValue: - oneOf: *ref_251 - discriminator: *ref_252 + oneOf: *ref_352 + discriminator: *ref_353 + StopAfterIf: + type: object + properties: *ref_39 + required: *ref_40 Retry: type: object - properties: *ref_95 + properties: *ref_114 FlowValue: type: object - properties: *ref_66 - required: *ref_67 + properties: *ref_78 + required: *ref_79 OpenFlow: type: object - properties: *ref_54 - required: *ref_55 + properties: *ref_61 + required: *ref_62 FlowStatusModule: type: object - properties: *ref_68 - required: *ref_69 + properties: *ref_80 + required: *ref_81 FlowStatus: type: object - properties: *ref_84 - required: *ref_85 + properties: *ref_99 + required: *ref_100 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index ad59bda610..8f6f925f65 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.475.0 + version: 1.501.4 title: Windmill API contact: @@ -377,6 +377,9 @@ paths: type: string company: type: string + skip_email: + type: boolean + description: Skip sending email notifications to the user required: - email - password @@ -573,6 +576,20 @@ paths: schema: type: string + /github_app/connected_repositories: + get: + summary: get connected repositories + operationId: getGlobalConnectedRepositories + tags: + - git_sync + responses: + "200": + description: connected repositories + content: + application/json: + schema: + $ref: "#/components/schemas/GithubInstallations" + /workspaces/list: get: summary: list all workspaces visible to me @@ -1286,6 +1303,144 @@ paths: schema: $ref: "#/components/schemas/User" + /w/{workspace}/github_app/token: + post: + summary: get github app token + operationId: getGithubAppToken + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: jwt job token + required: true + content: + application/json: + schema: + type: object + properties: + job_token: + type: string + required: + - job_token + responses: + "200": + description: github app token + content: + application/json: + schema: + type: object + properties: + token: + type: string + required: + - token + + /w/{workspace}/github_app/install_from_workspace: + post: + tags: + - Git Sync + summary: Install a GitHub installation from another workspace + operationId: installFromWorkspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + source_workspace_id: + type: string + description: The ID of the workspace containing the installation to copy + installation_id: + type: number + description: The ID of the GitHub installation to copy + required: + - source_workspace_id + - installation_id + responses: + "200": + description: Installation successfully copied + + /w/{workspace}/github_app/installation/{installation_id}: + delete: + summary: Delete a GitHub installation from a workspace + operationId: deleteFromWorkspace + description: Removes a GitHub installation from the specified workspace. Requires admin privileges. + tags: + - Git Sync + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: installation_id + in: path + required: true + schema: + type: integer + format: int64 + description: The ID of the GitHub installation to delete + responses: + "200": + description: Installation successfully deleted + + /w/{workspace}/github_app/export/{installationId}: + get: + summary: Export GitHub installation JWT token + description: Exports the JWT token for a specific GitHub installation in the workspace + operationId: exportInstallation + tags: + - Git Sync + parameters: + - name: workspace + in: path + required: true + schema: + type: string + - name: installationId + in: path + required: true + schema: + type: integer + responses: + "200": + description: Successfully exported the JWT token + content: + application/json: + schema: + type: object + properties: + jwt_token: + type: string + + /w/{workspace}/github_app/import: + post: + summary: Import GitHub installation from JWT token + description: Imports a GitHub installation from a JWT token exported from another instance + operationId: importInstallation + tags: + - Git Sync + parameters: + - name: workspace + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - jwt_token + properties: + jwt_token: + type: string + responses: + "200": + description: Successfully imported the installation + /users/accept_invite: post: summary: accept invite to workspace @@ -1639,7 +1794,7 @@ paths: schema: $ref: "#/components/schemas/OperatorSettings" responses: - '200': + "200": description: Operator settings updated successfully content: text/plain: @@ -1747,22 +1902,14 @@ paths: type: boolean plan: type: string - automatic_billing: - type: boolean customer_id: type: string webhook: type: string deploy_to: type: string - ai_resource: - $ref: "#/components/schemas/AIResource" - code_completion_model: - type: string - ai_models: - type: array - items: - type: string + ai_config: + $ref: "#/components/schemas/AIConfig" error_handler: type: string error_handler_extra_args: @@ -1786,8 +1933,6 @@ paths: operator_settings: $ref: "#/components/schemas/OperatorSettings" required: - - ai_models - - automatic_billing - error_handler_muted_on_cancel /w/{workspace}/workspaces/get_deploy_to: @@ -1848,48 +1993,14 @@ paths: type: boolean usage: type: number - seats: - type: number - automatic_billing: - type: boolean owner: type: string + status: + type: string required: - premium - - automatic_billing - owner - /w/{workspace}/workspaces/set_automatic_billing: - post: - summary: set automatic billing - operationId: setAutomaticBilling - tags: - - workspace - parameters: - - $ref: "#/components/parameters/WorkspaceId" - requestBody: - description: automatic billing - required: true - content: - application/json: - schema: - type: object - properties: - automatic_billing: - type: boolean - seats: - type: number - required: - - automatic_billing - responses: - "200": - description: status - content: - text/plain: - schema: - type: string - - /w/{workspace}/workspaces/threshold_alert: get: summary: get threshold alert info @@ -2013,7 +2124,7 @@ paths: type: string team_id: type: string - + /w/{workspace}/workspaces/available_teams_channels: get: summary: list available teams channels @@ -2102,7 +2213,7 @@ paths: properties: job_uuid: type: string - + /w/{workspace}/workspaces/run_teams_message_test_job: post: summary: run a job that sends a message to Teams @@ -2235,18 +2346,7 @@ paths: content: application/json: schema: - type: object - required: - - ai_models - properties: - ai_resource: - $ref: "#/components/schemas/AIResource" - code_completion_model: - type: string - ai_models: - type: array - items: - type: string + $ref: "#/components/schemas/AIConfig" responses: "200": description: status @@ -2268,23 +2368,9 @@ paths: "200": description: status content: - text/plain: + application/json: schema: - type: object - properties: - ai_provider: - $ref: "#/components/schemas/AIProvider" - exists_ai_resource: - type: boolean - code_completion_model: - type: string - ai_models: - type: array - items: - type: string - required: - - exists_ai_resource - - ai_models + $ref: "#/components/schemas/AIConfig" /w/{workspace}/workspaces/edit_error_handler: post: @@ -2618,6 +2704,8 @@ paths: type: boolean mqtt_used: type: boolean + gcp_used: + type: boolean sqs_used: type: boolean required: @@ -2627,6 +2715,7 @@ paths: - nats_used - postgres_used - mqtt_used + - gcp_used - sqs_used /w/{workspace}/users/list: get: @@ -3443,7 +3532,7 @@ paths: type: array items: type: string - + /teams/sync: post: operationId: syncTeams @@ -3451,14 +3540,14 @@ paths: tags: - teams responses: - '200': + "200": description: Teams information successfully synchronized content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeamInfo' + $ref: "#/components/schemas/TeamInfo" /teams/activities: post: @@ -3492,7 +3581,7 @@ paths: description: The card block to be sent in the Teams card responses: - '200': + "200": description: Activity processed successfully /w/{workspace}/resources/create: @@ -4342,7 +4431,7 @@ paths: type: string - name: last_parent_hash description: | - mask to filter scripts whom last parent in the chain has exact hash. + mask to filter scripts whom last parent in the chain has exact hash. Beware that each script stores only a limited number of parents. Hence the last parent hash for a script is not necessarily its top-most parent. To find the top-most parent you will have to jump from last to last hash @@ -4363,7 +4452,7 @@ paths: (default false) show only the archived files. when multiple archived hash share the same path, only the ones with the latest create_at - are + are ed. in: query schema: @@ -4412,6 +4501,13 @@ paths: in: query schema: type: boolean + - name: languages + in: query + description: | + Filter to only include scripts written in the given languages. + Accepts multiple values as a comma-separated list. + schema: + type: string responses: "200": description: All scripts @@ -4668,6 +4764,11 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/ScriptPath" + - name: keep_captures + description: keep captures + in: query + schema: + type: boolean responses: "200": description: script path @@ -4769,6 +4870,25 @@ paths: items: $ref: "#/components/schemas/ScriptHistory" + /w/{workspace}/scripts/list_paths_from_workspace_runnable/{path}: + get: + summary: list script paths using provided script as a relative import + operationId: listScriptPathsFromWorkspaceRunnable + tags: + - script + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + responses: + "200": + description: list of script paths + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/scripts/get_latest_version/{path}: get: summary: get scripts's latest version (hash) @@ -4783,8 +4903,6 @@ paths: description: Script version/hash content: application/json: - required: false - schema: $ref: "#/components/schemas/ScriptHistory" @@ -4835,8 +4953,7 @@ paths: /scripts_u/tokened_raw/{workspace}/{token}/{path}: get: - summary: - raw script by path with a token (mostly used by lsp to be used with + summary: raw script by path with a token (mostly used by lsp to be used with import maps to resolve scripts) operationId: rawScriptByPathTokened tags: @@ -4930,6 +5047,63 @@ paths: lock_error_logs: type: string + /w/{workspace}/jobs/list_selected_job_groups: + # We use post because sending a huge array as a query param can produce + # URLs that may be too long + post: + summary: list selected jobs script/flow schemas grouped by (kind, path) + operationId: listSelectedJobGroups + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: script args + required: true + content: + application/json: + schema: + type: array + items: + type: string + format: uuid + responses: + "200": + description: result + content: + text/plain: + schema: + type: array + items: + type: object + properties: + kind: + type: string + enum: ["script", "flow"] + script_path: + type: string + latest_schema: + type: object + schemas: + type: array + items: + type: object + properties: + schema: + type: object + script_hash: + type: string + job_ids: + type: array + items: + type: string + required: [schema, script_hash, job_ids] + required: + - kind + - script_path + - latest_schema + - schemas + /w/{workspace}/jobs/run/p/{path}: post: summary: run script by path @@ -5290,8 +5464,6 @@ paths: description: Flow version content: application/json: - required: false - schema: $ref: "#/components/schemas/FlowVersion" @@ -5321,8 +5493,7 @@ paths: operationId: getFlowVersion parameters: - $ref: "#/components/parameters/WorkspaceId" - - type: string - name: version + - name: version in: path required: true schema: @@ -5344,8 +5515,7 @@ paths: operationId: updateFlowHistory parameters: - $ref: "#/components/parameters/WorkspaceId" - - type: string - name: version + - name: version in: path required: true schema: @@ -5414,7 +5584,6 @@ paths: lock_error_logs: type: string - /w/{workspace}/flows/get_triggers_count/{path}: get: summary: get triggers count of flow @@ -5613,6 +5782,11 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/ScriptPath" + - name: keep_captures + description: keep captures + in: query + schema: + type: boolean responses: "200": description: flow delete @@ -5815,6 +5989,55 @@ paths: schema: type: string + /w/{workspace}/apps/create_raw: + post: + summary: create app raw + operationId: createAppRaw + tags: + - app + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: new app + required: true + content: + multipart/form-data: + schema: + type: object + properties: + app: + type: object + properties: + path: + type: string + value: {} + summary: + type: string + policy: + $ref: "#/components/schemas/Policy" + draft_only: + type: boolean + deployment_message: + type: string + custom_path: + type: string + required: + - path + - value + - summary + - policy + js: + type: string + css: + type: string + responses: + "201": + description: app created + content: + text/plain: + schema: + type: string + /w/{workspace}/apps/exists/{path}: get: summary: does an app exisst at path @@ -5920,9 +6143,27 @@ 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 + operationId: listAppPathsFromWorkspaceRunnable + tags: + - app + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/RunnableKind" + - $ref: "#/components/parameters/ScriptPath" + responses: + "200": + description: list of app paths + content: + application/json: + schema: + type: array + items: + type: string /w/{workspace}/apps/history_update/a/{id}/v/{version}: post: @@ -6154,6 +6395,49 @@ paths: schema: type: string + /w/{workspace}/apps/update_raw/{path}: + post: + summary: update app + operationId: updateAppRaw + tags: + - app + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + requestBody: + description: update app + required: true + content: + multipart/form-data: + schema: + type: object + properties: + app: + type: object + properties: + path: + type: string + summary: + type: string + value: {} + policy: + $ref: "#/components/schemas/Policy" + deployment_message: + type: string + custom_path: + type: string + js: + type: string + css: + type: string + responses: + "200": + description: app updated + content: + text/plain: + schema: + type: string + /w/{workspace}/apps/custom_path_exists/{custom_path}: get: summary: check if custom path exists @@ -6171,6 +6455,38 @@ paths: schema: type: boolean + /w/{workspace}/apps/sign_s3_objects: + post: + summary: sign s3 objects, to be used by anonymous users in public apps + operationId: signS3Objects + tags: + - app + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: s3 objects to sign + required: true + content: + application/json: + schema: + type: object + properties: + s3_objects: + type: array + items: + $ref: "#/components/schemas/S3Object" + required: + - s3_objects + responses: + "200": + description: signed s3 objects + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/S3Object" + /w/{workspace}/apps_u/execute_component/{path}: post: summary: executeComponent @@ -6355,7 +6671,6 @@ paths: in: query schema: type: boolean - requestBody: description: flow args required: true @@ -6363,7 +6678,6 @@ paths: application/json: schema: $ref: "#/components/schemas/ScriptArgs" - responses: "201": description: job created @@ -6373,6 +6687,57 @@ paths: type: string format: uuid + /w/{workspace}/jobs/run/batch_rerun_jobs: + post: + summary: re-run multiple jobs + operationId: batchReRunJobs + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: list of job ids to re run and arg tranforms + required: true + content: + application/json: + schema: + type: object + required: [job_ids, script_options_by_path, flow_options_by_path] + properties: + job_ids: + type: array + items: + type: string + script_options_by_path: + type: object + additionalProperties: + type: object + properties: + input_transforms: + type: object + additionalProperties: + $ref: "#/components/schemas/InputTransform" + use_latest_version: + type: boolean + flow_options_by_path: + type: object + additionalProperties: + type: object + properties: + input_transforms: + type: object + additionalProperties: + $ref: "#/components/schemas/InputTransform" + use_latest_version: + type: boolean + responses: + "201": + description: stream of created job uuids separated by \n. Lines may start with 'Error:' + content: + text/event-stream: + schema: + type: string + /w/{workspace}/jobs/restart/f/{id}/from/{step_id}/{branch_or_iteration_n}: post: summary: restart a completed flow at a given step @@ -6389,8 +6754,7 @@ paths: schema: type: string - name: branch_or_iteration_n - description: - for branchall or loop, the iteration at which the flow should + description: for branchall or loop, the iteration at which the flow should restart required: true in: path @@ -6639,6 +7003,7 @@ paths: - $ref: "#/components/parameters/OrderDesc" - $ref: "#/components/parameters/CreatedBy" - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/Worker" - $ref: "#/components/parameters/ScriptExactPath" - $ref: "#/components/parameters/ScriptStartPath" - $ref: "#/components/parameters/SchedulePath" @@ -6652,6 +7017,7 @@ paths: - $ref: "#/components/parameters/Running" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -6756,10 +7122,82 @@ paths: schema: type: integer - /w/{workspace}/jobs/queue/list_filtered_uuids: + /w/{workspace}/jobs/list_filtered_uuids: get: summary: get the ids of all jobs matching the given filters - operationId: listFilteredUuids + operationId: listFilteredJobsUuids + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/CreatedBy" + - $ref: "#/components/parameters/Label" + - $ref: "#/components/parameters/Worker" + - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/ScriptExactPath" + - $ref: "#/components/parameters/ScriptStartPath" + - $ref: "#/components/parameters/SchedulePath" + - $ref: "#/components/parameters/ScriptExactHash" + - $ref: "#/components/parameters/StartedBefore" + - $ref: "#/components/parameters/StartedAfter" + - $ref: "#/components/parameters/CreatedBefore" + - $ref: "#/components/parameters/CreatedAfter" + - $ref: "#/components/parameters/CreatedOrStartedBefore" + - $ref: "#/components/parameters/Running" + - $ref: "#/components/parameters/ScheduledForBeforeNow" + - $ref: "#/components/parameters/CreatedOrStartedAfter" + - $ref: "#/components/parameters/CreatedOrStartedAfterCompletedJob" + - $ref: "#/components/parameters/JobKinds" + - $ref: "#/components/parameters/Suspended" + - $ref: "#/components/parameters/ArgsFilter" + - $ref: "#/components/parameters/Tag" + - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + - name: is_skipped + description: is the job skipped + in: query + schema: + type: boolean + - name: is_flow_step + description: is the job a flow step + in: query + schema: + type: boolean + - name: has_null_parent + description: has null parent + in: query + schema: + type: boolean + - name: success + description: filter on successful jobs + in: query + schema: + type: boolean + - name: all_workspaces + description: get jobs from all workspaces (only valid if request come from the `admins` workspace) + in: query + schema: + type: boolean + - name: is_not_schedule + description: is not a scheduled job + in: query + schema: + type: boolean + responses: + "200": + description: uuids of jobs + content: + application/json: + schema: + type: array + items: + type: string + + /w/{workspace}/jobs/queue/list_filtered_uuids: + get: + summary: get the ids of all queued jobs matching the given filters + operationId: listFilteredQueueUuids tags: - job parameters: @@ -6780,6 +7218,7 @@ paths: - $ref: "#/components/parameters/Running" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -6846,6 +7285,7 @@ paths: - $ref: "#/components/parameters/OrderDesc" - $ref: "#/components/parameters/CreatedBy" - $ref: "#/components/parameters/Label" + - $ref: "#/components/parameters/Worker" - $ref: "#/components/parameters/ParentJob" - $ref: "#/components/parameters/ScriptExactPath" - $ref: "#/components/parameters/ScriptStartPath" @@ -6857,6 +7297,7 @@ paths: - $ref: "#/components/parameters/JobKinds" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -6900,6 +7341,7 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/CreatedBy" - $ref: "#/components/parameters/Label" + - $ref: "#/components/parameters/Worker" - $ref: "#/components/parameters/ParentJob" - $ref: "#/components/parameters/ScriptExactPath" - $ref: "#/components/parameters/ScriptStartPath" @@ -6919,6 +7361,7 @@ paths: - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" - name: is_skipped @@ -7146,7 +7589,8 @@ paths: description: job log content: text/plain: - type: string + schema: + type: string /w/{workspace}/jobs_u/get_flow_debug_info/{id}: get: @@ -7453,6 +7897,52 @@ paths: "200": description: Interactive slack approval message sent successfully + /w/{workspace}/jobs/teams_approval/{id}: + get: + summary: generate interactive teams approval for suspended job + operationId: getTeamsApprovalPayload + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/JobId" + - name: approver + in: query + schema: + type: string + - name: message + in: query + schema: + type: string + - name: team_name + in: query + required: true + schema: + type: string + - name: channel_name + in: query + required: true + schema: + type: string + - name: flow_step_id + in: query + required: true + schema: + type: string + - name: default_args_json + in: query + required: false + schema: + type: string + - name: dynamic_enums_json + in: query + required: false + schema: + type: string + responses: + "200": + description: Interactive slack approval message sent successfully + /w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}: get: summary: resume a job for a suspended flow @@ -7961,6 +8451,76 @@ paths: "201": description: default error handler set + /w/{workspace}/openapi/generate: + post: + summary: generate openapi spec from http routes/webhook + operationId: generateOpenapiSpec + tags: + - openapi + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: openapi spec info and url + content: + application/json: + schema: + $ref: "#/components/schemas/GenerateOpenapiSpec" + responses: + "200": + description: openapi spec + content: + text/plain: + schema: + type: string + + /w/{workspace}/openapi/download: + post: + summary: Download the OpenAPI v3.1 spec as a file + operationId: DownloadOpenapiSpec + tags: + - openapi + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: openapi spec info and url + content: + application/json: + schema: + $ref: "#/components/schemas/GenerateOpenapiSpec" + responses: + "200": + description: Downloaded OpenAPI spec + content: + application/octet-stream: + schema: + type: string + format: binary + + /w/{workspace}/http_triggers/create_many: + post: + summary: create many HTTP triggers + operationId: createHttpTriggers + tags: + - http_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: new http trigger + required: true + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/NewHttpTrigger" + responses: + "201": + description: http trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/http_triggers/create: post: summary: create http trigger @@ -8112,10 +8672,11 @@ paths: route_path: type: string http_method: - type: string - enum: ["get", "post", "put", "delete", "patch"] + $ref: "#/components/schemas/HttpMethod" trigger_path: type: string + workspaced_route: + type: boolean required: - route_path - http_method @@ -8562,7 +9123,7 @@ paths: summary: delete nats trigger operationId: deleteNatsTrigger tags: - - nats_trigger + - nats_trigger parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" @@ -8591,7 +9152,6 @@ paths: schema: $ref: "#/components/schemas/NatsTrigger" - /w/{workspace}/nats_triggers/list: get: summary: list nats triggers @@ -8626,7 +9186,6 @@ paths: items: $ref: "#/components/schemas/NatsTrigger" - /w/{workspace}/nats_triggers/exists/{path}: get: summary: does nats trigger exists @@ -8673,7 +9232,6 @@ paths: schema: type: string - /w/{workspace}/nats_triggers/test: post: summary: test NATS connection @@ -9080,7 +9638,282 @@ paths: schema: type: string + /w/{workspace}/gcp_triggers/create: + post: + summary: create gcp trigger + operationId: createGcpTrigger + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: new gcp trigger + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/GcpTriggerData" + responses: + "201": + description: gcp trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/gcp_triggers/update/{path}: + post: + summary: update gcp trigger + operationId: updateGcpTrigger + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/GcpTriggerData" + responses: + "200": + description: gcp trigger updated + content: + text/plain: + schema: + type: string + + /w/{workspace}/gcp_triggers/delete/{path}: + delete: + summary: delete gcp trigger + operationId: deleteGcpTrigger + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: gcp trigger deleted + content: + text/plain: + schema: + type: string + + /w/{workspace}/gcp_triggers/get/{path}: + get: + summary: get gcp trigger + operationId: getGcpTrigger + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: gcp trigger deleted + content: + application/json: + schema: + $ref: "#/components/schemas/GcpTrigger" + + /w/{workspace}/gcp_triggers/list: + get: + summary: list gcp triggers + operationId: listGcpTriggers + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + required: true + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + "200": + description: gcp trigger list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/GcpTrigger" + + /w/{workspace}/gcp_triggers/exists/{path}: + get: + summary: does gcp trigger exists + operationId: existsGcpTrigger + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: gcp trigger exists + content: + application/json: + schema: + type: boolean + + /w/{workspace}/gcp_triggers/setenabled/{path}: + post: + summary: set enabled gcp trigger + operationId: setGcpTriggerEnabled + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: updated gcp trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + "200": + description: gcp trigger enabled set + content: + text/plain: + schema: + type: string + + /w/{workspace}/gcp_triggers/test: + post: + summary: test gcp connection + operationId: testGcpConnection + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: test gcp connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + "200": + description: try to connect to a gcp broker + content: + text/plain: + schema: + type: string + + + /w/{workspace}/gcp_triggers/subscriptions/delete/{path}: + delete: + summary: delete gcp trigger + operationId: deleteGcpSubscription + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: args to delete subscription from google cloud + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteGcpSubscription" + responses: + "200": + description: gcp trigger deleted + content: + text/plain: + schema: + type: string + + /w/{workspace}/gcp_triggers/topics/list/{path}: + get: + summary: list all topics of google cloud service + operationId: listGoogleTopics + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: get all google topics + content: + application/json: + schema: + type: array + items: + type: string + + /w/{workspace}/gcp_triggers/subscriptions/list/{path}: + post: + summary: list all subscription of a give topic from google cloud service + operationId: listAllTGoogleTopicSubscriptions + tags: + - gcp_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: args to get subscription's topic from google cloud + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/GetAllTopicSubscription" + responses: + "200": + description: get all google topic subscriptions name + content: + application/json: + schema: + type: array + 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 @@ -9088,8 +9921,8 @@ paths: tags: - postgres_trigger parameters: - - $ref: "#/components/parameters/WorkspaceId" - - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" responses: "200": description: boolean that indicates if postgres is set to logical level or not @@ -9159,7 +9992,7 @@ paths: /w/{workspace}/postgres_triggers/slot/create/{path}: post: - summary: create replication slot for postgres + summary: create replication slot for postgres operationId: createPostgresReplicationSlot tags: - postgres_trigger @@ -9292,7 +10125,6 @@ paths: schema: type: string - /w/{workspace}/postgres_triggers/publication/delete/{publication}/{path}: delete: summary: delete postgres publication @@ -10033,7 +10865,7 @@ paths: application/json: schema: $ref: "#/components/schemas/Folder" - + /w/{workspace}/folders/exists/{name}: get: summary: exists folder @@ -10280,7 +11112,8 @@ paths: description: a config content: application/json: - schema: {} + schema: + $ref: "#/components/schemas/Configs" /configs/update/{name}: post: @@ -10356,6 +11189,150 @@ 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 + operationId: createAgentToken + tags: + - agent_workers + requestBody: + description: agent token + required: true + content: + application/json: + schema: + type: object + properties: + worker_group: + type: string + tags: + type: array + items: + type: string + exp: + type: integer + required: + - worker_group + - tags + - exp + responses: + "200": + description: agent token created + content: + application/json: + schema: + type: string + + /agent_workers/blacklist_token: + post: + summary: blacklist agent token (requires super admin) + operationId: blacklistAgentToken + tags: + - agent_workers + requestBody: + description: token to blacklist + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The agent token to blacklist + expires_at: + type: string + format: date-time + description: Optional expiration date for the blacklist entry + required: + - token + responses: + "200": + description: token blacklisted successfully + + /agent_workers/remove_blacklist_token: + post: + summary: remove agent token from blacklist (requires super admin) + operationId: removeBlacklistAgentToken + tags: + - agent_workers + requestBody: + description: token to remove from blacklist + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The agent token to remove from blacklist + required: + - token + responses: + "200": + description: token removed from blacklist successfully + + /agent_workers/list_blacklisted_tokens: + get: + summary: list blacklisted agent tokens (requires super admin) + operationId: listBlacklistedAgentTokens + tags: + - agent_workers + parameters: + - name: include_expired + in: query + description: Whether to include expired blacklisted tokens + schema: + type: boolean + default: false + responses: + "200": + description: list of blacklisted tokens + content: + application/json: + schema: + type: array + items: + type: object + properties: + token: + type: string + description: The blacklisted token (without prefix) + expires_at: + type: string + format: date-time + description: When the blacklist entry expires + blacklisted_at: + type: string + format: date-time + description: When the token was blacklisted + blacklisted_by: + type: string + description: Email of the user who blacklisted the token + required: + - token + - expires_at + - blacklisted_at + - blacklisted_by + /w/{workspace}/acls/get/{kind}/{path}: get: summary: get granular acls @@ -10370,7 +11347,8 @@ paths: required: true schema: type: string - enum: [ + enum: + [ script, group_, resource, @@ -10386,6 +11364,7 @@ paths: nats_trigger, postgres_trigger, mqtt_trigger, + gcp_trigger, sqs_trigger ] responses: @@ -10429,6 +11408,7 @@ paths: nats_trigger, postgres_trigger, mqtt_trigger, + gcp_trigger, sqs_trigger ] requestBody: @@ -10483,6 +11463,7 @@ paths: nats_trigger, postgres_trigger, mqtt_trigger, + gcp_trigger, sqs_trigger ] requestBody: @@ -10535,6 +11516,10 @@ paths: responses: "200": description: capture config set + content: + application/json: + schema: + type: object /w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}: post: @@ -10601,6 +11586,34 @@ paths: items: $ref: "#/components/schemas/Capture" + /w/{workspace}/capture/move/{runnable_kind}/{path}: + post: + summary: move captures and configs for a script or flow + operationId: moveCapturesAndConfigs + tags: + - capture + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/RunnableKind" + - $ref: "#/components/parameters/Path" + requestBody: + description: move captures and configs to a new path + required: true + content: + application/json: + schema: + type: object + properties: + new_path: + type: string + responses: + "200": + description: captures and configs moved + content: + text/plain: + schema: + type: string + /w/{workspace}/capture/{id}: get: summary: get a capture @@ -10695,6 +11708,7 @@ paths: - $ref: "#/components/parameters/RunnableTypeQuery" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" + - $ref: "#/components/parameters/ArgsFilter" - name: include_preview in: query schema: @@ -10829,8 +11843,7 @@ paths: /w/{workspace}/job_helpers/duckdb_connection_settings: post: - summary: - Converts an S3 resource to the set of instructions necessary to connect + summary: Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket operationId: duckdbConnectionSettings tags: @@ -10859,8 +11872,7 @@ paths: type: string /w/{workspace}/job_helpers/v2/duckdb_connection_settings: post: - summary: - Converts an S3 resource to the set of instructions necessary to connect + summary: Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket operationId: duckdbConnectionSettingsV2 tags: @@ -10896,8 +11908,7 @@ paths: /w/{workspace}/job_helpers/polars_connection_settings: post: - summary: - Converts an S3 resource to the set of arguments necessary to connect + summary: Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket operationId: polarsConnectionSettings tags: @@ -10941,8 +11952,7 @@ paths: - client_kwargs /w/{workspace}/job_helpers/v2/polars_connection_settings: post: - summary: - Converts an S3 resource to the set of arguments necessary to connect + summary: Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket operationId: polarsConnectionSettingsV2 tags: @@ -11019,8 +12029,7 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" requestBody: - description: - S3 resource path to use. If empty, the S3 resource defined in the + description: S3 resource path to use. If empty, the S3 resource defined in the workspace settings will be used required: true content: @@ -11636,7 +12645,6 @@ paths: text/plain: schema: type: string - /concurrency_groups/list: get: summary: List all concurrency groups @@ -11720,6 +12728,7 @@ paths: - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" - name: is_skipped @@ -11773,6 +12782,11 @@ paths: required: true schema: type: string + - name: pagination_offset + in: query + required: false + schema: + type: integer responses: "200": description: search results @@ -11785,15 +12799,26 @@ paths: description: a list of the terms that couldn't be parsed (and thus ignored) type: array items: - type: object - properties: - dancer: - type: string + type: string hits: description: the jobs that matched the query type: array items: $ref: "#/components/schemas/JobSearchHit" + hit_count: + description: how many jobs matched in total + type: number + index_metadata: + description: Metadata about the index current state + type: object + properties: + indexed_until: + description: Datetime of the most recently indexed job + type: string + format: date-time + lost_lock_ownership: + description: Is the current indexer service being replaced + type: boolean /srch/index/search/service_logs: get: @@ -12055,10 +13080,15 @@ components: in: query schema: type: string + Worker: + name: worker + description: worker this job was ran on + in: query + schema: + type: string ParentJob: name: parent_job - description: - The parent job that is at the origin and responsible for the execution + description: The parent job that is at the origin and responsible for the execution of this script if any in: query schema: @@ -12078,8 +13108,7 @@ components: type: string NewJobId: name: job_id - description: - The job id to assign to the created job. if missing, job is chosen + description: The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) in: query @@ -12170,8 +13199,7 @@ components: format: date-time CreatedOrStartedAfter: name: created_or_started_after - description: - filter on created_at for non non started job and started_at otherwise + description: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query schema: @@ -12179,8 +13207,7 @@ components: format: date-time CreatedOrStartedAfterCompletedJob: name: created_or_started_after_completed_jobs - description: - filter on created_at for non non started job and started_at otherwise + description: filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query schema: @@ -12188,8 +13215,7 @@ components: format: date-time CreatedOrStartedBefore: name: created_or_started_before - description: - filter on created_at for non non started job and started_at otherwise + description: filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query schema: @@ -12219,6 +13245,12 @@ components: in: query schema: type: boolean + AllowWildcards: + name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: + type: boolean ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) @@ -12271,8 +13303,7 @@ components: enum: [Create, Update, Delete, Execute] JobKinds: name: job_kinds - description: - filter on job kind (values 'preview', 'script', 'dependencies', 'flow') + description: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query schema: @@ -12319,23 +13350,123 @@ components: enum: [script, flow] schemas: - $ref: "../../openflow.openapi.yaml#/components/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 AIProvider: type: string - enum: [openai, anthropic, mistral, deepseek, googleai, groq, openrouter, customai] + enum: [openai, azure_openai, anthropic, mistral, deepseek, googleai, groq, openrouter, togetherai, customai] - AIResource: + AIProviderModel: type: object properties: - path: + model: type: string provider: $ref: "#/components/schemas/AIProvider" required: - - path + - model - provider + AIProviderConfig: + type: object + properties: + resource_path: + type: string + models: + type: array + items: + type: string + required: + - resource_path + - models + + AIConfig: + type: object + properties: + providers: + type: object + additionalProperties: + $ref: "#/components/schemas/AIProviderConfig" + default_model: + $ref: "#/components/schemas/AIProviderModel" + code_completion_model: + $ref: "#/components/schemas/AIProviderModel" + + Alert: + type: object + properties: + name: + type: string + tags_to_monitor: + type: array + items: + type: string + jobs_num_threshold: + type: integer + alert_cooldown_seconds: + type: integer + alert_time_threshold_seconds: + type: integer + required: + - name + - tags_to_monitor + - jobs_num_threshold + - alert_cooldown_seconds + - alert_time_threshold_seconds + + Configs: + type: object + nullable: true + properties: + alerts: + type: array + items: + $ref: '#/components/schemas/Alert' + Script: type: object properties: @@ -12655,14 +13786,14 @@ components: "singlescriptflow", "flowscript", "flownode", - "appscript", + "appscript" ] schedule_path: type: string permissioned_as: type: string description: | - The user (u/userfoo) or group (g/groupfoo) whom + The user (u/userfoo) or group (g/groupfoo) whom the execution of this script will be permissioned_as and by extension its DT_TOKEN. flow_status: $ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus" @@ -12690,6 +13821,8 @@ components: type: number preprocessed: type: boolean + worker: + type: string required: - id - running @@ -12760,14 +13893,14 @@ components: "singlescriptflow", "flowscript", "flownode", - "appscript", + "appscript" ] schedule_path: type: string permissioned_as: type: string description: | - The user (u/userfoo) or group (g/groupfoo) whom + The user (u/userfoo) or group (g/groupfoo) whom the execution of this script will be permissioned_as and by extension its DT_TOKEN. flow_status: $ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus" @@ -12799,6 +13932,8 @@ components: type: number preprocessed: type: boolean + worker: + type: string required: - id - created_by @@ -13208,7 +14343,7 @@ components: "bytes", "dict", "datetime", - "sql", + "sql" ] - type: object properties: @@ -13248,7 +14383,7 @@ components: "bytes", "dict", "datetime", - "sql", + "sql" ] - type: object properties: @@ -13274,7 +14409,7 @@ components: "bytes", "dict", "datetime", - "sql", + "sql" ] - type: object properties: @@ -13306,8 +14441,7 @@ components: ScriptLang: type: string - enum: - [ + enum: [ python3, deno, go, @@ -13325,7 +14459,11 @@ components: php, rust, ansible, - csharp + csharp, + nu, + java, + duckdb + # for related places search: ADD_NEW_LANG ] Preview: @@ -13335,6 +14473,8 @@ components: type: string path: type: string + script_hash: + type: string args: $ref: "#/components/schemas/ScriptArgs" language: @@ -13552,6 +14692,8 @@ components: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" summary: type: string + description: + type: string no_flow_overlap: type: boolean tag: @@ -13638,6 +14780,8 @@ components: type: boolean summary: type: string + description: + type: string tag: type: string paused_until: @@ -13689,6 +14833,8 @@ components: type: boolean summary: type: string + description: + type: string tag: type: string paused_until: @@ -13735,6 +14881,125 @@ components: - edited_at - is_flow + AuthenticationMethod: + type: string + enum: + - none + - windmill + - api_key + - basic_http + - custom_script + - signature + + RunnableKind: + type: string + enum: + - script + - flow + + OpenapiSpecFormat: + type: string + enum: + - yaml + - json + + OpenapiHttpRouteFilters: + type: object + properties: + folder_regex: + type: string + path_regex: + type: string + route_path_regex: + type: string + required: + - folder_regex + - path_regex + - route_path_regex + + WebhookFilters: + type: object + properties: + user_or_folder_regex: + type: string + enum: + - "*" + - u + - f + user_or_folder_regex_value: + type: string + path: + type: string + runnable_kind: + $ref: "#/components/schemas/RunnableKind" + required: + - user_or_folder_regex + - user_or_folder_regex_value + - path + - runnable_kind + + OpenapiV3Info: + type: object + properties: + title: + type: string + version: + type: string + description: + type: string + terms_of_service: + type: string + contact: + type: object + properties: + name: + type: string + url: + type: string + email: + type: string + license: + type: object + properties: + name: + type: string + identifier: + type: string + url: + type: string + required: + - name + required: + - title + - version + + GenerateOpenapiSpec: + type: object + properties: + info: + $ref: "#/components/schemas/OpenapiV3Info" + url: + type: string + openapi_spec_format: + $ref: "#/components/schemas/OpenapiSpecFormat" + http_route_filters: + type: array + items: + $ref: "#/components/schemas/OpenapiHttpRouteFilters" + webhook_filters: + type: array + items: + $ref: "#/components/schemas/WebhookFilters" + + HttpMethod: + type: string + enum: + - get + - post + - put + - delete + - patch + HttpTrigger: allOf: - $ref: "#/components/schemas/TriggerExtraProperty" @@ -13754,26 +15019,35 @@ components: required: - s3 http_method: + $ref: "#/components/schemas/HttpMethod" + authentication_resource_path: + type: string + summary: + type: string + description: type: string - enum: - - get - - post - - put - - delete - - patch is_async: type: boolean - requires_auth: - type: boolean + authentication_method: + $ref: "#/components/schemas/AuthenticationMethod" is_static_website: type: boolean + workspaced_route: + type: boolean + wrap_body: + type: boolean + raw_string: + type: boolean required: - route_path - is_async - - requires_auth + - authentication_method - http_method - is_static_website + - workspaced_route + - wrap_body + - raw_string NewHttpTrigger: type: object @@ -13784,6 +15058,12 @@ components: type: string route_path: type: string + workspaced_route: + type: boolean + summary: + type: string + description: + type: string static_asset_config: type: object properties: @@ -13798,19 +15078,19 @@ components: is_flow: type: boolean http_method: + $ref: "#/components/schemas/HttpMethod" + authentication_resource_path: type: string - enum: - - get - - post - - put - - delete - - patch is_async: type: boolean - requires_auth: - type: boolean + authentication_method: + $ref: "#/components/schemas/AuthenticationMethod" is_static_website: type: boolean + wrap_body: + type: boolean + raw_string: + type: boolean required: - path @@ -13818,7 +15098,7 @@ components: - route_path - is_flow - is_async - - requires_auth + - authentication_method - http_method - is_static_website @@ -13831,6 +15111,12 @@ components: type: string route_path: type: string + summary: + type: string + description: + type: string + workspaced_route: + type: boolean static_asset_config: type: object properties: @@ -13842,29 +15128,29 @@ components: type: string required: - s3 + authentication_resource_path: + type: string is_flow: type: boolean http_method: - type: string - enum: - - get - - post - - put - - delete - - patch + $ref: "#/components/schemas/HttpMethod" is_async: type: boolean - requires_auth: - type: boolean + authentication_method: + $ref: "#/components/schemas/AuthenticationMethod" is_static_website: type: boolean + wrap_body: + type: boolean + raw_string: + type: boolean required: - path - script_path - is_flow - kind - is_async - - requires_auth + - authentication_method - http_method - is_static_website @@ -13894,6 +15180,8 @@ components: type: number mqtt_count: type: number + gcp_count: + type: number sqs_count: type: number @@ -14046,8 +15334,8 @@ components: MqttQoS: type: string - enum: ['qos0', 'qos1', 'qos2'] - + enum: ["qos0", "qos1", "qos2"] + MqttV3Config: type: object properties: @@ -14117,7 +15405,7 @@ components: type: object properties: mqtt_resource_path: - type: string + type: string subscribe_topics: type: array items: @@ -14175,9 +15463,125 @@ components: - script_path - is_flow - enabled - - subscribe_topics + - subscribe_topics - mqtt_resource_path - + + DeliveryType: + type: string + enum: + - push + - pull + + PushConfig: + type: object + properties: + audience: + type: string + authenticate: + type: boolean + required: + - authenticate + - base_endpoint + + GcpTrigger: + allOf: + - $ref: "#/components/schemas/TriggerExtraProperty" + type: object + properties: + gcp_resource_path: + type: string + topic_id: + type: string + subscription_id: + type: string + server_id: + type: string + delivery_type: + $ref: "#/components/schemas/DeliveryType" + delivery_config: + $ref: "#/components/schemas/PushConfig" + subscription_mode: + $ref: "#/components/schemas/SubscriptionMode" + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + required: + - gcp_resource_path + - topic_id + - subscription_id + - enabled + - delivery_type + - subscription_mode + + + SubscriptionMode: + type: string + enum: + - existing + - create_update + description: "The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription." + + + GcpTriggerData: + type: object + properties: + gcp_resource_path: + type: string + subscription_mode: + $ref: "#/components/schemas/SubscriptionMode" + topic_id: + type: string + subscription_id: + type: string + base_endpoint: + type: string + delivery_type: + $ref: "#/components/schemas/DeliveryType" + delivery_config: + $ref: "#/components/schemas/PushConfig" + path: + type: string + script_path: + type: string + is_flow: + type: boolean + enabled: + type: boolean + required: + - path + - script_path + - is_flow + - gcp_resource_path + - topic_id + - subscription_mode + + GetAllTopicSubscription: + type: object + properties: + topic_id: + type: string + required: + - topic_id + + + DeleteGcpSubscription: + type: object + properties: + subscription_id: + type: string + required: + - subscription_id + + AwsAuthResourceType: + type: string + enum: + - oidc + - credentials + SqsTrigger: allOf: - $ref: "#/components/schemas/TriggerExtraProperty" @@ -14185,6 +15589,8 @@ components: properties: queue_url: type: string + aws_auth_resource_type: + $ref: "#/components/schemas/AwsAuthResourceType" aws_resource_path: type: string message_attributes: @@ -14205,12 +15611,15 @@ components: - queue_url - aws_resource_path - enabled + - aws_auth_resource_type NewSqsTrigger: type: object properties: queue_url: type: string + aws_auth_resource_type: + $ref: "#/components/schemas/AwsAuthResourceType" aws_resource_path: type: string message_attributes: @@ -14227,16 +15636,19 @@ components: type: boolean required: - queue_url - - aws_resource_path + - aws_resource_path - path - script_path - is_flow + - aws_auth_resource_type EditSqsTrigger: type: object properties: queue_url: type: string + aws_auth_resource_type: + $ref: "#/components/schemas/AwsAuthResourceType" aws_resource_path: type: string message_attributes: @@ -14253,12 +15665,12 @@ components: type: boolean required: - queue_url - - aws_resource_path + - aws_resource_path - path - script_path - is_flow - enabled - + - aws_auth_resource_type Slot: type: object @@ -14287,7 +15699,7 @@ components: type: string required: - transaction_to_track - + TableToTrack: type: array items: @@ -14335,7 +15747,7 @@ components: - postgres_resource_path - relations - language - + PostgresTrigger: allOf: - $ref: "#/components/schemas/TriggerExtraProperty" @@ -14354,8 +15766,8 @@ components: error: type: string last_server_ping: - type: string - format: date-time + type: string + format: date-time required: - enabled - postgres_resource_path @@ -14387,7 +15799,7 @@ components: - is_flow - enabled - postgres_resource_path - + EditPostgresTrigger: type: object properties: @@ -14525,7 +15937,7 @@ components: type: string enabled: type: boolean - + required: - nats_resource_path - use_jetstream @@ -14555,7 +15967,7 @@ components: type: string enabled: type: boolean - + required: - path - script_path @@ -14585,7 +15997,7 @@ components: type: string is_flow: type: boolean - + required: - path - script_path @@ -14978,6 +16390,8 @@ components: execution_mode: type: string enum: [viewer, publisher, anonymous] + raw_app: + type: boolean required: - id - workspace_id @@ -15133,10 +16547,8 @@ components: - access_token HubScriptKind: - name: kind - schema: - type: string - enum: [script, failure, trigger, approval] + type: string + enum: [script, failure, trigger, approval] PolarsClientKwargs: type: object @@ -15151,13 +16563,7 @@ components: properties: type: type: string - enum: - [ - "S3Storage", - "AzureBlobStorage", - "AzureWorkloadIdentity", - "S3AwsOidc", - ] + enum: ["S3Storage", "AzureBlobStorage", "AzureWorkloadIdentity", "S3AwsOidc"] s3_resource_path: type: string azure_blob_resource_path: @@ -15172,12 +16578,7 @@ components: type: type: string enum: - [ - "S3Storage", - "AzureBlobStorage", - "AzureWorkloadIdentity", - "S3AwsOidc", - ] + ["S3Storage", "AzureBlobStorage", "AzureWorkloadIdentity", "S3AwsOidc"] s3_resource_path: type: string azure_blob_resource_path: @@ -15269,6 +16670,7 @@ components: - schedule - user - group + - trigger repositories: type: array items: @@ -15336,6 +16738,7 @@ components: - schedule - user - group + - trigger required: - script_path - git_repo_resource_path @@ -15563,15 +16966,15 @@ components: CaptureTriggerKind: type: string - enum: [webhook, http, websocket, kafka, email, nats, postgres, sqs, mqtt] + enum: [webhook, http, websocket, kafka, email, nats, postgres, sqs, mqtt, gcp] Capture: type: object properties: trigger_kind: $ref: "#/components/schemas/CaptureTriggerKind" - payload: {} - trigger_extra: {} + main_args: {} + preprocessor_args: {} id: type: integer created_at: @@ -15579,7 +16982,8 @@ components: format: date-time required: - trigger_kind - - payload + - main_args + - preprocessor_args - id - created_at CaptureConfig: @@ -15657,7 +17061,7 @@ components: type: array description: List of channels within the team items: - $ref: '#/components/schemas/ChannelInfo' + $ref: "#/components/schemas/ChannelInfo" ChannelInfo: type: object @@ -15682,4 +17086,81 @@ components: service_url: type: string description: The service URL for the channel - example: "https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/" \ No newline at end of file + example: "https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/" + + GithubInstallations: + type: array + items: + type: object + properties: + workspace_id: + type: string + installation_id: + type: number + account_id: + type: string + repositories: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + required: + - name + - url + required: + - installation_id + - account_id + - repositories + + WorkspaceGithubInstallation: + type: object + properties: + account_id: + type: string + installation_id: + type: number + required: + - account_id + - installation_id + + S3Object: + type: object + properties: + s3: + type: string + filename: + type: string + storage: + type: string + presigned: + type: string + required: + - s3 + TeamsChannel: + type: object + required: + - team_id + - team_name + - channel_id + - channel_name + properties: + team_id: + type: string + description: Microsoft Teams team ID + minLength: 1 + team_name: + type: string + description: Microsoft Teams team name + minLength: 1 + channel_id: + type: string + description: Microsoft Teams channel ID + minLength: 1 + channel_name: + type: string + description: Microsoft Teams channel name + minLength: 1 diff --git a/backend/windmill-api/src/agent_workers_oss.rs b/backend/windmill-api/src/agent_workers_oss.rs new file mode 100644 index 0000000000..dabb2cb079 --- /dev/null +++ b/backend/windmill-api/src/agent_workers_oss.rs @@ -0,0 +1,52 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::agent_workers_ee::*; + +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2042 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +#[cfg(not(feature = "private"))] +use crate::db::DB; + +#[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( + db: DB, + _base_internal_url: String, +) -> ( + Router, + Vec>, + Option, +) { + use windmill_common::worker::Connection; + use windmill_worker::JobCompletedSender; + + let (job_completed_tx, _job_completed_rx) = + JobCompletedSender::new(&Connection::Sql(db.clone()), 10); + + let router = Router::new(); + + (router, vec![], Some(job_completed_tx)) +} + +#[cfg(not(feature = "private"))] +pub struct AgentCache {} + +#[cfg(not(feature = "private"))] +impl AgentCache { + pub fn new() -> Self { + AgentCache {} + } +} diff --git a/backend/windmill-api/src/ai.rs b/backend/windmill-api/src/ai.rs index d9469900b1..44a6b2986e 100644 --- a/backend/windmill-api/src/ai.rs +++ b/backend/windmill-api/src/ai.rs @@ -3,192 +3,109 @@ use crate::{ variables::get_variable_or_self, }; -use anthropic::AnthropicCache; -use anyhow::Context; use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router}; -use http::HeaderMap; -use lazy_static::lazy_static; +use http::{HeaderMap, Method}; use quick_cache::sync::Cache; use reqwest::{Client, RequestBuilder}; use serde::{Deserialize, Serialize}; -use serde_json::value::{RawValue, Value}; +use serde_json::value::RawValue; use std::collections::HashMap; -use windmill_audit::{audit_ee::audit_log, ActionKind}; +use windmill_audit::{audit_oss::audit_log, ActionKind}; use windmill_common::error::{to_anyhow, Error, Result}; -use mistral::MistralCache; -use openai::OpenaiCache; -use openai_api_compatible::OpenaiApiCompatibleCache; - lazy_static::lazy_static! { static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new() .timeout(std::time::Duration::from_secs(60 * 5)) .user_agent("windmill/beta") .build().unwrap(); + + static ref OPENAI_AZURE_BASE_PATH: Option = std::env::var("OPENAI_AZURE_BASE_PATH").ok(); + + pub static ref AI_REQUEST_CACHE: Cache<(String, AIProvider), ExpiringAIRequestConfig> = Cache::new(500); } -mod openai_api_compatible { - use super::*; +const AZURE_API_VERSION: &str = "2025-04-01-preview"; +const OPENAI_BASE_URL: &str = "https://api.openai.com/v1"; - #[derive(Deserialize, Clone, Debug)] - pub struct OpenaiApiCompatibleCache { - pub base_url: String, - pub api_key: Option, - } +#[derive(Deserialize, Debug)] +struct AIOAuthResource { + client_id: String, + client_secret: String, + token_url: String, + user: Option, +} - impl OpenaiApiCompatibleCache { - pub fn prepare_request(self, path: &str, body: Bytes) -> Result { - let url = format!("{}/{}", self.base_url, path); +#[derive(Deserialize, Debug)] +struct AIStandardResource { + #[serde(alias = "baseUrl")] + base_url: Option, + #[serde(alias = "apiKey")] + api_key: Option, + organization_id: Option, +} - let mut request = HTTP_CLIENT - .post(url) - .header("content-type", "application/json") - .body(body); +#[derive(Deserialize, Debug)] +struct OAuthTokens { + access_token: String, +} - if let Some(api_key) = self.api_key { - request = request.header("Authorization", format!("Bearer {}", api_key)); - } +#[derive(Deserialize, Debug)] +#[serde(untagged)] +enum AIResource { + OAuth(AIOAuthResource), + Standard(AIStandardResource), +} - Ok(request) - } - } +#[derive(Deserialize, Clone, Debug)] +struct AIRequestConfig { + pub base_url: String, + pub api_key: Option, + pub access_token: Option, + pub organization_id: Option, + pub user: Option, +} - pub async fn get_cached_value( +impl AIRequestConfig { + pub async fn new( + provider: &AIProvider, db: &DB, w_id: &str, - resource: Value, - base_url: Option, - ) -> Result { - let mut resource: OpenaiApiCompatibleCache = if let Some(base_url) = base_url { - let api_key = match resource { - Value::Object(mut obj) => obj - .remove("api_key") - .map(|v| serde_json::from_value::(v.clone()).ok()) - .flatten(), - _ => None, - }; - OpenaiApiCompatibleCache { base_url, api_key } - } else { - serde_json::from_value(resource).with_context(|| "validating custom AI resource")? + resource: AIResource, + ) -> Result { + let (api_key, access_token, organization_id, base_url, user) = match resource { + AIResource::Standard(resource) => { + let base_url = provider.get_base_url(resource.base_url, db).await?; + let api_key = if let Some(api_key) = resource.api_key { + Some(get_variable_or_self(api_key, db, w_id).await?) + } else { + None + }; + let organization_id = if let Some(organization_id) = resource.organization_id { + Some(get_variable_or_self(organization_id, db, w_id).await?) + } else { + None + }; + + (api_key, None, organization_id, base_url, None) + } + AIResource::OAuth(resource) => { + let user = if let Some(user) = resource.user.clone() { + Some(get_variable_or_self(user, db, w_id).await?) + } else { + None + }; + let token = Self::get_token_using_oauth(resource, db, w_id).await?; + let base_url = provider.get_base_url(None, db).await?; + + (None, Some(token), None, base_url, user) + } }; - if let Some(api_key) = resource.api_key { - resource.api_key = Some(get_variable_or_self(api_key, db, w_id).await?); - } - - Ok(KeyCache::OpenaiApiCompatible(resource)) - } -} - -mod openai { - use super::*; - - const API_VERSION: &str = "2024-10-21"; - - #[derive(Deserialize, Debug)] - struct OpenaiResource { - api_key: String, - organization_id: Option, + Ok(Self { base_url, organization_id, api_key, access_token, user }) } - #[derive(Deserialize, Debug)] - struct OpenaiClientCredentialsOauthResource { - client_id: String, - client_secret: String, - token_url: String, - user: Option, - } - - #[derive(Deserialize, Debug)] - #[serde(untagged, rename_all = "snake_case")] - enum OpenaiConfig { - Resource(OpenaiResource), - ClientCredentialsOauthResource(OpenaiClientCredentialsOauthResource), - } - - lazy_static::lazy_static! { - pub static ref OPENAI_AZURE_BASE_PATH: Option = std::env::var("OPENAI_AZURE_BASE_PATH").ok(); - } - - #[derive(Deserialize, Debug)] - struct OpenaiCredentials { - access_token: String, - } - - #[derive(Clone, Debug, Deserialize)] - pub struct OpenaiCache { - api_key: String, - organization_id: Option, - azure_base_path: Option, - user: Option, - } - - impl OpenaiCache { - pub fn new( - api_key: String, - organization_id: Option, - azure_base_path: Option, - user: Option, - ) -> Self { - Self { api_key, organization_id, azure_base_path, user } - } - } - - const BASE_URL: &str = "https://api.openai.com/v1"; - impl OpenaiCache { - pub fn prepare_request(self, openai_path: &str, mut body: Bytes) -> Result { - let OpenaiCache { api_key, azure_base_path, organization_id, user } = self; - if user.is_some() { - tracing::debug!("Adding user to request body"); - let mut json_body: HashMap> = serde_json::from_slice(&body) - .map_err(|e| { - Error::internal_err(format!("Failed to parse request body: {}", e)) - })?; - - let user_json_string = serde_json::Value::String(user.unwrap()).to_string(); // makes sure to escape characters - - json_body.insert( - "user".to_string(), - RawValue::from_string(user_json_string) - .map_err(|e| Error::internal_err(format!("Failed to parse user: {}", e)))?, - ); - - body = serde_json::to_vec(&json_body) - .map_err(|e| { - Error::internal_err(format!("Failed to reserialize request body: {}", e)) - })? - .into(); - } - - let base_url = if let Some(base_url) = azure_base_path { - base_url - } else { - BASE_URL.to_string() - }; - let url = format!("{}/{}", base_url, openai_path); - let mut request = HTTP_CLIENT - .post(url) - .header("content-type", "application/json") - .body(body); - - if base_url != BASE_URL { - request = request - .header("api-key", api_key) - .query(&[("api-version", API_VERSION)]) - } else { - request = request.header("authorization", format!("Bearer {}", api_key)) - } - - if let Some(org_id) = organization_id { - request = request.header("OpenAI-Organization", org_id); - } - - Ok(request) - } - } - - async fn get_openai_key_using_credentials_flow( - mut resource: OpenaiClientCredentialsOauthResource, + async fn get_token_using_oauth( + mut resource: AIOAuthResource, db: &DB, w_id: &str, ) -> Result { @@ -197,208 +114,208 @@ mod openai { resource.token_url = get_variable_or_self(resource.token_url, db, w_id).await?; let mut params = HashMap::new(); params.insert("grant_type", "client_credentials"); + params.insert("scope", "https://cognitiveservices.azure.com/.default"); let response = HTTP_CLIENT .post(resource.token_url) .form(¶ms) .basic_auth(resource.client_id, Some(resource.client_secret)) .send() .await + .and_then(|r| r.error_for_status()) .map_err(|err| { Error::internal_err(format!( - "Failed to get OpenAI credentials using credentials flow: {}", + "Failed to get access token using credentials flow: {}", err )) })?; - let response = response.json::().await.map_err(|err| { + let response = response.json::().await.map_err(|err| { Error::internal_err(format!( - "Failed to parse OpenAI credentials from credentials flow: {}", + "Failed to parse access token from credentials flow: {}", err )) })?; Ok(response.access_token) } - pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result { - let config = serde_json::from_value(resource) - .map_err(|e| Error::internal_err(format!("validating openai resource {e:#}")))?; - - let mut user = None::; - let mut resource = match config { - OpenaiConfig::Resource(resource) => { - tracing::debug!("Getting OpenAI key from static resource"); - resource - } - OpenaiConfig::ClientCredentialsOauthResource(resource) => { - tracing::debug!("Getting OpenAI key with client credentials flow"); - user = resource.user.clone(); - let token = get_openai_key_using_credentials_flow(resource, db, w_id).await?; - OpenaiResource { api_key: token, organization_id: None } - } - }; - - resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?; - - if let Some(organization_id) = resource.organization_id { - resource.organization_id = Some(get_variable_or_self(organization_id, db, w_id).await?); - } - - if user.is_some() { - user = Some(get_variable_or_self(user.unwrap(), db, w_id).await?); - } - - let azure_base_path = sqlx::query_scalar!( - "SELECT value - FROM global_settings - WHERE name = 'openai_azure_base_path'", - ) - .fetch_optional(db) - .await?; - - let azure_base_path = if let Some(azure_base_path) = azure_base_path { - Some( - serde_json::from_value::(azure_base_path).map_err(|e| { - Error::internal_err(format!("validating openai azure base path {e:#}")) - })?, - ) + pub fn prepare_request( + self, + provider: &AIProvider, + path: &str, + method: Method, + headers: HeaderMap, + body: Bytes, + ) -> Result { + let body = if let Some(user) = self.user { + Self::add_user_to_body(body, user)? } else { - OPENAI_AZURE_BASE_PATH.clone() + body }; - let workspace_cache = OpenaiCache::new( - resource.api_key.clone(), - resource.organization_id.clone(), - azure_base_path.clone(), - user.clone(), + let base_url = self.base_url.trim_end_matches('/'); + + let is_azure = matches!(provider, AIProvider::OpenAI) && base_url != OPENAI_BASE_URL + || matches!(provider, AIProvider::AzureOpenAI); + let is_anthropic = matches!(provider, AIProvider::Anthropic); + + let url = if is_azure && method != Method::GET { + if base_url.ends_with("/deployments") { + let model = Self::get_azure_model(&body)?; + format!("{}/{}/{}", base_url, model, path) + } else if base_url.ends_with("/openai") { + let model = Self::get_azure_model(&body)?; + format!("{}/deployments/{}/{}", base_url, model, path) + } else { + format!("{}/{}", base_url, path) + } + } else { + format!("{}/{}", base_url, path) + }; + + tracing::debug!("AI request URL: {}", url); + + let mut request = HTTP_CLIENT + .request(method, url) + .header("content-type", "application/json"); + + for (header_name, header_value) in headers.iter() { + if header_name.to_string().starts_with("anthropic-") { + request = request.header(header_name, header_value); + } + } + + request = request.body(body); + + if is_azure { + request = request.query(&[("api-version", AZURE_API_VERSION)]) + } + + if let Some(api_key) = self.api_key { + if is_azure { + request = request.header("api-key", api_key.clone()) + } else { + request = request.header("authorization", format!("Bearer {}", api_key.clone())) + } + if is_anthropic { + request = request.header("X-API-Key", api_key); + } + } + + if let Some(access_token) = self.access_token { + request = request.header("authorization", format!("Bearer {}", access_token)) + } + + if let Some(org_id) = self.organization_id { + request = request.header("OpenAI-Organization", org_id); + } + + Ok(request) + } + + fn add_user_to_body(body: Bytes, user: String) -> Result { + tracing::debug!("Adding user to request body"); + let mut json_body: HashMap> = serde_json::from_slice(&body) + .map_err(|e| Error::internal_err(format!("Failed to parse request body: {}", e)))?; + + let user_json_string = serde_json::Value::String(user).to_string(); // makes sure to escape characters + + json_body.insert( + "user".to_string(), + RawValue::from_string(user_json_string) + .map_err(|e| Error::internal_err(format!("Failed to parse user: {}", e)))?, ); - Ok(KeyCache::Openai(workspace_cache)) - } -} -mod anthropic { - - use super::*; - - #[derive(Clone, Deserialize, Debug)] - pub struct AnthropicCache { - #[serde(rename = "apiKey")] - pub api_key: String, + Ok(serde_json::to_vec(&json_body) + .map_err(|e| Error::internal_err(format!("Failed to reserialize request body: {}", e)))? + .into()) } - const API_VERSION: &str = "2023-06-01"; - - const BASE_URL: &str = "https://api.anthropic.com"; - impl AnthropicCache { - pub fn prepare_request(self, anthropic_path: &str, body: Bytes) -> Result { - let AnthropicCache { api_key } = self; - let url = format!("{}/{}", BASE_URL, anthropic_path); - let request = HTTP_CLIENT - .post(url) - .header("x-api-key", api_key) - .header("anthropic-version", API_VERSION) - .header("content-type", "application/json") - .body(body); - Ok(request) + fn get_azure_model(body: &Bytes) -> Result { + #[derive(Deserialize, Debug)] + struct AzureModel { + model: String, } - } - pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result { - let mut resource: AnthropicCache = serde_json::from_value(resource) - .map_err(|e| Error::internal_err(format!("validating anthropic resource {e:#}")))?; - resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?; - Ok(KeyCache::Anthropic(resource)) - } -} + let azure_model: AzureModel = serde_json::from_slice(body) + .map_err(|e| Error::internal_err(format!("Failed to parse request body: {}", e)))?; -mod mistral { - use super::*; - #[derive(Deserialize, Clone, Debug)] - pub struct MistralCache { - #[serde(rename = "apiKey")] - pub api_key: String, - } - - const BASE_URL: &str = "https://api.mistral.ai"; - impl MistralCache { - pub fn prepare_request(self, mistral_path: &str, body: Bytes) -> Result { - let MistralCache { api_key } = self; - - let url = format!("{}/{}", BASE_URL, mistral_path); - let request = HTTP_CLIENT - .post(url) - .header("content-type", "application/json") - .header("Accept", "application/json") - .header("authorization", format!("Bearer {}", api_key)) - .body(body); - Ok(request) - } - } - - pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result { - let mut resource: MistralCache = serde_json::from_value(resource) - .map_err(|e| Error::internal_err(format!("validating mistral resource {e:#}")))?; - resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?; - Ok(KeyCache::Mistral(resource)) + Ok(azure_model.model) } } #[derive(Clone, Debug)] -pub enum KeyCache { - Openai(OpenaiCache), - Anthropic(AnthropicCache), - Mistral(MistralCache), - OpenaiApiCompatible(OpenaiApiCompatibleCache), +pub struct ExpiringAIRequestConfig { + config: AIRequestConfig, + expires_at: std::time::Instant, } -#[derive(Clone, Debug)] -pub struct AICache { - pub path: String, - pub cached_key: KeyCache, - pub expires_at: std::time::Instant, -} - -impl AICache { - pub fn new(path: String, cached_key: KeyCache) -> Self { - Self { - path, - cached_key, - expires_at: std::time::Instant::now() + std::time::Duration::from_secs(60), - } +impl ExpiringAIRequestConfig { + fn new(config: AIRequestConfig) -> Self { + Self { config, expires_at: std::time::Instant::now() + std::time::Duration::from_secs(60) } } fn is_expired(&self) -> bool { self.expires_at < std::time::Instant::now() } } -lazy_static! { - pub static ref AI_KEY_CACHE: Cache = Cache::new(500); -} - -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)] #[serde(rename_all = "lowercase")] pub enum AIProvider { OpenAI, + #[serde(rename = "azure_openai")] + AzureOpenAI, Anthropic, Mistral, DeepSeek, GoogleAI, Groq, OpenRouter, + TogetherAI, CustomAI, } impl AIProvider { - pub fn get_openai_compatible_base_url(&self) -> Result> { + pub async fn get_base_url(&self, resource_base_url: Option, db: &DB) -> Result { match self { - AIProvider::DeepSeek => Ok(Some("https://api.deepseek.com/v1".to_string())), - AIProvider::GoogleAI => Ok(Some( - "https://generativelanguage.googleapis.com/v1beta/openai".to_string(), - )), - AIProvider::Groq => Ok(Some("https://api.groq.com/openai/v1".to_string())), - AIProvider::OpenRouter => Ok(Some("https://openrouter.ai/api/v1".to_string())), - AIProvider::CustomAI => Ok(None), - _ => Err(Error::BadRequest( - "Please use the specific provider instead of the OpenAI compatible one".to_string(), - )), + AIProvider::OpenAI => { + let azure_base_path = sqlx::query_scalar!( + "SELECT value + FROM global_settings + WHERE name = 'openai_azure_base_path'", + ) + .fetch_optional(db) + .await?; + + let azure_base_path = if let Some(azure_base_path) = azure_base_path { + Some( + serde_json::from_value::(azure_base_path).map_err(|e| { + Error::internal_err(format!("validating openai azure base path {e:#}")) + })?, + ) + } else { + OPENAI_AZURE_BASE_PATH.clone() + }; + + Ok(azure_base_path.unwrap_or(OPENAI_BASE_URL.to_string())) + } + AIProvider::DeepSeek => Ok("https://api.deepseek.com/v1".to_string()), + AIProvider::GoogleAI => { + Ok("https://generativelanguage.googleapis.com/v1beta/openai".to_string()) + } + AIProvider::Groq => Ok("https://api.groq.com/openai/v1".to_string()), + AIProvider::OpenRouter => Ok("https://openrouter.ai/api/v1".to_string()), + AIProvider::TogetherAI => Ok("https://api.together.xyz/v1".to_string()), + AIProvider::Anthropic => Ok("https://api.anthropic.com/v1".to_string()), + AIProvider::Mistral => Ok("https://api.mistral.ai/v1".to_string()), + p @ (AIProvider::CustomAI | AIProvider::AzureOpenAI) => { + if let Some(base_url) = resource_base_url { + Ok(base_url) + } else { + Err(Error::BadRequest(format!( + "{:?} provider requires a base URL in the resource", + p + ))) + } + } } } } @@ -406,144 +323,193 @@ impl AIProvider { impl TryFrom<&str> for AIProvider { type Error = Error; fn try_from(s: &str) -> Result { - match s { - "openai" => Ok(AIProvider::OpenAI), - "anthropic" => Ok(AIProvider::Anthropic), - "mistral" => Ok(AIProvider::Mistral), - "groq" => Ok(AIProvider::Groq), - "openrouter" => Ok(AIProvider::OpenRouter), - "deepseek" => Ok(AIProvider::DeepSeek), - "googleai" => Ok(AIProvider::GoogleAI), - "customai" => Ok(AIProvider::CustomAI), - _ => Err(Error::BadRequest(format!("Invalid AI provider: {}", s))), - } + let s = serde_json::from_value::(serde_json::Value::String(s.to_string())) + .map_err(|e| Error::BadRequest(format!("Invalid AI provider: {}", e)))?; + Ok(s) } } -#[derive(Deserialize, Debug)] -pub struct AIResource { - pub path: Option, +#[derive(Serialize, Deserialize, Debug)] +pub struct ProviderConfig { + pub resource_path: String, + pub models: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ProviderModel { + pub model: String, pub provider: AIProvider, } -pub fn workspaced_service() -> Router { - let router = Router::new().route("/proxy/*ai", post(proxy)); +#[derive(Serialize, Deserialize, Debug)] +pub struct AIConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub providers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_completion_model: Option, +} - router +pub fn global_service() -> Router { + Router::new().route("/proxy/*ai", post(global_proxy).get(global_proxy)) +} + +pub fn workspaced_service() -> Router { + Router::new().route("/proxy/*ai", post(proxy).get(proxy)) +} + +async fn global_proxy( + authed: ApiAuthed, + Extension(db): Extension, + Path(ai_path): Path, + method: Method, + headers: HeaderMap, + body: Bytes, +) -> impl IntoResponse { + let provider = headers + .get("X-Provider") + .map(|v| v.to_str().unwrap_or("").to_string()); + let api_key = headers + .get("X-API-Key") + .map(|v| v.to_str().unwrap_or("").to_string()); + + let provider = match provider { + Some(provider) => AIProvider::try_from(provider.as_str())?, + None => return Err(Error::BadRequest("Provider is required".to_string())), + }; + + let Some(api_key) = api_key else { + return Err(Error::BadRequest("API key is required".to_string())); + }; + + let base_url = provider.get_base_url(None, &db).await?; + + let url = format!("{}/{}", base_url, ai_path); + + let request = HTTP_CLIENT + .request(method, url) + .header("content-type", "application/json") + .header("Authorization", format!("Bearer {}", api_key)) + .body(body); + + let response = request.send().await.map_err(to_anyhow)?; + + let mut tx = db.begin().await?; + + audit_log( + &mut *tx, + &authed, + "ai.global_request", + ActionKind::Execute, + "global", + Some(&authed.email), + None, + ) + .await?; + tx.commit().await?; + + if response.error_for_status_ref().is_err() { + let err_msg = response.text().await.unwrap_or("".to_string()); + return Err(Error::AIError(err_msg)); + } + + let status_code = response.status(); + let headers = response.headers().clone(); + let stream = response.bytes_stream(); + Ok((status_code, headers, axum::body::Body::from_stream(stream))) } async fn proxy( authed: ApiAuthed, Extension(db): Extension, Path((w_id, ai_path)): Path<(String, String)>, + method: Method, headers: HeaderMap, body: Bytes, ) -> impl IntoResponse { - let workspace_cache = AI_KEY_CACHE.get(&w_id); + let provider = headers + .get("X-Provider") + .map(|v| v.to_str().unwrap_or("").to_string()); + + let provider = match provider { + Some(provider) => AIProvider::try_from(provider.as_str())?, + None => return Err(Error::BadRequest("Provider is required".to_string())), + }; + + let workspace_cache = AI_REQUEST_CACHE.get(&(w_id.clone(), provider.clone())); + let forced_resource_path = headers .get("X-Resource-Path") .map(|v| v.to_str().unwrap_or("").to_string()); - let ai_cache = match workspace_cache { - Some(cache) if !cache.is_expired() && forced_resource_path.is_none() => cache.cached_key, + let request_config = match workspace_cache { + Some(request_cache) if !request_cache.is_expired() && forced_resource_path.is_none() => { + request_cache.config + } _ => { - let (resource, resource_path, ai_provider) = if let Some(resource_path) = - forced_resource_path - { - // guess the provider from the resource type - let record = sqlx::query!( - "SELECT value, resource_type FROM resource WHERE path = $1 AND workspace_id = $2", - &resource_path, - &w_id - ) - .fetch_optional(&db) - .await? - .ok_or_else(|| { - Error::NotFound(format!( - "Could not find the resource {}, update the resource path in the workspace settings", resource_path - )) - })?; - - ( - record.value, - resource_path, - AIProvider::try_from(record.resource_type.as_str())?, - ) + let (resource_path, save_to_cache) = if let Some(resource_path) = forced_resource_path { + // forced resource path + (resource_path, false) } else { - let ai_resource = sqlx::query_scalar!( - "SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1", + let ai_config = sqlx::query_scalar!( + "SELECT ai_config FROM workspace_settings WHERE workspace_id = $1", &w_id ) .fetch_one(&db) .await?; - if ai_resource.is_none() { + if ai_config.is_none() { return Err(Error::internal_err( "AI resource not configured".to_string(), )); } - let ai_resource = serde_json::from_value::(ai_resource.unwrap()) + let mut ai_config = serde_json::from_value::(ai_config.unwrap()) .map_err(|e| Error::BadRequest(e.to_string()))?; - let path = ai_resource.path.unwrap_or("".to_string()); - if path.is_empty() { + let provider_config = ai_config + .providers + .as_mut() + .map(|providers| providers.remove(&provider)) + .flatten() + .ok_or_else(|| { + Error::BadRequest(format!("Provider {:?} not configured", provider)) + })?; + + if provider_config.resource_path.is_empty() { return Err(Error::BadRequest("Resource path is empty".to_string())); } - let resource = sqlx::query_scalar!( - "SELECT value - FROM resource - WHERE path = $1 AND workspace_id = $2", - &path, - &w_id - ) - .fetch_optional(&db) - .await? - .ok_or_else(|| { - Error::NotFound(format!( - "Could not find the {:?} resource at path {}, update the resource path in the workspace settings", ai_resource.provider, path - )) - })?; - (resource, path, ai_resource.provider) + (provider_config.resource_path, true) }; - if resource.is_none() { - return Err(Error::internal_err(format!( - "{:?} resource missing value", - ai_provider - ))); + let resource= sqlx::query_scalar!( + "SELECT value as \"value: sqlx::types::Json>\" FROM resource WHERE path = $1 AND workspace_id = $2", + &resource_path, + &w_id + ) + .fetch_optional(&db) + .await? + .ok_or_else(|| Error::NotFound(format!("Could not find the resource {}, update the resource path in the workspace settings", resource_path)))? + .ok_or_else(|| Error::BadRequest(format!("Empty resource value for {}", resource_path)))?; + + let resource = serde_json::from_str::(resource.0.get()) + .map_err(|e| Error::BadRequest(e.to_string()))?; + + let request_config = AIRequestConfig::new(&provider, &db, &w_id, resource).await?; + if save_to_cache { + AI_REQUEST_CACHE.insert( + (w_id.clone(), provider.clone()), + ExpiringAIRequestConfig::new(request_config.clone()), + ); } - - let resource = resource.unwrap(); - - let ai_cache = match ai_provider { - AIProvider::OpenAI => openai::get_cached_value(&db, &w_id, resource).await, - AIProvider::Anthropic => anthropic::get_cached_value(&db, &w_id, resource).await, - AIProvider::Mistral => mistral::get_cached_value(&db, &w_id, resource).await, - _ => { - openai_api_compatible::get_cached_value( - &db, - &w_id, - resource, - ai_provider.get_openai_compatible_base_url()?, - ) - .await - } - }; - let ai_cache = ai_cache?; - AI_KEY_CACHE.insert(w_id.clone(), AICache::new(resource_path, ai_cache.clone())); - ai_cache + request_config } }; - let request = match ai_cache { - KeyCache::Openai(cached) => cached.prepare_request(&ai_path, body), - KeyCache::Anthropic(cached) => cached.prepare_request(&ai_path, body), - KeyCache::Mistral(cached) => cached.prepare_request(&ai_path, body), - KeyCache::OpenaiApiCompatible(cached) => cached.prepare_request(&ai_path, body), - }; + let request = request_config.prepare_request(&provider, &ai_path, method, headers, body)?; - let response = request?.send().await.map_err(to_anyhow)?; + let response = request.send().await.map_err(to_anyhow)?; let mut tx = db.begin().await?; @@ -554,14 +520,14 @@ async fn proxy( ActionKind::Execute, &w_id, Some(&authed.email), - Some([("ai_resource_path", &format!("{:?}", ai_path)[..])].into()), + Some([("ai_config_path", &format!("{:?}", ai_path)[..])].into()), ) .await?; tx.commit().await?; if response.error_for_status_ref().is_err() { let err_msg = response.text().await.unwrap_or("".to_string()); - return Err(Error::AiError(err_msg)); + return Err(Error::AIError(err_msg)); } let status_code = response.status(); diff --git a/backend/windmill-api/src/approvals.rs b/backend/windmill-api/src/approvals.rs new file mode 100644 index 0000000000..f4ee9dd843 --- /dev/null +++ b/backend/windmill-api/src/approvals.rs @@ -0,0 +1,292 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use uuid::Uuid; +use std::str::FromStr; +use regex::Regex; +use serde_json::Value; +use crate::db::{ApiAuthed, DB}; +use crate::jobs::{cancel_suspended_job, resume_suspended_job, QueryApprover, QueryOrBody, ResumeUrls, get_resume_urls_internal}; +use axum::{extract::{Path, Query}, Extension}; +use windmill_common::error::Error; +use windmill_common::cache; +use windmill_common::jobs::JobKind; +use windmill_common::scripts::ScriptHash; +use serde_json::value::RawValue; + +#[derive(Debug, Deserialize, Serialize)] +pub struct ResumeSchema { + pub schema: Schema, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Schema { + pub order: Vec, + pub required: Vec, + pub properties: HashMap, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "lowercase")] +pub enum FieldType { + Boolean, + String, + Number, + Integer, + #[serde(other)] + Unknown, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ResumeFormField { + pub r#type: FieldType, + pub format: Option, + pub default: Option, + pub description: Option, + pub title: Option, + pub r#enum: Option>, + #[serde(rename = "enumLabels")] + pub enum_labels: Option>, + pub nullable: Option, + pub placeholder: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ResumeFormRow { + pub resume_form: Option, + pub hide_cancel: Option, +} + +#[derive(Deserialize)] +pub struct QueryMessage { + pub message: Option, +} + +#[derive(Deserialize)] +pub struct QueryFlowStepId { + pub flow_step_id: String, +} + +#[derive(Deserialize, Debug)] +pub struct QueryDefaultArgsJson { + pub default_args_json: Option, +} + +#[derive(Deserialize, Debug)] +pub struct QueryDynamicEnumJson { + pub dynamic_enums_json: Option, +} + +#[derive(Debug)] +pub struct ApprovalFormDetails { + pub message_str: String, + pub urls: ResumeUrls, + pub schema: Option, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +pub enum MessageFormat { + Slack, + Teams, +} + +pub fn extract_w_id_from_resume_url(resume_url: &str) -> Result<&str, Error> { + let re = Regex::new(r"/api/w/(?P[^/]+)/jobs_u/(?Presume|cancel)/(?P[^/]+)/(?P[^/]+)/(?P[a-fA-F0-9]+)(?:\?approver=(?P[^&]+))?").unwrap(); + let captures = re.captures(resume_url).ok_or_else(|| { + tracing::error!("Resume URL does not match the pattern."); + Error::BadRequest("Invalid URL format.".to_string()) + })?; + Ok(captures.name("w_id").map_or("", |m| m.as_str())) +} + +pub async fn handle_resume_action( + authed: Option, + db: DB, + resume_url: &str, + form_data: Value, + action: &str, +) -> Result<(), Error> { + // Extract information from resume_url using regex + let re = Regex::new(r"/api/w/(?P[^/]+)/jobs_u/(?Presume|cancel)/(?P[^/]+)/(?P[^/]+)/(?P[a-fA-F0-9]+)(?:\?approver=(?P[^&]+))?").unwrap(); + let captures = re.captures(resume_url).ok_or_else(|| { + tracing::error!("Resume URL does not match the pattern."); + Error::BadRequest("Invalid URL format.".to_string()) + })?; + + let (w_id, job_id, resume_id, secret, approver) = ( + captures.name("w_id").map_or("", |m| m.as_str()), + captures.name("job_id").map_or("", |m| m.as_str()), + captures.name("resume_id").map_or("", |m| m.as_str()), + captures.name("secret").map_or("", |m| m.as_str()), + captures.name("approver").map(|m| m.as_str().to_string()), + ); + + let approver = QueryApprover { approver }; + + // Convert job_id and resume_id to appropriate types + let job_uuid = Uuid::from_str(job_id) + .map_err(|_| Error::BadRequest("Invalid job ID format.".to_string()))?; + + let resume_id_parsed = resume_id + .parse::() + .map_err(|_| Error::BadRequest("Invalid resume ID format.".to_string()))?; + + // Call the appropriate function based on the action + let res = if action == "resume" { + resume_suspended_job( + authed, + Extension(db.clone()), + Path(( + w_id.to_string(), + job_uuid, + resume_id_parsed, + secret.to_string(), + )), + Query(approver), + QueryOrBody(Some(form_data)), + ) + .await + } else { + cancel_suspended_job( + authed, + Extension(db.clone()), + Path(( + w_id.to_string(), + job_uuid, + resume_id_parsed, + secret.to_string(), + )), + Query(approver), + QueryOrBody(Some(form_data)), + ) + .await + }; + + tracing::debug!("Job action result: {:#?}", res); + res?; + + Ok(()) +} + +pub async fn get_approval_form_details( + db: DB, + w_id: &str, + job_id: Uuid, + flow_step_id: Option<&str>, + resume_id: u32, + approver: Option<&str>, + message: Option<&str>, + format: MessageFormat, +) -> Result { + let res = get_resume_urls_internal( + axum::Extension(db.clone()), + Path((w_id.to_string(), job_id, resume_id)), + Query(QueryApprover { approver: approver.map(|a| a.to_string()) }), + ) + .await?; + + let urls = res.0; + + tracing::debug!("Job ID: {:?}", job_id); + + // TODO: do we have a helper function for this? + let (job_kind, script_hash, raw_flow, parent_job_id, created_at, created_by, script_path, args) = sqlx::query!( + "WITH job_info AS ( + -- Query for Teams (running jobs) + SELECT + parent.job_kind AS \"job_kind!: JobKind\", + parent.script_hash AS \"script_hash: ScriptHash\", + parent.raw_flow AS \"raw_flow: sqlx::types::Json>\", + child.parent_job AS \"parent_job: Uuid\", + parent.created_at AS \"created_at!: chrono::NaiveDateTime\", + parent.created_by AS \"created_by!\", + parent.script_path, + parent.args AS \"args: sqlx::types::Json>\" + FROM v2_as_queue child + JOIN v2_as_queue parent ON parent.id = child.parent_job + WHERE child.id = $1 AND child.workspace_id = $2 + UNION ALL + -- Query for Slack (completed jobs) + SELECT + v2_as_queue.job_kind AS \"job_kind!: JobKind\", + v2_as_queue.script_hash AS \"script_hash: ScriptHash\", + v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json>\", + v2_as_completed_job.parent_job AS \"parent_job: Uuid\", + v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\", + v2_as_completed_job.created_by AS \"created_by!\", + v2_as_queue.script_path, + v2_as_queue.args AS \"args: sqlx::types::Json>\" + FROM v2_as_queue + JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id + WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2 + ) + SELECT * FROM job_info LIMIT 1", + job_id, + &w_id + ) + .fetch_optional(&db) + .await + .map_err(|e| Error::BadRequest(e.to_string()))? + .ok_or_else(|| Error::BadRequest("This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string())) + .map(|r| (r.job_kind, r.script_hash, r.raw_flow, r.parent_job, r.created_at, r.created_by, r.script_path, r.args))?; + + let flow_data = match cache::job::fetch_flow(&db, job_kind, script_hash).await { + Ok(data) => data, + Err(_) => { + if let Some(parent_job_id) = parent_job_id.as_ref() { + cache::job::fetch_preview_flow(&db, parent_job_id, raw_flow).await? + } else { + return Err(Error::BadRequest( + "This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string(), + )); + } + } + }; + + let flow_value = &flow_data.flow; + let flow_step_id = flow_step_id.unwrap_or(""); + let module = flow_value.modules.iter().find(|m| m.id == flow_step_id); + + tracing::debug!("Module: {:#?}", module); + + let schema = module.and_then(|module| { + module.suspend.as_ref().map(|suspend| ResumeFormRow { + resume_form: suspend.resume_form.clone(), + hide_cancel: suspend.hide_cancel, + }) + }); + + let args_str = args.map_or("None".to_string(), |a| a.get().to_string()); + let parent_job_id_str = parent_job_id.map_or("None".to_string(), |id| id.to_string()); + let script_path_str = script_path.as_deref().unwrap_or("None"); + + let created_at_formatted = created_at.format("%Y-%m-%d %H:%M:%S").to_string(); + + let bold_format = match format { + MessageFormat::Slack => "*{}*", + MessageFormat::Teams => "**{}**", + }; + + let mut message_str = format!( + "A workflow has been suspended and is waiting for approval:\n\n\ + {}: {created_by}\n\n\ + {}: {created_at_formatted}\n\n\ + {}: {script_path_str}\n\n\ + {}: {args_str}\n\n\ + {}: {parent_job_id_str}\n\n", + bold_format.replace("{}", "Created by"), + bold_format.replace("{}", "Created at"), + bold_format.replace("{}", "Script path"), + bold_format.replace("{}", "Args"), + bold_format.replace("{}", "Flow ID") + ); + + // Append custom message if provided + if let Some(msg) = message { + message_str.push_str(msg); + } + + tracing::debug!("Schema: {:#?}", schema); + + Ok(ApprovalFormDetails { message_str, urls, schema }) +} diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 0a8878fad6..85d1745caa 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -18,17 +18,16 @@ use crate::{ }; #[cfg(feature = "parquet")] use crate::{ - job_helpers_ee::{ + job_helpers_oss::{ download_s3_file_internal, get_random_file_name, get_s3_resource, - get_workspace_s3_resource, load_image_preview_internal, upload_file_from_req, - DownloadFileQuery, LoadImagePreviewQuery, + get_workspace_s3_resource, upload_file_from_req, DownloadFileQuery, }, users::fetch_api_authed_from_permissioned_as, }; -#[cfg(feature = "parquet")] use axum::response::Response; use axum::{ - extract::{Extension, Json, Path, Query}, + body::Body, + extract::{Extension, Json, Multipart, Path, Query}, response::IntoResponse, routing::{delete, get, post}, Router, @@ -49,9 +48,8 @@ use sha2::{Digest, Sha256}; use sql_builder::{bind::Bind, SqlBuilder}; use sqlx::{types::Uuid, FromRow}; use std::str; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; -use windmill_common::variables::encrypt; use windmill_common::{ apps::{AppScriptId, ListAppQuery}, cache::{self, future::FutureCachedExt}, @@ -61,18 +59,26 @@ use windmill_common::{ users::username_to_permissioned_as, utils::{ http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, require_admin, - Pagination, StripPath, + Pagination, RunnableKind, StripPath, }, - variables::{build_crypt, build_crypt_with_key_suffix}, + variables::{build_crypt, build_crypt_with_key_suffix, encrypt}, worker::{to_raw_value, CLOUD_HOSTED}, HUB_BASE_URL, }; -#[cfg(feature = "parquet")] -use windmill_common::{jwt, s3_helpers::build_object_store_client}; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; use windmill_queue::{push, PushArgs, PushArgsOwned, PushIsolationLevel}; +#[cfg(feature = "parquet")] +use hmac::Mac; +#[cfg(feature = "parquet")] +use windmill_common::{ + jwt, + oauth2::HmacSha256, + s3_helpers::{build_object_store_client, S3Object}, + variables::get_workspace_key, +}; + pub fn workspaced_service() -> Router { Router::new() .route("/list", get(list_apps)) @@ -82,14 +88,22 @@ pub fn workspaced_service() -> Router { .route("/get/draft/*path", get(get_app_w_draft)) .route("/secret_of/*path", get(get_secret_id)) .route("/get/v/*id", get(get_app_by_id)) + .route("/get_data/v/*id", get(get_raw_app_data)) .route("/exists/*path", get(exists_app)) .route("/update/*path", post(update_app)) + .route("/update_raw/*path", post(update_app_raw)) .route("/delete/*path", delete(delete_app)) .route("/create", post(create_app)) + .route("/create_raw", post(create_app_raw)) .route("/history/p/*path", get(get_app_history)) .route("/get_latest_version/*path", get(get_latest_version)) .route("/history_update/a/:id/v/:version", post(update_app_history)) + .route( + "/list_paths_from_workspace_runnable/:runnable_kind/*path", + get(list_paths_from_workspace_runnable), + ) .route("/custom_path_exists/*custom_path", get(custom_path_exists)) + .route("/sign_s3_objects", post(sign_s3_objects)) } pub fn unauthed_service() -> Router { @@ -98,10 +112,6 @@ pub fn unauthed_service() -> Router { .route("/upload_s3_file/*path", post(upload_s3_file_from_app)) .route("/delete_s3_file", delete(delete_s3_file_from_app)) .route("/download_s3_file/*path", get(download_s3_file_from_app)) - .route( - "/load_image_preview/*path", - get(load_s3_file_image_preview_from_app), - ) .route("/public_app/:secret", get(get_public_app_by_secret)) .route("/public_resource/*path", get(get_public_resource)) } @@ -128,6 +138,12 @@ pub struct ListableApp { #[sqlx(default)] #[serde(skip_serializing_if = "Option::is_none")] pub deployment_msg: Option, + #[serde(skip_serializing_if = "is_false")] + pub raw_app: bool, +} + +fn is_false(b: &bool) -> bool { + !b } #[derive(FromRow, Serialize, Deserialize)] @@ -228,7 +244,8 @@ pub struct S3Input { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct S3Key { s3_path: String, - resource: String, + #[serde(skip_serializing_if = "Option::is_none")] + storage: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] @@ -320,7 +337,8 @@ async fn list_apps( "app.extra_perms", "favorite.path IS NOT NULL as starred", "draft.path IS NOT NULL as has_draft", - "draft_only" + "draft_only", + "app_version.raw_app", ]) .left() .join("favorite") @@ -379,6 +397,44 @@ async fn list_apps( Ok(Json(rows)) } +async fn get_raw_app_data(Path((w_id, version_id)): Path<(String, String)>) -> Result { + let file_path = format!("/tmp/wmill/{}/{}", w_id, version_id); + let file = tokio::fs::File::open(file_path).await?; + let stream = tokio_util::io::ReaderStream::new(file); + let res = Response::builder().header( + http::header::CONTENT_TYPE, + if version_id.ends_with(".css") { + "text/css" + } else { + "text/javascript" + }, + ); + Ok(res.body(Body::from_stream(stream)).unwrap()) +} + +// async fn get_app_version( +// authed: ApiAuthed, +// Extension(user_db): Extension, +// Path((w_id, path)): Path<(String, StripPath)>, +// ) -> JsonResult { +// let path = path.to_path(); +// let mut tx = user_db.begin(&authed).await?; + +// let version_o = sqlx::query_scalar!( +// "SELECT app.versions[array_upper(app.versions, 1)] as version FROM app +// WHERE app.path = $1 AND app.workspace_id = $2", +// path, +// &w_id, +// ) +// .fetch_optional(&mut *tx) +// .await? +// .flatten(); +// tx.commit().await?; + +// let version = not_found_if_none(version_o, "App", path)?; +// Ok(Json(version)) +// } + async fn get_app( authed: ApiAuthed, Extension(user_db): Extension, @@ -719,23 +775,172 @@ async fn get_secret_id( Ok(hx) } +macro_rules! process_app_multipart { + ($authed:expr, $user_db:expr, $db:expr, $w_id:expr, $path:expr, $multipart:expr, $internal_fn:expr) => { + async { + let mut saved_app = None; + let mut uploaded_js = false; + + //todo: use s3 instead + let file_path = format!("/tmp/wmill/{}", $w_id); + std::fs::create_dir_all(&file_path).unwrap(); + + let mut multipart = $multipart; + while let Some(field) = multipart.next_field().await.unwrap() { + let name = field.name().unwrap().to_string(); + let data = field.bytes().await.unwrap(); + if name == "app" { + let app = serde_json::from_slice(&data).map_err(to_anyhow)?; + let (ntx, npath, nid) = $internal_fn( + $authed.clone(), + $db.clone(), + $user_db.clone(), + $w_id, + $path, + true, + app, + ) + .await?; + saved_app = Some((npath, nid, ntx)); + } else if name == "js" { + if let Some((_npath, id, _tx)) = saved_app.as_ref() { + let file_path = format!("{}/{}.js", file_path, id); + std::fs::write(file_path, data).unwrap(); + uploaded_js = true; + } else { + return Err(Error::BadRequest( + "App payload need to be created first".to_string(), + )); + } + } else if name == "css" { + if let Some((_npath, id, _tx)) = saved_app.as_ref() { + let file_path = format!("{}/{}.css", file_path, id); + std::fs::write(file_path, data).unwrap(); + } else { + return Err(Error::BadRequest( + "App payload need to be created first".to_string(), + )); + } + } else { + return Err(Error::BadRequest(format!("Unsupported field: {}", name))); + } + } + if !uploaded_js { + return Err(Error::BadRequest("js or css file not uploaded".to_string())); + } + if let Some((npath, id, tx)) = saved_app { + tx.commit().await?; + Ok((npath, id)) + } else { + Err(Error::BadRequest("App not created".to_string())) + } + } + }; +} + +async fn create_app_raw<'a>( + authed: ApiAuthed, + Extension(user_db): Extension, + Extension(db): Extension, + Extension(webhook): Extension, + Path(w_id): Path, + multipart: Multipart, +) -> Result<(StatusCode, String)> { + let (path, _id) = process_app_multipart!( + authed, + user_db, + db, + &w_id, + "", + multipart, + |authed, db, user_db, w_id, _path, raw_app, app| create_app_internal( + authed, db, user_db, w_id, raw_app, app + ) + ) + .await?; + + webhook.send_message( + w_id.clone(), + WebhookMessage::CreateApp { workspace: w_id, path: path.clone() }, + ); + Ok((StatusCode::CREATED, path)) +} + +async fn list_paths_from_workspace_runnable( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, StripPath)>, +) -> JsonResult> { + let mut tx = user_db.begin(&authed).await?; + let runnables = sqlx::query_scalar!( + r#"SELECT a.path + FROM workspace_runnable_dependencies wru + JOIN app a + ON wru.app_path = a.path AND wru.workspace_id = a.workspace_id + WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3"#, + path.to_path(), + matches!(runnable_kind, RunnableKind::Flow), + w_id + ) + .fetch_all(&mut *tx) + .await?; + tx.commit().await?; + Ok(Json(runnables)) +} + async fn create_app( authed: ApiAuthed, Extension(user_db): Extension, Extension(db): Extension, Extension(webhook): Extension, Path(w_id): Path, - Json(mut app): Json, + Json(app): Json, ) -> Result<(StatusCode, String)> { - let mut tx = user_db.clone().begin(&authed).await?; + let path = app.path.clone(); + let (new_tx, _path, _id) = create_app_internal(authed, db, user_db, &w_id, false, app).await?; + new_tx.commit().await?; + + webhook.send_message( + w_id.clone(), + WebhookMessage::CreateApp { workspace: w_id, path: path.clone() }, + ); + + Ok((StatusCode::CREATED, path)) +} + +async fn create_app_internal<'a>( + authed: ApiAuthed, + db: sqlx::Pool, + user_db: UserDB, + w_id: &String, + raw_app: bool, + mut app: CreateApp, +) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> { + if *CLOUD_HOSTED { + let nb_apps = + sqlx::query_scalar!("SELECT COUNT(*) FROM app WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_apps.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if app.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + } + let mut tx = user_db.clone().begin(&authed).await?; app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username)); app.policy.on_behalf_of_email = Some(authed.email.clone()); - + let path = app.path.clone(); if &app.path == "" { return Err(Error::BadRequest("App path cannot be empty".to_string())); } - let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)", &app.path, @@ -744,21 +949,19 @@ async fn create_app( .fetch_one(&mut *tx) .await? .unwrap_or(false); - if exists { return Err(Error::BadRequest(format!( "App with path {} already exists", &app.path ))); } - if let Some(custom_path) = &app.custom_path { require_admin(authed.is_admin, &authed.username)?; let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))", custom_path, - if *CLOUD_HOSTED { Some(&w_id) } else { None } + if *CLOUD_HOSTED { Some(w_id) } else { None } ) .fetch_one(&mut *tx) .await?.unwrap_or(false); @@ -770,7 +973,6 @@ async fn create_app( ))); } } - sqlx::query!( "DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'", &app.path, @@ -778,7 +980,6 @@ async fn create_app( ) .execute(&mut *tx) .await?; - let id = sqlx::query_scalar!( "INSERT INTO app (workspace_id, path, summary, policy, versions, draft_only, custom_path) @@ -789,24 +990,24 @@ async fn create_app( json!(app.policy), app.draft_only, app.custom_path + .as_ref() .map(|s| if s.is_empty() { None } else { Some(s) }) .flatten() ) .fetch_one(&mut *tx) .await?; - let v_id = sqlx::query_scalar!( "INSERT INTO app_version - (app_id, value, created_by) - VALUES ($1, $2::text::json, $3) RETURNING id", + (app_id, value, created_by, raw_app) + VALUES ($1, $2::text::json, $3, $4) RETURNING id", id, //to preserve key orders serde_json::to_string(&app.value).unwrap(), authed.username, + raw_app ) .fetch_one(&mut *tx) .await?; - sqlx::query!( "UPDATE app SET versions = array_append(versions, $1::bigint) WHERE id = $2", v_id, @@ -820,22 +1021,20 @@ async fn create_app( &authed, "apps.create", ActionKind::Create, - &w_id, + w_id, Some(&app.path), None, ) .await?; - let mut args: HashMap> = HashMap::new(); - if let Some(dm) = app.deployment_message { + if let Some(dm) = &app.deployment_message { args.insert("deployment_message".to_string(), to_raw_value(&dm)); } - let tx = PushIsolationLevel::Transaction(tx); let (dependency_job_uuid, new_tx) = push( &db, tx, - &w_id, + w_id, JobPayload::AppDependencies { path: app.path.clone(), version: v_id }, PushArgs { args: &args, extra: None }, &authed.username, @@ -859,14 +1058,7 @@ async fn create_app( .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); - new_tx.commit().await?; - - webhook.send_message( - w_id.clone(), - WebhookMessage::CreateApp { workspace: w_id, path: app.path.clone() }, - ); - - Ok((StatusCode::CREATED, app.path)) + Ok((new_tx, path, v_id)) } async fn list_hub_apps(Extension(db): Extension) -> impl IntoResponse { @@ -987,12 +1179,76 @@ async fn update_app( Path((w_id, path)): Path<(String, StripPath)>, Json(ns): Json, ) -> Result { - use sql_builder::prelude::*; - + // create_app_internal(authed, user_db, db, &w_id, &mut app).await?; let path = path.to_path(); + let opath = path.to_string(); + let (new_tx, npath, _v_id) = + update_app_internal(authed, db, user_db, &w_id, path, false, ns).await?; + new_tx.commit().await?; + webhook.send_message( + w_id.clone(), + WebhookMessage::UpdateApp { + workspace: w_id.clone(), + old_path: opath.clone(), + new_path: npath.clone(), + }, + ); + + Ok(format!("app {} updated (npath: {:?})", opath, npath)) +} + +async fn update_app_raw<'a>( + authed: ApiAuthed, + Extension(user_db): Extension, + Extension(db): Extension, + Extension(webhook): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + multipart: Multipart, +) -> Result { + let path = path.to_path(); + let opath = path.to_string(); + let (npath, _id) = process_app_multipart!( + authed, + user_db, + db, + &w_id, + path, + multipart, + update_app_internal + ) + .await?; + + webhook.send_message( + w_id.clone(), + WebhookMessage::UpdateApp { + workspace: w_id.clone(), + old_path: opath.to_owned(), + new_path: npath.clone(), + }, + ); + + Ok(format!("app {} updated (npath: {:?})", opath, npath)) +} +// async fn create_app_internal<'a>( +// authed: ApiAuthed, +// db: sqlx::Pool, +// user_db: UserDB, +// w_id: &String, +// app: &mut CreateApp, +// ) + +async fn update_app_internal<'a>( + authed: ApiAuthed, + db: sqlx::Pool, + user_db: UserDB, + w_id: &str, + path: &str, + raw_app: bool, + ns: EditApp, +) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> { + use sql_builder::prelude::*; let mut tx = user_db.clone().begin(&authed).await?; - let npath = if ns.policy.is_some() || ns.path.is_some() || ns.summary.is_some() @@ -1039,7 +1295,7 @@ async fn update_app( let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))", ncustom_path, - if *CLOUD_HOSTED { Some(&w_id) } else { None }, + if *CLOUD_HOSTED { Some(w_id) } else { None }, path, w_id ) @@ -1086,12 +1342,13 @@ async fn update_app( let v_id = sqlx::query_scalar!( "INSERT INTO app_version - (app_id, value, created_by) - VALUES ($1, $2::text::json, $3) RETURNING id", + (app_id, value, created_by, raw_app) + VALUES ($1, $2::text::json, $3, $4) RETURNING id", app_id, //to preserve key orders serde_json::to_string(&nvalue).unwrap(), authed.username, + raw_app ) .fetch_one(&mut *tx) .await?; @@ -1122,7 +1379,6 @@ async fn update_app( ))); } }; - sqlx::query!( "DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'", path, @@ -1130,29 +1386,26 @@ async fn update_app( ) .execute(&mut *tx) .await?; - audit_log( &mut *tx, &authed, "apps.update", ActionKind::Update, - &w_id, + w_id, Some(&npath), None, ) .await?; - let tx = PushIsolationLevel::Transaction(tx); let mut args: HashMap> = HashMap::new(); if let Some(dm) = ns.deployment_message { args.insert("deployment_message".to_string(), to_raw_value(&dm)); } args.insert("parent_path".to_string(), to_raw_value(&path)); - let (dependency_job_uuid, new_tx) = push( &db, tx, - &w_id, + w_id, JobPayload::AppDependencies { path: npath.clone(), version: v_id }, PushArgs { args: &args, extra: None }, &authed.username, @@ -1175,18 +1428,7 @@ async fn update_app( ) .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); - new_tx.commit().await?; - - webhook.send_message( - w_id.clone(), - WebhookMessage::UpdateApp { - workspace: w_id, - old_path: path.to_owned(), - new_path: npath.clone(), - }, - ); - - Ok(format!("app {} updated (npath: {:?})", path, npath)) + Ok((new_tx, npath, v_id)) } #[derive(Debug, Deserialize, Clone)] @@ -1528,15 +1770,97 @@ struct UploadFileToS3Query { #[cfg(feature = "parquet")] #[derive(Serialize, Deserialize)] -struct DeleteTokenClaims { +struct S3DeleteTokenClaims { file_key: String, on_behalf_of_email: String, permissioned_as: String, username: String, s3_resource_path: Option, + workspace: String, pub exp: usize, } +#[cfg(feature = "parquet")] +#[derive(Deserialize)] +struct S3TokenRequestBody { + s3_objects: Vec, +} +#[cfg(feature = "parquet")] +async fn sign_s3_objects( + Extension(db): Extension, + Path(w_id): Path, + Json(body): Json, +) -> Result>> { + let workspace_key = get_workspace_key(&w_id, &db).await?; + + let futures = body.s3_objects.into_iter().map(|s3_object| async { + let exp = (chrono::Utc::now() + chrono::Duration::hours(12)).timestamp(); + let mut message = format!("file_key={}&exp={}", s3_object.s3.clone(), exp); + if let Some(ref storage) = s3_object.storage { + message = format!("{}&storage={}", message, storage); + } + + let mut max = HmacSha256::new_from_slice(workspace_key.as_bytes()) + .map_err(|err| Error::internal_err(format!("Failed to create hmac: {}", err)))?; + max.update(message.as_bytes()); + let result = max.finalize(); + let signature = hex::encode(result.into_bytes()); + + let presigned = format!("exp={}&sig={}", exp, signature); + + Ok::<_, Error>(S3Object { presigned: Some(presigned), ..s3_object }) + }); + + let signed_s3_objects = futures::future::try_join_all(futures).await?; + + Ok(Json(signed_s3_objects)) +} + +#[cfg(feature = "parquet")] +async fn validate_s3_signature(file_query: &AppS3FileQuery, w_id: &str, db: &DB) -> Result<()> { + let workspace_key = get_workspace_key(w_id, &db).await?; + + let Some(exp) = file_query + .exp + .as_ref() + .map(|e| e.parse::().unwrap_or_default()) + else { + return Err(Error::BadRequest("Missing exp".to_string())); + }; + + let Some(ref sig) = file_query.sig else { + return Err(Error::BadRequest("Missing signature".to_string())); + }; + + let mut message = format!("file_key={}&exp={}", file_query.s3, exp); + + if let Some(ref storage) = file_query.storage { + message = format!("{}&storage={}", message, storage); + } + + let mut mac = HmacSha256::new_from_slice(workspace_key.as_bytes()) + .map_err(|err| Error::internal_err(format!("Failed to create hmac: {}", err)))?; + + mac.update(message.as_bytes()); + + let sig_bytes = hex::decode(sig)?; + mac.verify_slice(&sig_bytes) + .map_err(|err| Error::BadRequest(format!("Invalid signature: {}", err)))?; + + if exp < chrono::Utc::now().timestamp() { + return Err(Error::BadRequest("Signature expired".to_string())); + } + + Ok(()) +} + +#[cfg(not(feature = "parquet"))] +async fn sign_s3_objects() -> Result<()> { + return Err(Error::BadRequest( + "This endpoint requires the parquet feature to be enabled".to_string(), + )); +} + #[cfg(feature = "parquet")] #[derive(Serialize)] struct AppUploadFileResponse { @@ -1704,7 +2028,7 @@ async fn upload_s3_file_from_app( if !has_unnamed_policy { return Err(Error::BadRequest( - "no policy found for unnamed s3 file uplooad".to_string(), + "no policy found for unnamed s3 file upload".to_string(), )); } @@ -1791,13 +2115,14 @@ async fn upload_s3_file_from_app( upload_file_from_req(s3_client, &file_key, request, options).await?; - let delete_token = jwt::encode_with_internal_secret(DeleteTokenClaims { + let delete_token = jwt::encode_with_internal_secret(S3DeleteTokenClaims { file_key: file_key.clone(), on_behalf_of_email, permissioned_as, username, s3_resource_path: query.s3_resource_path, - exp: (chrono::Utc::now() + chrono::Duration::seconds(3600 * 24)).timestamp() as usize, + workspace: w_id.clone(), + exp: (chrono::Utc::now() + chrono::Duration::hours(12)).timestamp() as usize, }) .await?; @@ -1817,14 +2142,19 @@ async fn delete_s3_file_from_app( Path(w_id): Path, Query(query): Query, ) -> Result<()> { - let DeleteTokenClaims { + let S3DeleteTokenClaims { file_key, on_behalf_of_email, permissioned_as, username, s3_resource_path, + workspace, .. - } = jwt::decode_with_internal_secret::(&query.delete_token).await?; + } = jwt::decode_with_internal_secret::(&query.delete_token).await?; + + if workspace != w_id { + return Err(Error::BadRequest("Invalid workspace".to_string())); + } let on_behalf_authed = fetch_api_authed_from_permissioned_as( permissioned_as, @@ -1932,7 +2262,7 @@ async fn get_on_behalf_authed_from_app( async fn check_if_allowed_to_access_s3_file_from_app( db: &DB, opt_authed: &Option, - file_key: &str, + file_query: &AppS3FileQuery, w_id: &str, path: &str, policy: &Policy, @@ -1940,40 +2270,59 @@ async fn check_if_allowed_to_access_s3_file_from_app( // if anonymous, check that the file was the result of an app script ran by an anonymous user in the last 3 hours // otherwise, if logged in, allow any file (TODO: change that when we implement better s3 policy) - let allowed = opt_authed.is_some() - || sqlx::query_scalar!( - r#"SELECT EXISTS ( - SELECT 1 FROM v2_as_completed_job - WHERE workspace_id = $2 - AND (job_kind = 'appscript' OR job_kind = 'preview') - AND created_by = 'anonymous' - AND started_at > now() - interval '3 hours' - AND script_path LIKE $3 || '/%' - AND result @> ('{"s3":"' || $1 || '"}')::jsonb - )"#, - file_key, - w_id, - path, - ) - .fetch_one(db) - .await? - .unwrap_or(false) - - // check if the file is allowed by the allowed_s3_keys policy - || policy.allowed_s3_keys.as_ref().unwrap().iter().any(|key| key.s3_path == file_key); - - if !allowed { - Err(Error::BadRequest("File restricted".to_string())) - } else { + if file_query.sig.is_some() { + validate_s3_signature(file_query, w_id, &db).await + } else if opt_authed.is_some() { Ok(()) + } else { + let allowed = policy + .allowed_s3_keys + .as_ref() + .unwrap() + .iter() + .any(|key| key.s3_path == file_query.s3 && key.storage == file_query.storage) + || { + sqlx::query_scalar!( + r#"SELECT EXISTS ( + SELECT 1 FROM v2_as_completed_job + WHERE workspace_id = $2 + AND (job_kind = 'appscript' OR job_kind = 'preview') + AND created_by = 'anonymous' + AND started_at > now() - interval '3 hours' + AND script_path LIKE $3 || '/%' + AND result @> ('{"s3":"' || $1 || '"}')::jsonb + )"#, + file_query.s3, + w_id, + path, + ) + .fetch_one(db) + .await? + .unwrap_or(false) + }; + + if !allowed { + Err(Error::BadRequest("File restricted".to_string())) + } else { + Ok(()) + } } } #[cfg(feature = "parquet")] -#[derive(Deserialize)] -pub struct DownloadFileQueryWithForceViewerAllowedS3Keys { +#[derive(Deserialize, Debug)] +struct AppS3FileQuery { + s3: String, + storage: Option, + sig: Option, + exp: Option, +} + +#[cfg(feature = "parquet")] +#[derive(Deserialize, Debug)] +struct AppS3FileQueryWithForceViewerAllowedS3Keys { #[serde(flatten)] - pub file_query: DownloadFileQuery, + pub file_query: AppS3FileQuery, pub force_viewer_allowed_s3_keys: Option, } @@ -1982,7 +2331,7 @@ async fn download_s3_file_from_app( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, + Query(query): Query, ) -> Result { let path = path.to_path(); @@ -2001,46 +2350,26 @@ async fn download_s3_file_from_app( check_if_allowed_to_access_s3_file_from_app( &db, &opt_authed, - &query.file_query.file_key, + &query.file_query, &w_id, &path, &policy, ) .await?; - download_s3_file_internal(on_behalf_authed, &db, None, "", &w_id, query.file_query).await -} - -#[cfg(not(feature = "parquet"))] -async fn load_s3_file_image_preview_from_app() -> Result<()> { - return Err(Error::BadRequest( - "This endpoint requires the parquet feature to be enabled".to_string(), - )); -} - -#[cfg(feature = "parquet")] -async fn load_s3_file_image_preview_from_app( - OptAuthed(opt_authed): OptAuthed, - Extension(db): Extension, - Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, -) -> Result { - let path = path.to_path(); - - let (on_behalf_authed, policy) = - get_on_behalf_authed_from_app(&db, &path, &w_id, &opt_authed, None).await?; - - check_if_allowed_to_access_s3_file_from_app( + download_s3_file_internal( + on_behalf_authed, &db, - &opt_authed, - &query.file_key, + None, + "", &w_id, - &path, - &policy, + DownloadFileQuery { + file_key: query.file_query.s3, + s3_resource_path: None, + storage: query.file_query.storage, + }, ) - .await?; - - load_image_preview_internal(on_behalf_authed, &db, "", &w_id, query).await + .await } fn get_on_behalf_of(policy: &Policy) -> Result<(String, String)> { diff --git a/backend/windmill-api/src/apps_ee.rs b/backend/windmill-api/src/apps_ee.rs deleted file mode 100644 index a7737664b9..0000000000 --- a/backend/windmill-api/src/apps_ee.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::Router; - -pub fn global_unauthed_service() -> Router { - Router::new() -} diff --git a/backend/windmill-api/src/apps_oss.rs b/backend/windmill-api/src/apps_oss.rs new file mode 100644 index 0000000000..346fff9ff1 --- /dev/null +++ b/backend/windmill-api/src/apps_oss.rs @@ -0,0 +1,11 @@ +#[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() +} diff --git a/backend/windmill-api/src/args.rs b/backend/windmill-api/src/args.rs index a482745f27..0b8f4d5210 100644 --- a/backend/windmill-api/src/args.rs +++ b/backend/windmill-api/src/args.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; -#[cfg(feature = "parquet")] -use crate::job_helpers_ee::get_workspace_s3_resource; use axum::{ extract::{FromRequest, FromRequestParts, Multipart, Query, Request}, http::{HeaderMap, Uri}, @@ -9,139 +7,323 @@ use axum::{ }; use bytes::Bytes; use http::{header::CONTENT_TYPE, request::Parts, StatusCode}; -#[cfg(feature = "parquet")] -use object_store::{Attribute, Attributes}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; use sqlx::types::JsonRawValue; -#[cfg(feature = "parquet")] -use windmill_common::s3_helpers::build_object_store_client; -use windmill_common::{error::Error, worker::to_raw_value, DB}; +use windmill_common::{ + error::Error, + triggers::{RunnableFormat, RunnableFormatVersion, TriggerKind}, + worker::to_raw_value, + DB, +}; use windmill_queue::PushArgsOwned; -use crate::db::ApiAuthed; -#[cfg(feature = "parquet")] -use crate::job_helpers_ee::{get_random_file_name, upload_file_internal}; +use crate::{ + db::ApiAuthed, + trigger_helpers::{get_runnable_format, RunnableId}, +}; -#[derive(Default)] -pub struct WebhookArgs { - pub args: PushArgsOwned, - pub multipart: Option, - pub wrap_body: Option, +#[derive(Debug)] +pub enum RawBody { + Json(String), + CEJson(String), + Text(String), + Xml(String), + UrlEncoded(Bytes), + Multipart(Multipart), + Empty, } -impl WebhookArgs { +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Body { + HashMap(HashMap>), + NoHashMap(Box), +} + +#[derive(Debug, Clone, Default)] +pub struct WebhookArgsMetadata { + pub raw_string: Option, + pub headers: HashMap>, + pub method: http::Method, + pub query: HashMap>, + pub query_wrap_body: bool, + pub query_use_raw: bool, +} + +pub struct RawWebhookArgs { + pub body: RawBody, + pub metadata: WebhookArgsMetadata, +} + +#[derive(Debug, Clone)] +pub struct WebhookArgs { + pub body: Body, + pub metadata: WebhookArgsMetadata, +} + +// capture +// + +impl RawWebhookArgs { #[cfg(not(feature = "parquet"))] - pub async fn to_push_args_owned( - self, + pub async fn process_multipart( + _multipart: Multipart, _authed: &ApiAuthed, _db: &DB, _w_id: &str, - ) -> Result { - if self.multipart.is_some() { - return Err(Error::BadRequest(format!( - "multipart/form-data requires the parquet feature" - ))); - } - - Ok(self.args) + ) -> Result>, Error> { + return Err(Error::BadRequest(format!( + "multipart/form-data requires the parquet feature" + ))); } #[cfg(feature = "parquet")] - pub async fn to_push_args_owned( - mut self, + async fn process_multipart( + mut multipart: Multipart, + authed: &ApiAuthed, + db: &DB, + w_id: &str, + ) -> Result>, Error> { + use crate::job_helpers_oss::{ + get_random_file_name, get_workspace_s3_resource, upload_file_internal, + }; + use futures::TryStreamExt; + use object_store::{Attribute, Attributes}; + use windmill_common::s3_helpers::build_object_store_client; + + let (_, s3_resource) = get_workspace_s3_resource(authed, db, None, "", w_id, None).await?; + + if let Some(s3_resource) = s3_resource { + let s3_client = build_object_store_client(&s3_resource).await?; + + let mut body = HashMap::new(); + let mut files = HashMap::new(); + + while let Some(field) = multipart.next_field().await.map_err(|e| { + Error::BadRequest(format!("Error reading multipart field: {}", e.body_text())) + })? { + if let Some(name) = field.name().map(|x| x.to_string()) { + if let Some(content_type) = field.content_type() { + let ext = field + .file_name() + .map(|x| x.split('.').last()) + .flatten() + .map(|x| x.to_string()); + + let file_key = get_random_file_name(ext); + + let options = Attributes::from_iter(vec![ + (Attribute::ContentType, content_type.to_string()), + ( + Attribute::ContentDisposition, + if let Some(filename) = field.file_name() { + format!("inline; filename=\"{}\"", filename) + } else { + "inline".to_string() + }, + ), + ]) + .into(); + + let bytes_stream = field + .into_stream() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)); + + upload_file_internal(s3_client.clone(), &file_key, bytes_stream, options) + .await?; + + files.entry(name).or_insert(vec![]).push(serde_json::json!({ + "s3": &file_key + })); + } else { + body.insert(name, to_raw_value(&field.text().await.unwrap_or_default())); + } + } + } + + for (k, v) in files { + body.insert(k, to_raw_value(&v)); + } + + Ok(body) + } else { + Err(Error::BadRequest(format!( + "You need to connect your workspace to an S3 bucket to use multipart/form-data" + ))) + } + } + + pub async fn process_args( + self, + authed: &ApiAuthed, + db: &DB, + w_id: &str, + force_use_raw: Option, + ) -> Result { + let use_raw = force_use_raw.unwrap_or(self.metadata.query_use_raw); + + match self.body { + RawBody::Multipart(multipart) => { + let body = Self::process_multipart(multipart, authed, db, w_id).await?; + Ok(WebhookArgs { body: Body::HashMap(body), metadata: self.metadata }) + } + RawBody::Empty => { + let mut metadata = self.metadata; + if use_raw { + metadata.raw_string = Some("".to_string()); + } + Ok(WebhookArgs { body: Body::HashMap(HashMap::new()), metadata }) + } + RawBody::Text(s) | RawBody::Xml(s) => Ok(WebhookArgs { + body: Body::HashMap(HashMap::new()), + metadata: WebhookArgsMetadata { raw_string: Some(s), ..self.metadata }, + }), + RawBody::UrlEncoded(bytes) => { + let mut metadata = self.metadata; + if use_raw { + let raw_string = String::from_utf8(bytes.to_vec()) + .map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)))?; + metadata.raw_string = Some(raw_string); + } + let payload: HashMap> = serde_urlencoded::from_bytes(&bytes) + .map_err(|e| Error::BadRequest(format!("invalid urlencoded data: {}", e)))?; + let payload = payload + .into_iter() + .map(|(k, v)| (k, to_raw_value(&v))) + .collect::>(); + + Ok(WebhookArgs { body: Body::HashMap(payload), metadata }) + } + RawBody::Json(s) => WebhookArgs::from_json(self.metadata, use_raw, s).await, + RawBody::CEJson(s) => WebhookArgs::from_ce_json(self.metadata, use_raw, s).await, + } + } + + pub async fn to_main_args( + self, authed: &ApiAuthed, db: &DB, w_id: &str, ) -> Result { - use futures::TryStreamExt; + let args = self.process_args(authed, db, w_id, None).await?; + args.to_main_args() + } - if let Some(mut multipart) = self.multipart { - { - let (_, s3_resource) = - get_workspace_s3_resource(authed, db, None, "", w_id, None).await?; + pub async fn to_args_from_runnable( + self, + authed: &ApiAuthed, + db: &DB, + w_id: &str, + runnable_id: RunnableId, + skip_preprocessor: Option, + ) -> Result { + let args = self.process_args(authed, db, w_id, None).await?; + args.to_args_from_runnable(db, w_id, runnable_id, skip_preprocessor) + .await + } +} - if let Some(s3_resource) = s3_resource { - let s3_client = build_object_store_client(&s3_resource).await?; +#[derive(Serialize)] +struct WebhookPreprocessorEvent { + kind: String, + body: Box, + raw_string: Option, + headers: HashMap>, + query: HashMap>, +} - let mut body = HashMap::new(); - let mut files = HashMap::new(); +impl WebhookArgs { + pub fn to_main_args(self) -> Result { + self.to_args_from_format(RunnableFormat { + has_preprocessor: false, + version: RunnableFormatVersion::V2, + }) + } - while let Some(field) = multipart.next_field().await.map_err(|e| { - Error::BadRequest(format!( - "Error reading multipart field: {}", - e.body_text() - )) - })? { - if let Some(name) = field.name().map(|x| x.to_string()) { - if let Some(content_type) = field.content_type() { - let ext = field - .file_name() - .map(|x| x.split('.').last()) - .flatten() - .map(|x| x.to_string()); + pub async fn to_args_from_runnable( + self, + db: &DB, + w_id: &str, + runnable_id: RunnableId, + skip_preprocessor: Option, + ) -> Result { + if skip_preprocessor.unwrap_or(false) { + self.to_main_args() + } else { + let runnable_format = + get_runnable_format(runnable_id, w_id, db, &TriggerKind::Webhook).await?; - let file_key = get_random_file_name(ext); + self.to_args_from_format(runnable_format) + } + } - let options = Attributes::from_iter(vec![ - (Attribute::ContentType, content_type.to_string()), - ( - Attribute::ContentDisposition, - if let Some(filename) = field.file_name() { - format!("inline; filename=\"{}\"", filename) - } else { - "inline".to_string() - }, - ), - ]) - .into(); + pub fn to_args_from_format( + self, + runnable_format: RunnableFormat, + ) -> Result { + match runnable_format { + RunnableFormat { has_preprocessor: true, version: RunnableFormatVersion::V2 } => { + let mut args = HashMap::new(); - let bytes_stream = field.into_stream().map_err(|err| { - std::io::Error::new(std::io::ErrorKind::Other, err) - }); + args.insert( + "event".to_string(), + to_raw_value(&WebhookPreprocessorEvent { + kind: "webhook".to_string(), + body: to_raw_value(&self.body), + raw_string: self.metadata.raw_string, + headers: self.metadata.headers, + query: self.metadata.query, + }), + ); - upload_file_internal( - s3_client.clone(), - &file_key, - bytes_stream, - options, - ) - .await?; + Ok(PushArgsOwned { args, extra: None }) + } + RunnableFormat { has_preprocessor, .. } => { + let mut extra = HashMap::new(); - files.entry(name).or_insert(vec![]).push(serde_json::json!({ - "s3": &file_key - })); - } else { - body.insert( - name, - to_raw_value(&field.text().await.unwrap_or_default()), - ); - } + let WebhookArgsMetadata { query, query_wrap_body, headers, raw_string, .. } = + self.metadata; + + for (k, v) in headers { + extra.insert(k, v); + } + + for (k, v) in query { + extra.insert(k, v); + } + + if let Some(raw_string) = raw_string { + extra.insert("raw_string".to_string(), to_raw_value(&raw_string)); + } + + if has_preprocessor { + // if has preprocessor, it has to be v1 + extra.insert( + "wm_trigger".to_string(), + to_raw_value(&serde_json::json!({ + "kind": "webhook", + })), + ); + } + + let extra = if extra.is_empty() { None } else { Some(extra) }; + + match self.body { + Body::HashMap(mut body) => { + if query_wrap_body { + body = HashMap::from([("body".to_string(), to_raw_value(&body))]); } + Ok(PushArgsOwned { args: body, extra }) } - - for (k, v) in files { - body.insert(k, to_raw_value(&v)); + Body::NoHashMap(args) => { + let mut hm = HashMap::new(); + hm.insert("body".to_string(), args); + Ok(PushArgsOwned { args: hm, extra }) } - - if self.wrap_body.unwrap_or(false) { - self.args - .args - .insert("body".to_string(), to_raw_value(&body)); - } else { - self.args.args.extend(body); - } - - return Ok(self.args); } } - - return Err(Error::BadRequest(format!( - "You need to connect your workspace to an S3 bucket to use multipart/form-data" - ))); } - - Ok(self.args) } } @@ -163,131 +345,107 @@ async fn req_to_string( .map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)).into_response()) } +pub async fn try_from_request_body( + request: Request, + _state: &S, + is_http_trigger: bool, +) -> Result +where + S: Send + Sync, +{ + let (content_type, metadata) = { + let headers_map = request.headers(); + let content_type_header = headers_map.get(CONTENT_TYPE); + let content_type = content_type_header.and_then(|value| value.to_str().ok()); + let uri = request.uri(); + let request_query = Query::::try_from_uri(uri).unwrap().0; + let headers = build_headers(&headers_map, request_query.include_header, is_http_trigger); + let query_decode = DecodeQueries::from_uri(uri, is_http_trigger); + let mut query = HashMap::new(); + if let Some(DecodeQueries(queries)) = query_decode { + query.extend(queries); + } + let raw = !is_http_trigger && request_query.raw.unwrap_or(false); + let wrap_body = !is_http_trigger && request_query.wrap_body.unwrap_or(false); + ( + content_type, + WebhookArgsMetadata { + headers, + query, + method: request.method().clone(), + raw_string: None, + query_wrap_body: wrap_body, + query_use_raw: raw, + }, + ) + }; + + let no_content_type = content_type.is_none(); + if no_content_type || content_type.unwrap().starts_with("application/json") { + let bytes = Bytes::from_request(request, _state) + .await + .map_err(IntoResponse::into_response)?; + if no_content_type && bytes.is_empty() { + Ok(RawWebhookArgs { body: RawBody::Empty, metadata }) + } else { + let str = String::from_utf8(bytes.to_vec()) + .map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)).into_response())?; + Ok(RawWebhookArgs { body: RawBody::Json(str), metadata }) + } + } else if content_type + .unwrap() + .starts_with("application/cloudevents+json") + { + let str = req_to_string(request, _state).await?; + + Ok(RawWebhookArgs { body: RawBody::CEJson(str), metadata }) + } else if content_type + .unwrap() + .starts_with("application/cloudevents-batch+json") + { + Err( + Error::BadRequest(format!("Cloud events batching is not supported yet")) + .into_response(), + ) + } else if content_type.unwrap().starts_with("text/plain") { + let str = req_to_string(request, _state).await?; + Ok(RawWebhookArgs { body: RawBody::Text(str), metadata }) + } else if content_type + .unwrap() + .starts_with("application/x-www-form-urlencoded") + { + let bytes = Bytes::from_request(request, _state) + .await + .map_err(IntoResponse::into_response)?; + + Ok(RawWebhookArgs { body: RawBody::UrlEncoded(bytes), metadata }) + } else if content_type.unwrap().starts_with("application/xml") + || content_type.unwrap().starts_with("text/xml") + { + let str = req_to_string(request, _state).await?; + Ok(RawWebhookArgs { body: RawBody::Xml(str), metadata }) + } else if content_type.unwrap().starts_with("multipart/form-data") { + let multipart = Multipart::from_request(request, _state) + .await + .map_err(IntoResponse::into_response)?; + + Ok(RawWebhookArgs { body: RawBody::Multipart(multipart), metadata }) + } else { + Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response()) + } +} + #[axum::async_trait] -impl FromRequest for WebhookArgs +impl FromRequest for RawWebhookArgs where S: Send + Sync, { type Rejection = Response; - async fn from_request( - req: Request, - _state: &S, - ) -> Result { - let (content_type, mut extra, use_raw, wrap_body) = { - let headers_map = req.headers(); - let content_type_header = headers_map.get(CONTENT_TYPE); - let content_type = content_type_header.and_then(|value| value.to_str().ok()); - let uri = req.uri(); - let query = Query::::try_from_uri(uri).unwrap().0; - let mut extra = build_extra(&headers_map, query.include_header); - let query_decode = DecodeQueries::from_uri(uri); - if let Some(DecodeQueries(queries)) = query_decode { - extra.extend(queries); - } - let raw = query.raw.as_ref().is_some_and(|x| *x); - let wrap_body = query.wrap_body.as_ref().is_some_and(|x| *x); - (content_type, extra, raw, wrap_body) - }; + async fn from_request(request: Request, _state: &S) -> Result { + let args = try_from_request_body(request, _state, false).await?; - let no_content_type = content_type.is_none(); - if no_content_type || content_type.unwrap().starts_with("application/json") { - let bytes = Bytes::from_request(req, _state) - .await - .map_err(IntoResponse::into_response)?; - if no_content_type && bytes.is_empty() { - if use_raw { - extra.insert("raw_string".to_string(), to_raw_value(&"".to_string())); - } - let mut args = HashMap::new(); - if wrap_body { - args.insert("body".to_string(), to_raw_value(&serde_json::json!({}))); - } - return Ok(Self { - args: PushArgsOwned { extra: Some(extra), args: args }, - ..Default::default() - }); - } - let str = String::from_utf8(bytes.to_vec()) - .map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)).into_response())?; - - PushArgsOwned::from_json(extra, use_raw, wrap_body, str) - .await - .map(|args| Self { args, ..Default::default() }) - } else if content_type - .unwrap() - .starts_with("application/cloudevents+json") - { - let str = req_to_string(req, _state).await?; - - PushArgsOwned::from_ce_json(extra, use_raw, str) - .await - .map(|args| Self { args, ..Default::default() }) - } else if content_type - .unwrap() - .starts_with("application/cloudevents-batch+json") - { - Err( - Error::BadRequest(format!("Cloud events batching is not supported yet")) - .into_response(), - ) - } else if content_type.unwrap().starts_with("text/plain") { - let str = req_to_string(req, _state).await?; - extra.insert("raw_string".to_string(), to_raw_value(&str)); - Ok(Self { - args: PushArgsOwned { extra: Some(extra), args: HashMap::new() }, - ..Default::default() - }) - } else if content_type - .unwrap() - .starts_with("application/x-www-form-urlencoded") - { - let bytes = Bytes::from_request(req, _state) - .await - .map_err(IntoResponse::into_response)?; - - if use_raw { - let raw_string = String::from_utf8(bytes.to_vec()).map_err(|e| { - Error::BadRequest(format!("invalid utf8: {}", e)).into_response() - })?; - extra.insert("raw_string".to_string(), to_raw_value(&raw_string)); - } - - let payload: HashMap> = serde_urlencoded::from_bytes(&bytes) - .map_err(|e| { - Error::BadRequest(format!("invalid urlencoded data: {}", e)).into_response() - })?; - let payload = payload - .into_iter() - .map(|(k, v)| (k, to_raw_value(&v))) - .collect::>(); - - return Ok(Self { - args: PushArgsOwned { extra: Some(extra), args: payload }, - ..Default::default() - }); - } else if content_type.unwrap().starts_with("application/xml") - || content_type.unwrap().starts_with("text/xml") - { - let str = req_to_string(req, _state).await?; - extra.insert("raw_string".to_string(), to_raw_value(&str)); - Ok(Self { - args: PushArgsOwned { extra: Some(extra), args: HashMap::new() }, - ..Default::default() - }) - } else if content_type.unwrap().starts_with("multipart/form-data") { - let multipart = Multipart::from_request(req, _state) - .await - .map_err(IntoResponse::into_response)?; - - Ok(Self { - args: PushArgsOwned { extra: Some(extra), args: HashMap::new() }, - multipart: Some(multipart), - wrap_body: Some(wrap_body), - }) - } else { - Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response()) - } + Ok(args) } } @@ -299,27 +457,37 @@ lazy_static::lazy_static! { .collect()).unwrap_or_default(); } -pub fn build_extra( +pub fn build_headers( headers: &HeaderMap, include_header: Option, + is_http_trigger: bool, ) -> HashMap> { - let mut args = HashMap::new(); - let whitelist = include_header - .map(|s| s.split(",").map(|s| s.to_string()).collect::>()) - .unwrap_or_default(); + let mut selected_headers = HashMap::new(); - whitelist - .iter() - .chain(INCLUDE_HEADERS.iter()) - .for_each(|h| { - if let Some(v) = headers.get(h) { - args.insert( - h.to_string().to_lowercase().replace('-', "_"), - to_raw_value(&v.to_str().unwrap().to_string()), - ); - } - }); - args + if is_http_trigger { + for (k, v) in headers.iter() { + selected_headers.insert( + k.to_string(), + to_raw_value(&v.to_str().unwrap_or("").to_string()), + ); + } + } else { + let whitelist = include_header + .map(|s| s.split(",").map(|s| s.to_string()).collect::>()) + .unwrap_or_default(); + whitelist + .iter() + .chain(INCLUDE_HEADERS.iter()) + .for_each(|h| { + if let Some(v) = headers.get(h) { + selected_headers.insert( + h.to_string().to_lowercase().replace('-', "_"), + to_raw_value(&v.to_str().unwrap_or("").to_string()), + ); + } + }); + } + selected_headers } #[derive(Deserialize)] @@ -337,37 +505,49 @@ where type Rejection = Response; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - Ok(DecodeQueries::from_uri(&parts.uri).unwrap_or_else(|| DecodeQueries(HashMap::new()))) + Ok(DecodeQueries::from_uri(&parts.uri, false) + .unwrap_or_else(|| DecodeQueries(HashMap::new()))) } } impl DecodeQueries { - pub fn from_uri(uri: &Uri) -> Option { + pub fn from_uri(uri: &Uri, is_http_trigger: bool) -> Option { let query = uri.query(); if query.is_none() { return None; } let query = query.unwrap(); - let include_query = serde_urlencoded::from_str::(query) - .map(|x| x.include_query) - .ok() - .flatten() - .unwrap_or_default(); - let parse_query_args = include_query - .split(",") - .map(|s| s.to_string()) - .collect::>(); - let mut args = HashMap::new(); - if !parse_query_args.is_empty() { + if is_http_trigger { let queries = serde_urlencoded::from_str::>(query).unwrap_or_default(); - parse_query_args.iter().for_each(|h| { - if let Some(v) = queries.get(h) { - args.insert(h.to_string(), to_raw_value(v)); - } - }); + Some(DecodeQueries( + queries + .into_iter() + .map(|(k, v)| (k, to_raw_value(&v))) + .collect(), + )) + } else { + let include_query = serde_urlencoded::from_str::(query) + .map(|x| x.include_query) + .ok() + .flatten() + .unwrap_or_default(); + let parse_query_args = include_query + .split(",") + .map(|s| s.to_string()) + .collect::>(); + let mut args = HashMap::new(); + if !parse_query_args.is_empty() { + let queries = serde_urlencoded::from_str::>(query) + .unwrap_or_default(); + parse_query_args.iter().for_each(|h| { + if let Some(v) = queries.get(h) { + args.insert(h.to_string(), to_raw_value(v)); + } + }); + } + Some(DecodeQueries(args)) } - Some(DecodeQueries(args)) } } @@ -404,69 +584,50 @@ fn restructure_cloudevents_metadata( } } -trait PushArgsOwnedExt: Sized { +impl WebhookArgs { async fn from_json( - extra: HashMap>, - use_raw: bool, - force_wrap_body: bool, - str: String, - ) -> Result; - - async fn from_ce_json( - extra: HashMap>, + mut metadata: WebhookArgsMetadata, use_raw: bool, str: String, - ) -> Result; -} - -impl PushArgsOwnedExt for PushArgsOwned { - async fn from_json( - mut extra: HashMap>, - use_raw: bool, - force_wrap_body: bool, - str: String, - ) -> Result { + ) -> Result { if use_raw { - extra.insert("raw_string".to_string(), to_raw_value(&str)); + metadata.raw_string = Some(str.clone()); } - let wrap_body = force_wrap_body || str.len() > 0 && str.chars().next().unwrap() != '{'; + let no_hashmap = str.len() > 0 && str.chars().next().unwrap() != '{'; - if wrap_body { + if no_hashmap { let args = serde_json::from_str::>>(&str) - .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)).into_response())? + .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))? .unwrap_or_else(|| to_raw_value(&serde_json::Value::Null)); - let mut hm = HashMap::new(); - hm.insert("body".to_string(), args); - Ok(PushArgsOwned { extra: Some(extra), args: hm }) + + Ok(Self { body: Body::NoHashMap(args), metadata }) } else { let hm = serde_json::from_str::>>>(&str) - .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)).into_response())? + .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))? .unwrap_or_else(HashMap::new); - Ok(PushArgsOwned { extra: Some(extra), args: hm }) + Ok(Self { body: Body::HashMap(hm), metadata }) } } async fn from_ce_json( - mut extra: HashMap>, + mut metadata: WebhookArgsMetadata, use_raw: bool, str: String, - ) -> Result { + ) -> Result { if use_raw { - extra.insert("raw_string".to_string(), to_raw_value(&str)); + metadata.raw_string = Some(str.clone()); } - let hm = serde_json::from_str::>>(&str).map_err(|e| { - Error::BadRequest(format!("invalid cloudevents+json: {}", e)).into_response() - })?; - let hm = restructure_cloudevents_metadata(hm).map_err(|e| e.into_response())?; - Ok(PushArgsOwned { extra: Some(extra), args: hm }) + let hm = serde_json::from_str::>>(&str) + .map_err(|e| Error::BadRequest(format!("invalid cloudevents+json: {}", e)))?; + let hm = restructure_cloudevents_metadata(hm)?; + Ok(Self { body: Body::HashMap(hm), metadata }) } } #[cfg(test)] mod tests { - use std::collections::HashMap; use super::*; @@ -504,24 +665,35 @@ mod tests { "data" : 1.5 } "#; - let extra = HashMap::new(); + let metadata = WebhookArgsMetadata::default(); - let a1 = PushArgsOwned::from_ce_json(extra.clone(), false, r1.to_string()) + let a1 = WebhookArgs::from_ce_json(metadata.clone(), false, r1.to_string()) .await .expect("Failed to parse the cloudevent"); - let a2 = PushArgsOwned::from_ce_json(extra.clone(), false, r2.to_string()) + let a2 = WebhookArgs::from_ce_json(metadata.clone(), false, r2.to_string()) .await .expect("Failed to parse the cloudevent"); - a1.args.get("WEBHOOK__METADATA__").expect( - "CloudEvents should generate a neighboring `webhook-metadata` field in PushArgs", - ); - assert_eq!( - a2.args - .get("body") - .expect("Cloud events with a data field with no wrapping curly brackets should be inside of a `body` field in PushArgs") - .to_string(), - "1.5" - ); + match a1.body { + Body::HashMap(body) => { + body.get("WEBHOOK__METADATA__").expect( + "CloudEvents should generate a neighboring `webhook-metadata` field in PushArgs", + ); + } + _ => panic!("Expected a HashMap"), + } + + match a2.body { + Body::HashMap(body) => { + assert_eq!( + body + .get("body") + .expect("Cloud events with a data field with no wrapping curly brackets should be inside of a `body` field in PushArgs") + .to_string(), + "1.5" + ); + } + _ => panic!("Expected a HashMap"), + } } } diff --git a/backend/windmill-api/src/audit.rs b/backend/windmill-api/src/audit.rs index 47ba881014..336fd32881 100644 --- a/backend/windmill-api/src/audit.rs +++ b/backend/windmill-api/src/audit.rs @@ -28,7 +28,7 @@ async fn get_audit( Path((w_id, id)): Path<(String, i32)>, ) -> JsonResult { let tx = user_db.begin(&authed).await?; - let audit = windmill_audit::audit_ee::get_audit(tx, id, &w_id).await?; + let audit = windmill_audit::audit_oss::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, ) -> JsonResult> { let tx = user_db.begin(&authed).await?; - let rows = windmill_audit::audit_ee::list_audit(tx, w_id, pagination, lq).await?; + let rows = windmill_audit::audit_oss::list_audit(tx, w_id, pagination, lq).await?; Ok(Json(rows)) } diff --git a/backend/windmill-api/src/auth.rs b/backend/windmill-api/src/auth.rs index b0a18112d4..954fcad43c 100644 --- a/backend/windmill-api/src/auth.rs +++ b/backend/windmill-api/src/auth.rs @@ -1,5 +1,5 @@ #[cfg(feature = "enterprise")] -use crate::ee::ExternalJwks; +use crate::ee_oss::ExternalJwks; use axum::{ async_trait, extract::{FromRequestParts, OriginalUri, Query}, @@ -26,6 +26,21 @@ use windmill_common::{ users::{COOKIE_NAME, SUPERADMIN_SECRET_EMAIL}, }; +lazy_static::lazy_static! { + // Global auth cache accessible from main.rs for direct invalidation + pub static ref AUTH_CACHE: Cache<(String, String), ExpiringAuthCache> = Cache::new(300); +} + +// Global function to invalidate a specific token from cache +pub fn invalidate_token_from_cache(token: &str) { + // Remove all cache entries for this token (across all workspaces) + AUTH_CACHE.retain(|(_workspace_id, cached_token), _cached_value| cached_token != token); + tracing::info!( + "Invalidated token from auth cache: {}...", + &token[..token.len().min(8)] + ); +} + #[derive(Clone)] pub struct ExpiringAuthCache { pub authed: ApiAuthed, @@ -33,7 +48,6 @@ pub struct ExpiringAuthCache { } pub struct AuthCache { - cache: Cache<(String, String), ExpiringAuthCache>, db: DB, superadmin_secret: Option, #[cfg(feature = "enterprise")] @@ -47,7 +61,6 @@ impl AuthCache { #[cfg(feature = "enterprise")] ext_jwks: Option>>, ) -> Self { AuthCache { - cache: Cache::new(300), db, superadmin_secret, #[cfg(feature = "enterprise")] @@ -56,7 +69,7 @@ impl AuthCache { } pub async fn invalidate(&self, w_id: &str, token: String) { - self.cache.remove(&(w_id.to_string(), token)); + AUTH_CACHE.remove(&(w_id.to_string(), token)); } pub async fn get_authed(&self, w_id: Option, token: &str) -> Option { @@ -64,14 +77,14 @@ impl AuthCache { w_id.as_ref().unwrap_or(&"".to_string()).to_string(), token.to_string(), ); - let s = self.cache.get(&key).map(|c| c.to_owned()); + let s = AUTH_CACHE.get(&key).map(|c| c.to_owned()); match s { Some(ExpiringAuthCache { authed, expiry }) if expiry > chrono::Utc::now() => { Some(authed) } #[cfg(feature = "enterprise")] _ if token.starts_with("jwt_ext_") => { - let authed_and_exp = match crate::ee::jwt_ext_auth( + let authed_and_exp = match crate::ee_oss::jwt_ext_auth( w_id.as_ref(), token.trim_start_matches("jwt_ext_"), self.ext_jwks.clone(), @@ -86,7 +99,7 @@ impl AuthCache { }; if let Some((authed, exp)) = authed_and_exp.clone() { - self.cache.insert( + AUTH_CACHE.insert( key, ExpiringAuthCache { authed: authed.clone(), @@ -123,7 +136,7 @@ impl AuthCache { username_override, }; - self.cache.insert( + AUTH_CACHE.insert( key, ExpiringAuthCache { authed: authed.clone(), @@ -317,7 +330,7 @@ impl AuthCache { } }; if let Some(authed) = authed_o.as_ref() { - self.cache.insert( + AUTH_CACHE.insert( key, ExpiringAuthCache { authed: authed.clone(), @@ -470,6 +483,27 @@ where } } +fn maybe_get_workspace_id_from_path(path_vec: &[&str]) -> Option { + let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" { + Some(path_vec[3].to_owned()) + } else if path_vec.len() >= 5 + && path_vec[0] == "" + && path_vec[1] == "api" + && path_vec[2] == "mcp" + && path_vec[3] == "w" + { + Some(path_vec[4].to_owned()) + } else { + if path_vec.len() >= 5 && path_vec[0] == "" && path_vec[2] == "srch" && path_vec[3] == "w" { + Some(path_vec[4].to_owned()) + } else { + None + } + }; + + workspace_id +} + #[async_trait] impl FromRequestParts for ApiAuthed where @@ -482,79 +516,62 @@ where state: &S, ) -> std::result::Result { if parts.method == http::Method::OPTIONS { - return Ok(ApiAuthed { - email: "".to_owned(), - username: "".to_owned(), - is_admin: false, - is_operator: false, - groups: Vec::new(), - folders: Vec::new(), - scopes: None, - username_override: None, - }); + return Ok(ApiAuthed::default()); }; let already_authed = parts.extensions.get::(); + if let Some(authed) = already_authed { - Ok(authed.clone()) + return Ok(authed.clone()); + } + + let already_tokened = parts.extensions.get::(); + let token_o = if let Some(token) = already_tokened { + Some(token.token.clone()) } else { - let already_tokened = parts.extensions.get::(); - let token_o = if let Some(token) = already_tokened { - Some(token.token.clone()) - } else { - extract_token(parts, state).await - }; - let original_uri = OriginalUri::from_request_parts(parts, state) - .await - .ok() - .map(|x| x.0) - .unwrap_or_default(); - let path_vec: Vec<&str> = original_uri.path().split("/").collect(); + extract_token(parts, state).await + }; - let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" { - Some(path_vec[3].to_owned()) - } else { - if path_vec.len() >= 5 - && path_vec[0] == "" - && path_vec[2] == "srch" - && path_vec[3] == "w" - { - Some(path_vec[4].to_string()) - } else { - None - } - }; - if let Some(token) = token_o { - if let Ok(Extension(cache)) = - Extension::>::from_request_parts(parts, state).await - { - if let Some(authed) = cache.get_authed(workspace_id.clone(), &token).await { - parts.extensions.insert(authed.clone()); - if authed.scopes.as_ref().is_some_and(|scopes| { - scopes - .iter() - .any(|s| s.starts_with("jobs:") || s.starts_with("run:")) - }) && (path_vec.len() < 3 - || (path_vec[4] != "jobs" && path_vec[4] != "jobs_u")) - { - BRUTE_FORCE_COUNTER.increment().await; - return Err(( - StatusCode::UNAUTHORIZED, - format!("Unauthorized scoped token: {:?}", authed.scopes), - )); - } - Span::current().record("username", &authed.username.as_str()); - Span::current().record("email", &authed.email); + if let Some(token) = token_o { + if let Ok(Extension(cache)) = + Extension::>::from_request_parts(parts, state).await + { + let original_uri = OriginalUri::from_request_parts(parts, state) + .await + .ok() + .map(|x| x.0) + .unwrap_or_default(); + let path_vec: Vec<&str> = original_uri.path().split("/").collect(); + let workspace_id = maybe_get_workspace_id_from_path(&path_vec); - if let Some(workspace_id) = workspace_id { - Span::current().record("workspace_id", &workspace_id); - } - return Ok(authed); + if let Some(authed) = cache.get_authed(workspace_id.clone(), &token).await { + if authed.scopes.as_ref().is_some_and(|scopes| { + scopes + .iter() + .any(|s| s.starts_with("jobs:") || s.starts_with("run:")) + }) && (path_vec.len() < 3 + || (path_vec[4] != "jobs" && path_vec[4] != "jobs_u")) + { + BRUTE_FORCE_COUNTER.increment().await; + return Err(( + StatusCode::UNAUTHORIZED, + format!("Unauthorized scoped token: {:?}", authed.scopes), + )); } + + parts.extensions.insert(authed.clone()); + + Span::current().record("username", &authed.username.as_str()); + Span::current().record("email", &authed.email); + + if let Some(workspace_id) = workspace_id { + Span::current().record("workspace_id", &workspace_id); + } + return Ok(authed); } } - BRUTE_FORCE_COUNTER.increment().await; - Err((StatusCode::UNAUTHORIZED, "Unauthorized".to_owned())) } + BRUTE_FORCE_COUNTER.increment().await; + Err((StatusCode::UNAUTHORIZED, "Unauthorized".to_owned())) } } diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index f52cee3fab..54ad2c35d4 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -7,53 +7,84 @@ */ #[cfg(feature = "http_trigger")] -use crate::http_triggers::{build_http_trigger_extra, HttpMethod}; -#[cfg(all(feature = "enterprise", feature = "kafka"))] -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_ee::NatsTriggerConfigConnection; -#[cfg(feature = "postgres_trigger")] -use crate::postgres_triggers::{ - create_logical_replication_slot_query, create_publication_query, drop_publication_query, - generate_random_string, get_database_connection, PublicationData, +use { + crate::http_trigger_args::{HttpMethod, RawHttpTriggerArgs}, + axum::response::{IntoResponse, Response}, + std::collections::HashMap, }; -#[cfg(feature = "http_trigger")] -use http::HeaderMap; -#[cfg(feature = "postgres_trigger")] -use itertools::Itertools; -#[cfg(feature = "postgres_trigger")] -use pg_escape::quote_literal; -#[cfg(feature = "http_trigger")] + +#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] +use { + crate::gcp_triggers_oss::{ + manage_google_subscription, process_google_push_request, validate_jwt_token, + CreateUpdateConfig, SubscriptionMode, + }, + axum::extract::Request, + http::HeaderMap, +}; + +#[cfg(any( + all(feature = "enterprise", feature = "gcp_trigger"), + feature = "postgres_trigger" +))] +use windmill_common::utils::empty_as_none; + +#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))] +use windmill_common::auth::aws::AwsAuthResourceType; + +#[cfg(any( + feature = "http_trigger", + all(feature = "enterprise", feature = "gcp_trigger") +))] use serde::de::DeserializeOwned; -#[cfg(feature = "http_trigger")] -use std::collections::HashMap; -#[cfg(feature = "http_trigger")] + +#[cfg(any( + feature = "http_trigger", + feature = "postgres_trigger", + all(feature = "enterprise", feature = "gcp_trigger") +))] use windmill_common::error::Error; +#[cfg(all(feature = "enterprise", feature = "kafka"))] +use crate::kafka_triggers_oss::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; + +#[cfg(feature = "postgres_trigger")] +use crate::postgres_triggers::{ + create_logical_replication_slot, create_pg_publication, generate_random_string, + get_default_pg_connection, PublicationData, +}; + use crate::{ - args::WebhookArgs, + args::RawWebhookArgs, db::{ApiAuthed, DB}, users::fetch_api_authed, - utils::RunnableKind, }; + use axum::{ extract::{Extension, Path, Query}, routing::{delete, get, head, post}, Json, Router, }; + use hyper::StatusCode; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; use sqlx::types::Json as SqlxJson; -use std::fmt; + use windmill_common::{ db::UserDB, error::{JsonResult, Result}, - utils::{not_found_if_none, paginate, Pagination, StripPath}, + triggers::{RunnableFormat, RunnableFormatVersion, TriggerKind}, + utils::{not_found_if_none, paginate, Pagination, RunnableKind, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, }; + use windmill_queue::{PushArgs, PushArgsOwned}; const KEEP_LAST: i64 = 20; @@ -67,6 +98,10 @@ pub fn workspaced_service() -> Router { ) .route("/get_configs/:runnable_kind/*path", get(get_configs)) .route("/list/:runnable_kind/*path", get(list_captures)) + .route( + "/move/:runnable_kind/*path", + post(move_captures_and_configs), + ) .route("/:id", delete(delete_capture)) .route("/:id", get(get_capture)) } @@ -77,48 +112,28 @@ pub fn workspaced_unauthed_service() -> Router { head(|| async {}).post(webhook_payload), ); - #[cfg(feature = "http_trigger")] + #[cfg(any( + feature = "http_trigger", + all(feature = "enterprise", feature = "gcp_trigger") + ))] { - router.route("/http/:runnable_kind/:path/*route_path", { + #[cfg(feature = "http_trigger")] + let router = router.route("/http/:runnable_kind/:path/*route_path", { head(|| async {}).fallback(http_payload) - }) - } + }); + + #[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] + let router = router.route("/gcp/:runnable_kind/*path", post(gcp_payload)); - #[cfg(not(feature = "http_trigger"))] - { router } -} -#[derive(sqlx::Type, Serialize, Deserialize, Debug)] -#[sqlx(type_name = "TRIGGER_KIND", rename_all = "lowercase")] -#[serde(rename_all = "lowercase")] -pub enum TriggerKind { - Webhook, - Http, - Websocket, - Kafka, - Email, - Nats, - Mqtt, - Sqs, - Postgres, -} - -impl fmt::Display for TriggerKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - TriggerKind::Webhook => "webhook", - TriggerKind::Http => "http", - TriggerKind::Websocket => "websocket", - TriggerKind::Kafka => "kafka", - TriggerKind::Email => "email", - TriggerKind::Nats => "nats", - TriggerKind::Mqtt => "mqtt", - TriggerKind::Sqs => "sqs", - TriggerKind::Postgres => "postgres", - }; - write!(f, "{}", s) + #[cfg(not(any( + feature = "http_trigger", + all(feature = "enterprise", feature = "gcp_trigger") + )))] + { + router } } @@ -127,6 +142,8 @@ impl fmt::Display for TriggerKind { struct HttpTriggerConfig { route_path: String, http_method: HttpMethod, + raw_string: Option, + wrap_body: Option, } #[cfg(all(feature = "enterprise", feature = "kafka"))] @@ -144,6 +161,21 @@ pub struct SqsTriggerConfig { pub queue_url: String, pub aws_resource_path: String, pub message_attributes: Option>, + pub aws_auth_resource_type: AwsAuthResourceType, +} + +#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] +#[derive(Debug, Serialize, Deserialize)] +pub struct GcpTriggerConfig { + pub gcp_resource_path: String, + pub subscription_mode: SubscriptionMode, + #[serde(default, deserialize_with = "empty_as_none")] + pub subscription_id: Option, + #[serde(default, deserialize_with = "empty_as_none")] + pub base_endpoint: Option, + #[serde(flatten)] + pub create_update: Option, + pub topic_id: String, } #[cfg(all(feature = "enterprise", feature = "nats"))] @@ -173,9 +205,12 @@ 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, + #[serde(default, deserialize_with = "empty_as_none")] pub replication_slot_name: Option, pub publication: PublicationData, + pub basic_mode: Option, } #[cfg(feature = "websocket")] @@ -203,6 +238,8 @@ enum TriggerConfig { Nats(NatsTriggerConfig), #[cfg(feature = "mqtt_trigger")] Mqtt(MqttTriggerConfig), + #[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] + Gcp(GcpTriggerConfig), } #[derive(Serialize, Deserialize)] @@ -230,15 +267,26 @@ async fn get_configs( let configs = sqlx::query_as!( CaptureConfig, - r#"SELECT trigger_config as "trigger_config: _", trigger_kind as "trigger_kind: _", error, last_server_ping - FROM capture_config - WHERE workspace_id = $1 AND path = $2 AND is_flow = $3"#, + r#" + SELECT + trigger_config AS "trigger_config: _", + trigger_kind AS "trigger_kind: _", + error, + last_server_ping + FROM + capture_config + WHERE + workspace_id = $1 + AND path = $2 + AND is_flow = $3 + "#, &w_id, &path.to_path(), matches!(runnable_kind, RunnableKind::Flow), ) .fetch_all(&mut *tx) .await?; + tx.commit().await?; Ok(Json(configs)) @@ -252,87 +300,155 @@ async fn set_postgres_trigger_config( user_db: UserDB, mut capture_config: NewCaptureConfig, ) -> Result { - let Some(TriggerConfig::Postgres(mut postgres_config)) = capture_config.trigger_config else { - return Err(windmill_common::error::Error::BadRequest( - "Invalid postgres config".to_string(), - )); + use windmill_common::error::to_anyhow; + + let Some(TriggerConfig::Postgres(postgres_config)) = capture_config.trigger_config.as_mut() + else { + return Err(Error::BadRequest("Invalid postgres config".to_string())); }; - let mut connection = get_database_connection( + if postgres_config.basic_mode.unwrap_or(false) { + let mut pg_connection = get_default_pg_connection( + authed, + Some(user_db), + &db, + &postgres_config.postgres_resource_path, + &w_id, + ) + .await?; + + let tx = pg_connection.transaction().await.map_err(to_anyhow)?; + + let publication_name = format!("windmill_capture_{}", generate_random_string()); + let replication_slot_name = publication_name.clone(); + + create_logical_replication_slot(tx.client(), &replication_slot_name) + .await + .map_err(to_anyhow)?; + + create_pg_publication( + tx.client(), + &publication_name, + postgres_config.publication.table_to_track.as_deref(), + &postgres_config.publication.transaction_to_track, + ) + .await + .map_err(to_anyhow)?; + + tx.commit().await.map_err(to_anyhow)?; + + 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(), + )); + } + } + + Ok(capture_config) +} + +#[inline] +#[cfg(not(feature = "postgres_trigger"))] +async fn set_postgres_trigger_config( + _w_id: &str, + _authed: ApiAuthed, + _db: &DB, + _user_db: UserDB, + capture_config: NewCaptureConfig, +) -> Result { + Ok(capture_config) +} + +#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] +async fn set_gcp_trigger_config( + w_id: &str, + authed: ApiAuthed, + db: &DB, + mut capture_config: NewCaptureConfig, +) -> Result { + let Some(TriggerConfig::Gcp(mut gcp_config)) = capture_config.trigger_config else { + return Err(Error::BadRequest("Invalid GCP Pub/Sub config".to_string())); + }; + + let config = manage_google_subscription( authed, - Some(user_db), - &db, - &postgres_config.postgres_resource_path, - &w_id, + db, + w_id, + &gcp_config.gcp_resource_path, + &capture_config.path, + &gcp_config.topic_id, + &mut gcp_config.subscription_id, + &mut gcp_config.base_endpoint, + gcp_config.subscription_mode, + gcp_config.create_update, + false, + capture_config.is_flow, ) .await?; + gcp_config.create_update = Some(config); + gcp_config.subscription_mode = SubscriptionMode::CreateUpdate; + capture_config.trigger_config = Some(TriggerConfig::Gcp(gcp_config)); - 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()); + Ok(capture_config) +} - let query = drop_publication_query(&publication_name); - - sqlx::query(&query).execute(&mut connection).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(), - ); - - 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)); +#[inline] +#[cfg(not(all(feature = "enterprise", feature = "gcp_trigger")))] +async fn set_gcp_trigger_config( + _w_id: &str, + _authed: ApiAuthed, + _db: &DB, + capture_config: NewCaptureConfig, +) -> Result { Ok(capture_config) } async fn set_config( authed: ApiAuthed, Extension(user_db): Extension, - #[cfg(feature = "postgres_trigger")] Extension(db): Extension, + Extension(db): Extension, Path(w_id): Path, Json(nc): Json, -) -> Result<()> { - #[cfg(feature = "postgres_trigger")] - let nc = if let TriggerKind::Postgres = nc.trigger_kind { - set_postgres_trigger_config(&w_id, authed.clone(), &db, user_db.clone(), nc).await? - } else { - nc +) -> JsonResult> { + let nc = match nc.trigger_kind { + TriggerKind::Postgres => { + set_postgres_trigger_config(&w_id, authed.clone(), &db, user_db.clone(), nc).await? + } + TriggerKind::Gcp => set_gcp_trigger_config(&w_id, authed.clone(), &db, nc).await?, + _ => nc, }; let mut tx = user_db.begin(&authed).await?; sqlx::query!( - "INSERT INTO capture_config - (workspace_id, path, is_flow, trigger_kind, trigger_config, owner, email) - VALUES ($1, $2, $3, $4, $5, $6, $7) + r#" + INSERT INTO capture_config ( + workspace_id, path, is_flow, trigger_kind, trigger_config, owner, email + ) + VALUES ( + $1, $2, $3, $4, $5, $6, $7 + ) ON CONFLICT (workspace_id, path, is_flow, trigger_kind) - DO UPDATE SET trigger_config = $5, owner = $6, email = $7, server_id = NULL, error = NULL", + DO UPDATE + SET + trigger_config = $5, + owner = $6, + email = $7, + server_id = NULL, + error = NULL + "#, &w_id, &nc.path, nc.is_flow, nc.trigger_kind as TriggerKind, - nc.trigger_config.map(|x| SqlxJson(to_raw_value(&x))) as Option>>, + nc.trigger_config + .as_ref() + .map(|x| SqlxJson(to_raw_value(&x))) as Option>>, &authed.username, &authed.email, ) @@ -341,7 +457,7 @@ async fn set_config( tx.commit().await?; - Ok(()) + Ok(Json(nc.trigger_config)) } async fn ping_config( @@ -355,8 +471,19 @@ async fn ping_config( )>, ) -> Result<()> { let mut tx = user_db.begin(&authed).await?; + sqlx::query!( - "UPDATE capture_config SET last_client_ping = now() WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4", + r#" + UPDATE + capture_config + SET + last_client_ping = NOW() + WHERE + workspace_id = $1 + AND path = $2 + AND is_flow = $3 + AND trigger_kind = $4 + "#, &w_id, &path.to_path(), matches!(runnable_kind, RunnableKind::Flow), @@ -364,6 +491,7 @@ async fn ping_config( ) .execute(&mut *tx) .await?; + tx.commit().await?; Ok(()) } @@ -373,8 +501,8 @@ struct Capture { id: i64, created_at: chrono::DateTime, trigger_kind: TriggerKind, - payload: SqlxJson>, - trigger_extra: Option>>, + main_args: SqlxJson>, + preprocessor_args: Option>>, } #[derive(Deserialize)] @@ -396,14 +524,31 @@ async fn list_captures( let captures = sqlx::query_as!( Capture, - r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '"WINDMILL_TOO_BIG"'::jsonb END as "payload!: _", trigger_extra as "trigger_extra: _" - FROM capture - WHERE workspace_id = $1 - AND path = $2 AND is_flow = $3 + r#" + SELECT + id, + created_at, + trigger_kind AS "trigger_kind: _", + CASE + WHEN pg_column_size(main_args) < 40000 THEN main_args + ELSE '"WINDMILL_TOO_BIG"'::jsonb + END AS "main_args!: _", + CASE + WHEN pg_column_size(preprocessor_args) < 40000 THEN preprocessor_args + ELSE '"WINDMILL_TOO_BIG"'::jsonb + END AS "preprocessor_args: _" + FROM + capture + WHERE + workspace_id = $1 + AND path = $2 + AND is_flow = $3 AND ($4::trigger_kind IS NULL OR trigger_kind = $4) - ORDER BY created_at DESC + ORDER BY + created_at DESC OFFSET $5 - LIMIT $6"#, + LIMIT $6 + "#, &w_id, &path.to_path(), matches!(runnable_kind, RunnableKind::Flow), @@ -425,14 +570,28 @@ async fn get_capture( Path((w_id, id)): Path<(String, i64)>, ) -> JsonResult { let mut tx = user_db.begin(&authed).await?; + let capture = sqlx::query_as!( Capture, - r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE id = $1 AND workspace_id = $2"#, + r#" + SELECT + id, + created_at, + trigger_kind AS "trigger_kind: _", + main_args AS "main_args!: _", + preprocessor_args AS "preprocessor_args: _" + FROM + capture + WHERE + id = $1 + AND workspace_id = $2 + "#, id, &w_id, ) .fetch_one(&mut *tx) - .await?; + .await?; + tx.commit().await?; Ok(Json(capture)) } @@ -443,9 +602,73 @@ async fn delete_capture( Path((_, id)): Path<(String, i64)>, ) -> Result<()> { let mut tx = user_db.begin(&authed).await?; - sqlx::query!("DELETE FROM capture WHERE id = $1", id) - .execute(&mut *tx) - .await?; + sqlx::query!( + r#" + DELETE FROM + capture + WHERE + id = $1 + "#, + id + ) + .execute(&mut *tx) + .await?; + tx.commit().await?; + Ok(()) +} + +#[derive(Deserialize)] +struct MoveCapturesAndConfigsBody { + new_path: String, +} + +async fn move_captures_and_configs( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, runnable_kind, old_path)): Path<(String, RunnableKind, StripPath)>, + Json(body): Json, +) -> Result<()> { + let mut tx = user_db.begin(&authed).await?; + let old_path = old_path.to_path(); + + sqlx::query!( + r#" + UPDATE + capture_config + SET + path = $1 + WHERE + path = $2 + AND workspace_id = $3 + AND is_flow = $4 + "#, + body.new_path, + old_path, + &w_id, + matches!(runnable_kind, RunnableKind::Flow), + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + r#" + UPDATE + capture + SET + path = $1 + WHERE + path = $2 + AND workspace_id = $3 + AND is_flow = $4 + "#, + body.new_path, + old_path, + &w_id, + matches!(runnable_kind, RunnableKind::Flow), + ) + .execute(&mut *tx) + .await?; + tx.commit().await?; Ok(()) } @@ -465,9 +688,19 @@ pub async fn get_active_capture_owner_and_email( ) -> Result<(String, String)> { let capture_config = sqlx::query_as!( ActiveCaptureOwner, - "SELECT owner, email - FROM capture_config - WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4 AND last_client_ping > NOW() - INTERVAL '10 seconds'", + r#" + SELECT + owner, + email + FROM + capture_config + WHERE + workspace_id = $1 + AND path = $2 + AND is_flow = $3 + AND trigger_kind = $4 + AND last_client_ping > NOW() - INTERVAL '10 seconds' + "#, &w_id, &path, is_flow, @@ -485,7 +718,10 @@ pub async fn get_active_capture_owner_and_email( Ok((capture_config.owner, capture_config.email)) } -#[cfg(feature = "http_trigger")] +#[cfg(any( + feature = "http_trigger", + all(feature = "enterprise", feature = "gcp_trigger") +))] async fn get_capture_trigger_config_and_owner( db: &DB, w_id: &str, @@ -499,16 +735,34 @@ async fn get_capture_trigger_config_and_owner( owner: String, email: String, } - let capture_config = sqlx::query_as!( CaptureTriggerConfigAndOwner, - r#"SELECT trigger_config as "trigger_config: _", owner, email - FROM capture_config - WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND trigger_kind = $4 AND last_client_ping > NOW() - INTERVAL '10 seconds'"#, + r#" + SELECT + trigger_config AS "trigger_config: _", + owner, + email + FROM + capture_config + WHERE + workspace_id = $1 + AND path = $2 + AND is_flow = $3 + AND trigger_kind = $4 + AND last_client_ping > NOW() - INTERVAL '10 seconds' + AND ( + $5::bool IS FALSE + OR ( + trigger_config IS NOT NULL + AND trigger_config ->> 'delivery_type' = 'push' + ) + ) + "#, &w_id, &path, is_flow, kind as &TriggerKind, + matches!(kind, TriggerKind::Gcp) ) .fetch_optional(db) .await?; @@ -541,17 +795,24 @@ async fn clear_captures_history(db: &DB, w_id: &str) -> Result<()> { if *CLOUD_HOSTED { /* Retain only KEEP_LAST most recent captures in this workspace. */ sqlx::query!( - "DELETE FROM capture - WHERE workspace_id = $1 - AND created_at <= - ( - SELECT created_at - FROM capture - WHERE workspace_id = $1 - ORDER BY created_at DESC - OFFSET $2 - LIMIT 1 - )", + r#" + DELETE FROM + capture + WHERE + workspace_id = $1 + AND created_at <= ( + SELECT + created_at + FROM + capture + WHERE + workspace_id = $1 + ORDER BY + created_at DESC + OFFSET $2 + LIMIT 1 + ) + "#, &w_id, KEEP_LAST, ) @@ -567,22 +828,27 @@ pub async fn insert_capture_payload( path: &str, is_flow: bool, trigger_kind: &TriggerKind, - payload: PushArgsOwned, - trigger_extra: Option>, + main_args: PushArgsOwned, + preprocessor_args: PushArgsOwned, owner: &str, ) -> Result<()> { sqlx::query!( - "INSERT INTO capture (workspace_id, path, is_flow, trigger_kind, payload, trigger_extra, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7)", + r#" + INSERT INTO + capture ( + workspace_id, path, is_flow, trigger_kind, main_args, preprocessor_args, created_by + ) + VALUES ( + $1, $2, $3, $4, $5, $6, $7 + ) + "#, &w_id, path, is_flow, trigger_kind as &TriggerKind, - SqlxJson(to_raw_value(&PushArgs { - args: &payload.args, - extra: payload.extra - })) as SqlxJson>, - trigger_extra.map(SqlxJson) as Option>>, + SqlxJson(PushArgs { args: &main_args.args, extra: main_args.extra }) as SqlxJson, + SqlxJson(PushArgs { args: &preprocessor_args.args, extra: preprocessor_args.extra }) + as SqlxJson, owner, ) .execute(db) @@ -596,7 +862,7 @@ pub async fn insert_capture_payload( async fn webhook_payload( Extension(db): Extension, Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, StripPath)>, - args: WebhookArgs, + args: RawWebhookArgs, ) -> Result { let (owner, email) = get_active_capture_owner_and_email( &db, @@ -608,7 +874,15 @@ async fn webhook_payload( .await?; let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None).await?; - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + + let args = args.process_args(&authed, &db, &w_id, None).await?; + + let preprocessor_args = args.clone().to_args_from_format(RunnableFormat { + has_preprocessor: true, + version: RunnableFormatVersion::V2, + })?; + + let main_args = args.to_main_args()?; insert_capture_payload( &db, @@ -616,12 +890,58 @@ async fn webhook_payload( &path.to_path(), matches!(runnable_kind, RunnableKind::Flow), &TriggerKind::Webhook, - args, - Some(to_raw_value(&serde_json::json!({ - "wm_trigger": { - "kind": "webhook", - } - }))), + main_args, + preprocessor_args, + &owner, + ) + .await?; + + Ok(StatusCode::NO_CONTENT) +} + +#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] +async fn gcp_payload( + Extension(db): Extension, + Extension(user_db): Extension, + Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, String)>, + headers: HeaderMap, + request: Request, +) -> Result { + use crate::{gcp_triggers_oss::GcpTrigger, trigger_helpers::TriggerJobArgs}; + + let is_flow = matches!(runnable_kind, RunnableKind::Flow); + let (gcp_trigger_config, owner, email): (GcpTriggerConfig, _, _) = + get_capture_trigger_config_and_owner(&db, &w_id, &path, is_flow, &TriggerKind::Gcp).await?; + + let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None).await?; + + let Some(config) = &gcp_trigger_config.create_update else { + return Err(Error::BadConfig("Bad config".to_string())); + }; + + validate_jwt_token( + &db, + user_db.clone(), + authed.clone(), + &headers, + &gcp_trigger_config.gcp_resource_path, + &w_id, + config.delivery_config.as_ref().unwrap(), + ) + .await?; + + let (payload, gcp) = process_google_push_request(headers, request).await?; + + let (main_args, preprocessor_args) = GcpTrigger::build_capture_payloads(payload, gcp); + + let _ = insert_capture_payload( + &db, + &w_id, + &path, + is_flow, + &TriggerKind::Gcp, + main_args, + preprocessor_args, &owner, ) .await?; @@ -632,33 +952,37 @@ async fn webhook_payload( #[cfg(feature = "http_trigger")] async fn http_payload( Extension(db): Extension, - Path((w_id, kind, path, route_path)): Path<(String, RunnableKind, String, StripPath)>, - Query(query): Query>, - method: http::Method, - headers: HeaderMap, - args: WebhookArgs, -) -> Result { - let route_path = route_path.to_path(); + Path((w_id, runnable_kind, path, route_path)): Path<(String, RunnableKind, String, StripPath)>, + args: RawHttpTriggerArgs, +) -> std::result::Result { let path = path.replace(".", "/"); - + let is_flow = matches!(runnable_kind, RunnableKind::Flow); + let route_path = route_path.to_path(); let (http_trigger_config, owner, email): (HttpTriggerConfig, _, _) = - get_capture_trigger_config_and_owner( + get_capture_trigger_config_and_owner(&db, &w_id, &path, is_flow, &TriggerKind::Http) + .await + .map_err(|e| e.into_response())?; + + let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None) + .await + .map_err(|e| e.into_response())?; + + let args = args + .process_args( + &authed, &db, &w_id, - &path, - matches!(kind, RunnableKind::Flow), - &TriggerKind::Http, + http_trigger_config.raw_string.unwrap_or(false), ) - .await?; - - let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None).await?; - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + .await + .map_err(|e| e.into_response())?; let mut router = matchit::Router::new(); router.insert(&http_trigger_config.route_path, ()).ok(); let match_ = router.at(route_path).ok(); - let match_ = not_found_if_none(match_, "capture http trigger", &route_path)?; + let match_ = not_found_if_none(match_, "capture http trigger", &route_path) + .map_err(|e| e.into_response())?; let matchit::Match { params, .. } = match_; @@ -667,30 +991,27 @@ async fn http_payload( .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); - let extra: HashMap> = HashMap::from_iter(vec![( - "wm_trigger".to_string(), - build_http_trigger_extra( - &http_trigger_config.route_path, - route_path, - &method, - ¶ms, - &query, - &headers, - ) - .await, - )]); + let preprocessor_args = args + .clone() + .to_v2_preprocessor_args(&http_trigger_config.route_path, &route_path, ¶ms) + .map_err(|e| e.into_response())?; + + let main_args = args + .to_main_args(http_trigger_config.wrap_body.unwrap_or(false)) + .map_err(|e| e.into_response())?; insert_capture_payload( &db, &w_id, &path, - matches!(kind, RunnableKind::Flow), + is_flow, &TriggerKind::Http, - args, - Some(to_raw_value(&extra)), + main_args, + preprocessor_args, &owner, ) - .await?; + .await + .map_err(|e| e.into_response())?; Ok(StatusCode::NO_CONTENT) } diff --git a/backend/windmill-api/src/concurrency_groups.rs b/backend/windmill-api/src/concurrency_groups.rs index 3f7e41617f..4135c3729c 100644 --- a/backend/windmill-api/src/concurrency_groups.rs +++ b/backend/windmill-api/src/concurrency_groups.rs @@ -199,6 +199,7 @@ async fn get_concurrent_intervals( result: None, tag: None, has_null_parent: None, + worker: None, label: None, scheduled_for_before_now: _, is_not_schedule: _, @@ -214,6 +215,7 @@ async fn get_concurrent_intervals( is_flow_step: _, all_workspaces: _, concurrency_key: Some(_), + allow_wildcards: None, } => true, _ => false, }; diff --git a/backend/windmill-api/src/configs.rs b/backend/windmill-api/src/configs.rs index 81b65216e0..606cd11ebe 100644 --- a/backend/windmill-api/src/configs.rs +++ b/backend/windmill-api/src/configs.rs @@ -14,14 +14,14 @@ use axum::{ use serde::{Deserialize, Serialize}; use sqlx::FromRow; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::{ error::{self}, DB, }; -use crate::{db::ApiAuthed, utils::require_super_admin}; +use crate::{db::ApiAuthed, utils::{require_devops_role}}; pub fn global_service() -> Router { Router::new() @@ -33,6 +33,10 @@ 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)] @@ -99,7 +103,7 @@ async fn get_config( Path(name): Path, Extension(db): Extension, ) -> error::JsonResult> { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let config = sqlx::query_as!(Config, "SELECT * FROM config WHERE name = $1", name) .fetch_optional(&db) @@ -115,7 +119,7 @@ async fn update_config( authed: ApiAuthed, Json(config): Json, ) -> error::Result { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; #[cfg(not(feature = "enterprise"))] if name.starts_with("worker__") { @@ -153,7 +157,7 @@ async fn delete_config( Extension(db): Extension, authed: ApiAuthed, ) -> error::Result { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let mut tx = db.begin().await?; @@ -205,12 +209,30 @@ async fn list_autoscaling_events( Ok(Json(events)) } +async fn list_available_python_versions() -> error::JsonResult> { + #[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, Extension(db): Extension, ) -> error::JsonResult> { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let configs = sqlx::query_as!(Config, "SELECT name, config FROM config") .fetch_all(&db) .await?; diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index 1ecef03fc5..6e3343494d 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -16,7 +16,7 @@ use sqlx::{ }; use tokio::task::JoinHandle; -use windmill_audit::audit_ee::{AuditAuthor, AuditAuthorable}; +use windmill_audit::audit_oss::{AuditAuthor, AuditAuthorable}; use windmill_common::{ db::{Authable, Authed}, error::Error, @@ -48,6 +48,9 @@ lazy_static::lazy_static! { (20250102145420, include_str!( "../../migrations/20250102145420_more_captures.up.sql" ).replace("public.", "")), + (20250429211554, include_str!( + "../../migrations/20250429211554_create_indices_on_queue.up.sql" + ).replace("public.", "")), (20241006144414, include_str!( "../../custom_migrations/grant_all_current_schema.sql" ).to_string()), @@ -231,7 +234,7 @@ pub async fn migrate(db: &DB) -> Result>, Error> { { Ok(_) => Ok(()), Err(sqlx::migrate::MigrateError::VersionMissing(e)) => { - tracing::error!("Database had been applied more migrations than this container. + tracing::error!("Database had been applied more migrations than this container. This usually mean than another container on a more recent version migrated the database and this one is on an earlier version. Please update the container to latest. Not critical, but may cause issues if migration introduced a breaking change. Version missing: {e:#}"); custom_migrator.unlock().await?; @@ -433,7 +436,7 @@ async fn v2_finalize(db: &DB) -> Result<(), Error> { r#" LOCK TABLE v2_job_completed IN ACCESS EXCLUSIVE MODE; DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; - DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; + DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; "#, ) .await?; @@ -445,7 +448,7 @@ async fn v2_finalize(db: &DB) -> Result<(), Error> { LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; - DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; + DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; "#, ) .await?; @@ -456,7 +459,7 @@ async fn v2_finalize(db: &DB) -> Result<(), Error> { r#" LOCK TABLE v2_job_runtime IN ACCESS EXCLUSIVE MODE; DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; - DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; + DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; "#, ) .await?; @@ -467,7 +470,7 @@ async fn v2_finalize(db: &DB) -> Result<(), Error> { r#" LOCK TABLE v2_job_status IN ACCESS EXCLUSIVE MODE; DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; - DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; + DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; "#, ) .await?; @@ -779,10 +782,50 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { .execute(db) .await?; }); + + run_windmill_migration!("job_completed_completed_at", db, |tx| { + sqlx::query!( + "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_job_completed_completed_at ON v2_job_completed (completed_at DESC)" + ) + .execute(db) + .await?; + }); + + run_windmill_migration!("alerts_by_workspace", db, |tx| { + sqlx::query!( + "CREATE INDEX CONCURRENTLY IF NOT EXISTS alerts_by_workspace ON alerts (workspace_id);" + ) + .execute(db) + .await?; + }); + + run_windmill_migration!("remove_redundant_log_file_index", db, |tx| { + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS log_file_hostname_log_ts_idx") + .execute(db) + .await?; + }); + + run_windmill_migration!("v2_job_queue_suspend", db, |tx| { + sqlx::query!( + "CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;" + ) + .execute(db) + .await?; + }); + + run_windmill_migration!("audit_recent_login_activities", db, |tx| { + sqlx::query!( + "CREATE INDEX CONCURRENTLY idx_audit_recent_login_activities +ON audit (timestamp, username) +WHERE operation IN ('users.login', 'oauth.login', 'users.token.refresh');" + ) + .execute(db) + .await?; + }); Ok(()) } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] pub struct ApiAuthed { pub email: String, pub username: String, diff --git a/backend/windmill-api/src/drafts.rs b/backend/windmill-api/src/drafts.rs index 6ba1f4452f..02d3b3c4fb 100644 --- a/backend/windmill-api/src/drafts.rs +++ b/backend/windmill-api/src/drafts.rs @@ -1,6 +1,6 @@ /* * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2042 + * Copyright: Windmill Labs, Inc 2024 * This file and its contents are licensed under the AGPLv3 License. * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee_oss.rs similarity index 61% rename from backend/windmill-api/src/ee.rs rename to backend/windmill-api/src/ee_oss.rs index cddb639e95..3fb1271a73 100644 --- a/backend/windmill-api/src/ee.rs +++ b/backend/windmill-api/src/ee_oss.rs @@ -1,15 +1,21 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::ee::*; + +#[cfg(not(feature = "private"))] use anyhow::anyhow; -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] use std::sync::Arc; -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] 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(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn jwt_ext_auth( _w_id: Option<&String>, _token: &str, @@ -20,10 +26,10 @@ pub async fn jwt_ext_auth( Err(anyhow!("External JWT auth is not open source")) } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub struct ExternalJwks; -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] impl ExternalJwks { pub async fn load() -> Option>> { // Implementation is not open source diff --git a/backend/windmill-api/src/embeddings.rs b/backend/windmill-api/src/embeddings.rs index 30415156ce..9b3d21844a 100644 --- a/backend/windmill-api/src/embeddings.rs +++ b/backend/windmill-api/src/embeddings.rs @@ -24,7 +24,7 @@ use candle_nn::VarBuilder; #[cfg(feature = "embedding")] use candle_transformers::models::bert::{BertModel, Config, DTYPE}; #[cfg(feature = "embedding")] -use hf_hub::{api::sync::Api, Cache, Repo}; +use hf_hub::api::tokio::Api; #[cfg(feature = "embedding")] use serde::Deserialize; #[cfg(feature = "embedding")] @@ -158,63 +158,22 @@ pub struct ModelInstance { #[cfg(feature = "embedding")] impl ModelInstance { pub async fn load_model_files() -> Result<(PathBuf, PathBuf, PathBuf)> { - let repo = Repo::model("thenlper/gte-small".to_string()); - - let cache = Cache::default().repo(repo.clone()); - let api = Api::new()?; - let api = api.repo(repo); + let repo_api = api.model("thenlper/gte-small".to_string()); - let (config_filename, tokenizer_filename, weights_filename) = ( - cache - .get("config.json") - .or_else(|| { - api.get("config.json") - .or_else(|e| { - tracing::error!("Failed to get config.json from hugging face: {}", e); - return Err(e); - }) - .ok() - }) - .ok_or(Error::msg("could not get config.json"))?, - cache - .get("tokenizer.json") - .or_else(|| { - api.get("tokenizer.json") - .or_else(|e| { - tracing::error!( - "Failed to get tokenizer.json from hugging face: {}", - e - ); - return Err(e); - }) - .ok() - }) - .ok_or(Error::msg("could not get tokenizer.json"))?, - cache - .get("model.safetensors") - .and_then(|p| { - tracing::info!("Found embedding model in cache"); - Some(p) - }) - .or_else(|| { - tracing::info!("Downloading embedding model..."); - api.get("model.safetensors") - .or_else(|e| { - tracing::error!( - "Failed to get model.safetensors from hugging face: {}", - e - ); - return Err(e); - }) - .ok() - .and_then(|p| { - tracing::info!("Downloaded embedding model"); - Some(p) - }) - }) - .ok_or(Error::msg("could not get model.safetensors"))?, - ); + let (config_filename, tokenizer_filename, weights_filename) = + ( + repo_api + .get("config.json") + .await + .map_err(|e| anyhow!("Failed to get config.json from hugging face: {}", e))?, + repo_api.get("tokenizer.json").await.map_err(|e| { + anyhow!("Failed to get tokenizer.json from hugging face: {}", e) + })?, + repo_api.get("model.safetensors").await.map_err(|e| { + anyhow!("Failed to get model.safetensors from hugging face: {}", e) + })?, + ); Ok((config_filename, tokenizer_filename, weights_filename)) } @@ -253,7 +212,7 @@ impl ModelInstance { let token_ids = Tensor::new(&tokens[..], &Device::Cpu)?.unsqueeze(0)?; let token_type_ids = token_ids.zeros_like()?; - let embedding = self.model.forward(&token_ids, &token_type_ids)?; + let embedding = self.model.forward(&token_ids, &token_type_ids, None)?; let embedding = (embedding.sum(1)? / embedding.dim(1)? as f64)?; let embedding = normalize_l2(&embedding)?; diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 8cd65dd55a..af0c8cef7a 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -12,7 +12,7 @@ use crate::db::ApiAuthed; use crate::triggers::{ get_triggers_count_internal, list_tokens_internal, TriggersCount, TruncatedTokenWithEmail, }; -use crate::utils::{RunnableKind, WithStarredInfoQuery}; +use crate::utils::WithStarredInfoQuery; use crate::{ db::DB, schedule::clear_schedule, @@ -31,10 +31,10 @@ use hyper::StatusCode; use serde::{Deserialize, Serialize}; use sql_builder::prelude::*; use sqlx::{FromRow, Postgres, Transaction}; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::utils::query_elems_from_hub; -use windmill_common::worker::to_raw_value; +use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; use windmill_common::HUB_BASE_URL; use windmill_common::{ db::UserDB, @@ -43,7 +43,7 @@ use windmill_common::{ jobs::JobPayload, schedule::Schedule, scripts::Schema, - utils::{http_get_from_hub, not_found_if_none, paginate, Pagination, StripPath}, + utils::{http_get_from_hub, not_found_if_none, paginate, Pagination, RunnableKind, StripPath}, }; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; use windmill_queue::{push, schedule::push_scheduled_job, PushIsolationLevel}; @@ -336,10 +336,10 @@ async fn list_paths_from_workspace_runnable( let mut tx = user_db.begin(&authed).await?; let runnables = sqlx::query_scalar!( r#"SELECT f.path - FROM flow_workspace_runnables fwr - JOIN flow f - ON fwr.flow_path = f.path AND fwr.workspace_id = f.workspace_id - WHERE fwr.runnable_path = $1 AND fwr.runnable_is_flow = $2 AND fwr.workspace_id = $3"#, + FROM workspace_runnable_dependencies wru + JOIN flow f + ON wru.flow_path = f.path AND wru.workspace_id = f.workspace_id + WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3"#, path.to_path(), matches!(runnable_kind, RunnableKind::Flow), w_id @@ -358,6 +358,32 @@ async fn create_flow( Path(w_id): Path, Json(nf): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_flows = + sqlx::query_scalar!("SELECT COUNT(*) FROM flow WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_flows.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of flows (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if nf.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + if nf + .description + .as_ref() + .is_some_and(|desc| desc.len() > 3000) + { + return Err(Error::BadRequest( + "Description must be less than 3000 characters on cloud".to_string(), + )); + } + } #[cfg(not(feature = "enterprise"))] if nf .value @@ -477,7 +503,7 @@ async fn create_flow( false, None, true, - nf.tag, + None, None, None, None, @@ -1183,12 +1209,18 @@ async fn archive_flow_by_path( Ok(format!("Flow {path} archived")) } +#[derive(Deserialize)] +struct DeleteFlowQuery { + keep_captures: Option, +} + async fn delete_flow_by_path( authed: ApiAuthed, Extension(db): Extension, Extension(user_db): Extension, Extension(webhook): Extension, Path((w_id, path)): Path<(String, StripPath)>, + Query(query): Query, ) -> Result { let path = path.to_path(); let mut tx = user_db.begin(&authed).await?; @@ -1209,21 +1241,23 @@ async fn delete_flow_by_path( .execute(&mut *tx) .await?; - sqlx::query!( - "DELETE FROM capture_config WHERE path = $1 AND workspace_id = $2 AND is_flow IS TRUE", - path, - &w_id - ) - .execute(&mut *tx) - .await?; + if !query.keep_captures.unwrap_or(false) { + sqlx::query!( + "DELETE FROM capture_config WHERE path = $1 AND workspace_id = $2 AND is_flow IS TRUE", + path, + &w_id + ) + .execute(&mut *tx) + .await?; - sqlx::query!( - "DELETE FROM capture WHERE path = $1 AND workspace_id = $2 AND is_flow IS TRUE", - path, - &w_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!( + "DELETE FROM capture WHERE path = $1 AND workspace_id = $2 AND is_flow IS TRUE", + path, + &w_id + ) + .execute(&mut *tx) + .await?; + } audit_log( &mut *tx, @@ -1337,7 +1371,7 @@ mod tests { }), stop_after_if: Some(StopAfterIf { expr: "foo = 'bar'".to_string(), - skip_if_stopped: false, + ..Default::default() }), stop_after_all_iters_if: None, summary: None, @@ -1366,7 +1400,7 @@ mod tests { }), stop_after_if: Some(StopAfterIf { expr: "previous.isEmpty()".to_string(), - skip_if_stopped: false, + ..Default::default() }), stop_after_all_iters_if: None, summary: None, @@ -1394,7 +1428,7 @@ mod tests { .into(), stop_after_if: Some(StopAfterIf { expr: "previous.isEmpty()".to_string(), - skip_if_stopped: false, + ..Default::default() }), stop_after_all_iters_if: None, summary: None, @@ -1444,7 +1478,8 @@ mod tests { }, "stop_after_if": { "expr": "foo = 'bar'", - "skip_if_stopped": false + "skip_if_stopped": false, + "error_message": null } }, { @@ -1466,6 +1501,7 @@ mod tests { "stop_after_if": { "expr": "previous.isEmpty()", "skip_if_stopped": false, + "error_message": null } } ], @@ -1478,7 +1514,8 @@ mod tests { }, "stop_after_if": { "expr": "previous.isEmpty()", - "skip_if_stopped": false + "skip_if_stopped": false, + "error_message": null } }, }); diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs index 5047a96cad..1e4d2e09c0 100644 --- a/backend/windmill-api/src/folders.rs +++ b/backend/windmill-api/src/folders.rs @@ -23,7 +23,7 @@ use axum::{ }; use lazy_static::lazy_static; use regex::Regex; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::{ db::UserDB, diff --git a/backend/windmill-api/src/gcp_triggers_oss.rs b/backend/windmill-api/src/gcp_triggers_oss.rs new file mode 100644 index 0000000000..78005309bc --- /dev/null +++ b/backend/windmill-api/src/gcp_triggers_oss.rs @@ -0,0 +1,167 @@ +#[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, + }, +}; + +#[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 + } +} + +#[derive(FromRow, Deserialize, Serialize, Debug)] +#[allow(unused)] +#[cfg(not(feature = "private"))] +pub struct PushConfig { + #[serde(deserialize_with = "empty_as_none")] + route_path: Option, + #[serde(deserialize_with = "empty_as_none")] + audience: Option, + authenticate: bool, + base_endpoint: String, +} +#[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")] + pub subscription_id: Option, + pub delivery_config: Option>, +} + +#[derive(Debug, Deserialize, Serialize)] +#[cfg(not(feature = "private"))] +pub struct ExistingGcpSubscription { + pub subscription_id: String, + pub base_endpoint: String, +} + +#[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<()>, +) -> () { + // implementation is not open source +} + +#[cfg(not(feature = "private"))] +pub async fn manage_google_subscription( + _authed: ApiAuthed, + _db: &DB, + _workspace_id: &str, + _gcp_resource_path: &str, + _path: &str, + _topic_id: &str, + _subscription_id: &mut Option, + _base_endpoint: &mut Option, + _subscription_mode: SubscriptionMode, + _create_update_config: Option, + _trigger_mode: bool, + _is_flow: bool, +) -> WindmillResult { + Ok(CreateUpdateConfig::default()) +} + +#[cfg(not(feature = "private"))] +pub async fn process_google_push_request( + _headers: HeaderMap, + _request: Request, +) -> Result<(String, HashMap>), WindmillError> { + Ok((String::new(), HashMap::new())) +} + +#[cfg(not(feature = "private"))] +pub async fn validate_jwt_token( + _db: &DB, + _user_db: UserDB, + _authed: ApiAuthed, + _headers: &HeaderMap, + _gcp_resource_path: &str, + _workspace_id: &str, + _delivery_config: &PushConfig, +) -> Result<(), windmill_common::error::Error> { + 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, + pub delivery_type: DeliveryType, + pub delivery_config: Option>, + pub subscription_mode: SubscriptionMode, + pub topic_id: String, + pub path: String, + pub script_path: String, + pub is_flow: bool, + pub workspace_id: String, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + pub extra_perms: Option, + pub error: Option, + pub server_id: Option, + pub last_server_ping: Option>, + pub enabled: bool, +} +#[cfg(not(feature = "private"))] +impl TriggerJobArgs for GcpTrigger { + fn v1_payload_fn(payload: String) -> HashMap> { + HashMap::from([("payload".to_string(), to_raw_value(&payload))]) + } + + fn trigger_kind() -> TriggerKind { + TriggerKind::Gcp + } +} diff --git a/backend/windmill-api/src/git_sync_oss.rs b/backend/windmill-api/src/git_sync_oss.rs new file mode 100644 index 0000000000..0451d88699 --- /dev/null +++ b/backend/windmill-api/src/git_sync_oss.rs @@ -0,0 +1,16 @@ +#[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() +} diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index e25d3ee7da..d5da9c25f5 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -14,7 +14,7 @@ use axum::{ routing::{delete, get, post}, Json, Router, }; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::worker::CLOUD_HOSTED; use windmill_common::{ diff --git a/backend/windmill-api/src/http_trigger_args.rs b/backend/windmill-api/src/http_trigger_args.rs new file mode 100644 index 0000000000..8fb96d42f0 --- /dev/null +++ b/backend/windmill-api/src/http_trigger_args.rs @@ -0,0 +1,210 @@ +use std::collections::HashMap; + +use axum::{ + extract::{FromRequest, Request}, + response::Response, +}; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; +use windmill_common::{ + error::Error, + triggers::{RunnableFormat, RunnableFormatVersion}, + worker::to_raw_value, + DB, +}; +use windmill_queue::PushArgsOwned; + +use crate::{ + args::{try_from_request_body, Body, RawWebhookArgs, WebhookArgs, WebhookArgsMetadata}, + db::ApiAuthed, +}; + +pub struct RawHttpTriggerArgs(pub RawWebhookArgs); + +#[derive(Serialize, Deserialize, sqlx::Type, Debug, Clone, Copy, Hash, Eq, PartialEq)] +#[sqlx(type_name = "HTTP_METHOD", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum HttpMethod { + Get, + Post, + Put, + Delete, + Patch, +} + +impl TryFrom<&http::Method> for HttpMethod { + type Error = Error; + fn try_from(method: &http::Method) -> Result { + match method { + &http::Method::GET => Ok(HttpMethod::Get), + &http::Method::POST => Ok(HttpMethod::Post), + &http::Method::PUT => Ok(HttpMethod::Put), + &http::Method::DELETE => Ok(HttpMethod::Delete), + &http::Method::PATCH => Ok(HttpMethod::Patch), + _ => Err(Error::BadRequest("Invalid HTTP method".to_string())), + } + } +} + +#[axum::async_trait] +impl FromRequest for RawHttpTriggerArgs +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(request: Request, _state: &S) -> Result { + let args = try_from_request_body(request, _state, true).await?; + + Ok(Self(args)) + } +} + +#[derive(Debug, Clone)] +pub struct HttpTriggerArgs(pub WebhookArgs); + +impl RawHttpTriggerArgs { + pub async fn process_args( + self, + authed: &ApiAuthed, + db: &DB, + w_id: &str, + use_raw: bool, + ) -> Result { + if self.0.metadata.query_use_raw || self.0.metadata.query_wrap_body { + return Err(Error::BadRequest( + "Specifying use raw or wrap body with query args is not supported anymore on http routes, please set it in the trigger config".to_string(), + ) + .into()); + } + + let args = self.0.process_args(authed, db, w_id, Some(use_raw)).await?; + + Ok(HttpTriggerArgs(args)) + } +} + +#[derive(Serialize)] +struct HttpTriggerPreprocessorEvent<'a> { + kind: String, + route: &'a str, + path: &'a str, + body: Box, + raw_string: Option, + params: &'a HashMap, + headers: HashMap>, + query: HashMap>, + method: HttpMethod, +} + +#[derive(Serialize)] +struct HttpTriggerWmTrigger<'a> { + route: &'a str, + path: &'a str, + params: &'a HashMap, + query: &'a HashMap>, + headers: &'a HashMap>, + method: HttpMethod, +} + +impl HttpTriggerArgs { + pub fn to_main_args(self, wrap_body: bool) -> Result { + let mut extra = HashMap::new(); + + let WebhookArgsMetadata { raw_string, .. } = self.0.metadata; + + if let Some(raw_string) = raw_string { + extra.insert("raw_string".to_string(), to_raw_value(&raw_string)); + } + + let extra = if extra.is_empty() { None } else { Some(extra) }; + + match self.0.body { + Body::HashMap(mut body) => { + if wrap_body { + body = HashMap::from([("body".to_string(), to_raw_value(&body))]); + } + Ok(PushArgsOwned { args: body, extra }) + } + Body::NoHashMap(args) => { + let mut hm = HashMap::new(); + hm.insert("body".to_string(), args); + Ok(PushArgsOwned { args: hm, extra }) + } + } + } + + pub fn to_args_from_format( + self, + route_path: &str, + called_path: &str, + params: &HashMap, + format: RunnableFormat, + wrap_body: bool, + ) -> Result { + match format { + RunnableFormat { has_preprocessor: true, version: RunnableFormatVersion::V2 } => { + // we don't care about wrap_body in v2 + self.to_v2_preprocessor_args(route_path, called_path, params) + } + RunnableFormat { has_preprocessor: true, version: RunnableFormatVersion::V1 } => { + self.to_v1_preprocessor_args(route_path, called_path, params, wrap_body) + } + RunnableFormat { has_preprocessor: false, .. } => self.to_main_args(wrap_body), + } + } + + fn to_v1_preprocessor_args( + self, + route_path: &str, + called_path: &str, + params: &HashMap, + wrap_body: bool, + ) -> Result { + let mut extra = HashMap::new(); + let mut wm_trigger = HashMap::new(); + wm_trigger.insert("kind".to_string(), to_raw_value(&"http".to_string())); + wm_trigger.insert( + "http".to_string(), + to_raw_value(&HttpTriggerWmTrigger { + route: route_path, + path: called_path, + method: (&self.0.metadata.method).try_into()?, + params, + query: &self.0.metadata.query, + headers: &self.0.metadata.headers, + }), + ); + extra.insert("wm_trigger".to_string(), to_raw_value(&wm_trigger)); + + let mut args = self.to_main_args(wrap_body)?; + + args.extra.get_or_insert_default().extend(extra); + + Ok(args) + } + + pub fn to_v2_preprocessor_args( + self, + route_path: &str, + called_path: &str, + params: &HashMap, + ) -> Result { + let mut args = HashMap::new(); + args.insert( + "event".to_string(), + to_raw_value(&HttpTriggerPreprocessorEvent { + kind: "http".to_string(), + body: to_raw_value(&self.0.body), + raw_string: self.0.metadata.raw_string, + headers: self.0.metadata.headers, + query: self.0.metadata.query, + method: (&self.0.metadata.method).try_into()?, + route: route_path, + path: called_path, + params, + }), + ); + Ok(PushArgsOwned { args, extra: None }) + } +} diff --git a/backend/windmill-api/src/http_trigger_auth.rs b/backend/windmill-api/src/http_trigger_auth.rs new file mode 100644 index 0000000000..85288d87b8 --- /dev/null +++ b/backend/windmill-api/src/http_trigger_auth.rs @@ -0,0 +1,751 @@ +use axum::response::{IntoResponse, Response}; +use base64::{ + prelude::{BASE64_STANDARD, BASE64_URL_SAFE}, + Engine, +}; +use hmac::{Hmac, Mac}; +use http::{header, HeaderMap, HeaderValue, StatusCode}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use sha1::Sha1; +use sha2::{Sha256, Sha512}; +use std::{borrow::Cow, collections::HashMap}; + +pub type HmacSha256 = Hmac; +pub type HmacSha512 = Hmac; +pub type HmacSha1 = Hmac; + +mod github { + use super::*; + pub struct Github; + + impl WebhookHandler for Github { + fn handle_challenge_request<'header>( + &self, + _: &'header HeaderMap, + _: &SignatureConfigData, + _: &str, + ) -> Result, AuthenticationError> { + Ok(None) + } + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let github_secret_header = headers.try_get_webhook_header("X-Hub-Signature-256")?; + + let authentication_data = SignatureAuthenticationData::new( + Cow::Borrowed(raw_payload), + github_secret_header, + Some("sha256="), + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + ); + + Ok(authentication_data) + } + } +} + +mod slack { + use super::*; + pub struct Slack; + + impl WebhookHandler for Slack { + fn handle_challenge_request<'header>( + &self, + _: &'header HeaderMap, + _: &SignatureConfigData, + _: &str, + ) -> Result, AuthenticationError> { + Ok(None) + } + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let slack_secret_signature = headers.try_get_webhook_header("X-Slack-Signature")?; + let slack_timestamp_header = + headers.try_get_webhook_header("X-Slack-Request-Timestamp")?; + let signed_payload = format!("v0:{}:{}", slack_timestamp_header, raw_payload); + + Ok(SignatureAuthenticationData::new( + Cow::Owned(signed_payload), + slack_secret_signature, + Some("v0="), + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + )) + } + } +} + +mod stripe { + use super::*; + + pub struct Stripe; + + impl WebhookHandler for Stripe { + fn handle_challenge_request<'header>( + &self, + _: &'header HeaderMap, + _: &SignatureConfigData, + _: &str, + ) -> Result, AuthenticationError> { + Ok(None) + } + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let stripe_signature_header = headers.try_get_webhook_header("STRIPE-SIGNATURE")?; + + let stripe_signature = parse_signature(stripe_signature_header, (",", "=")); + + let timestamp = *stripe_signature + .get("t") + .ok_or(AuthenticationError::InvalidTimestamp)?; + let v1 = *stripe_signature + .get("v1") + .ok_or(AuthenticationError::InvalidSignature)?; + + let signed_payload = format!("{}.{}", timestamp, raw_payload); + + Ok(SignatureAuthenticationData::new( + Cow::Owned(signed_payload), + v1, + None, + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + )) + } + } +} + +mod tiktok { + use super::*; + + pub struct TikTok; + + impl WebhookHandler for TikTok { + fn handle_challenge_request<'header>( + &self, + _: &'header HeaderMap, + _: &SignatureConfigData, + _: &str, + ) -> Result, AuthenticationError> { + Ok(None) + } + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let tiktok_secret_signature = headers.try_get_webhook_header("TikTok-Signature")?; + + let stripe_signature = parse_signature(tiktok_secret_signature, (",", "=")); + + let timestamp = *stripe_signature + .get("t") + .ok_or(AuthenticationError::InvalidTimestamp)?; + let s = *stripe_signature + .get("s") + .ok_or(AuthenticationError::InvalidSignature)?; + + let signed_payload = format!("{}.{}", timestamp, raw_payload); + + Ok(SignatureAuthenticationData::new( + Cow::Owned(signed_payload), + s, + None, + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + )) + } + } +} + +mod twitch { + use super::*; + use http::header; + use serde_json::value::RawValue; + #[derive(Debug, Deserialize)] + struct TwitchCrcBody { + challenge: String, + #[allow(unused)] + subscription: Box, + } + + pub struct Twitch; + + impl WebhookHandler for Twitch { + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let twitch_secret_signature = + headers.try_get_webhook_header("Twitch-Eventsub-Message-Signature")?; + let twitch_message_id_header = + headers.try_get_webhook_header("Twitch-Eventsub-Message-Id")?; + let twitch_timestamp_header = + headers.try_get_webhook_header("Twitch-Eventsub-Message-Timestamp")?; + + let message = format!( + "{}{}{}", + twitch_message_id_header, twitch_timestamp_header, raw_payload + ); + + Ok(SignatureAuthenticationData::new( + Cow::Owned(message), + twitch_secret_signature, + Some("sha256="), + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + )) + } + + fn handle_challenge_request<'header>( + &self, + headers: &'header HeaderMap, + signature_config_data: &SignatureConfigData, + raw_payload: &str, + ) -> Result, AuthenticationError> { + let authentication_data = self.get_hmac_authentication_data(headers, raw_payload)?; + verify_hmac_signature(authentication_data, &signature_config_data.secret_key)?; + + let twitch_eventsub_message_type = + headers.try_get_webhook_header("Twitch-Eventsub-Message-Type")?; + + if twitch_eventsub_message_type != "webhook_callback_verification" { + return Ok(None); + } + let twitch_crc_body = + serde_json::from_str::(raw_payload).map_err(|e| { + AuthenticationError::InvalidChallengeResponse(format!( + "Twitch :{}", + e.to_string() + )) + })?; + + let response = ( + StatusCode::OK, + [(header::CONTENT_TYPE, "text/plain")], + twitch_crc_body.challenge.to_string(), + ); + + Ok(Some(response.into_response())) + } + } +} + +mod zoom { + use axum::Json; + + use super::*; + + #[derive(Debug, Deserialize)] + struct ZoomPayload { + #[serde(rename = "plainToken")] + plain_token: String, + } + + #[derive(Debug, Deserialize)] + #[allow(unused)] + struct ZoomChallengeResponse { + payload: ZoomPayload, + event_ts: u64, + event: String, + } + + pub struct Zoom; + + impl WebhookHandler for Zoom { + fn handle_challenge_request<'header>( + &self, + _: &'header HeaderMap, + signature_config_data: &SignatureConfigData, + raw_payload: &str, + ) -> Result, AuthenticationError> { + let Ok(zoom_request_body) = serde_json::from_str::(raw_payload) + else { + return Ok(None); + }; + + if zoom_request_body.event != "endpoint.url_validation" { + return Ok(None); + } + + let hmac_signature = calculate_hmac_signature( + HmacAlgorithm::Sha256, + &signature_config_data.secret_key, + &zoom_request_body.payload.plain_token, + ); + + let encoded_hmac_signature = encode_hmac_signature(Encoding::Hex, &hmac_signature); + + let response = ( + StatusCode::OK, + Json(json!({ + "plainToken": zoom_request_body.payload.plain_token, + "encryptedToken": encoded_hmac_signature + })), + ); + + Ok(Some(response.into_response())) + } + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError> + { + let zoom_signature_header = headers.try_get_webhook_header("x-zm-signature")?; + let zoom_timestamp_header = headers.try_get_webhook_header("x-zm-request-timestamp")?; + + let message = format!("v0:{}:{}", zoom_timestamp_header, raw_payload); + + Ok(SignatureAuthenticationData::new( + Cow::Owned(message), + zoom_signature_header, + Some("v0="), + SignatureAuthenticationDetails::new(HmacAlgorithm::Sha256, Encoding::Hex), + )) + } + } +} + +use constant_time_eq::constant_time_eq; +use github::Github; +use slack::Slack; +use stripe::Stripe; +use tiktok::TikTok; +use twitch::Twitch; +use zoom::Zoom; + +#[derive(Debug)] +pub struct SignatureAuthenticationDetails { + pub algorithm_to_use: HmacAlgorithm, + pub header_key_encoding: Encoding, +} + +impl SignatureAuthenticationDetails { + #[inline] + fn new(algorithm_to_use: HmacAlgorithm, header_key_encoding: Encoding) -> Self { + Self { algorithm_to_use, header_key_encoding } + } +} + +fn parse_signature<'header>( + signature: &'header str, + splitters: (&str, &str), +) -> HashMap<&'header str, &'header str> { + let headers: HashMap<&str, &str> = signature + .split(splitters.0) + .map(|header| { + let mut key_and_value = header.split(splitters.1); + let key = key_and_value.next(); + let value = key_and_value.next(); + (key, value) + }) + .filter_map(|(key, value)| match (key, value) { + (Some(key), Some(value)) => Some((key, value)), + _ => None, + }) + .collect(); + headers +} + +#[derive(Debug)] +pub struct SignatureAuthenticationData<'payload, 'header, 'prefix> { + pub signed_payload: Cow<'payload, str>, + pub header_key_value: &'header str, + pub signature_prefix: Option<&'prefix str>, + pub config: SignatureAuthenticationDetails, +} + +impl<'payload, 'header, 'prefix> SignatureAuthenticationData<'payload, 'header, 'prefix> { + pub fn new( + signed_payload: Cow<'payload, str>, + header_key_value: &'header str, + signature_prefix: Option<&'prefix str>, + config: SignatureAuthenticationDetails, + ) -> Self { + Self { signed_payload, header_key_value, signature_prefix, config } + } +} + +pub trait WebhookHandler { + fn handle_challenge_request<'header>( + &self, + headers: &'header HeaderMap, + signature_config_data: &SignatureConfigData, + raw_payload: &str, + ) -> Result, AuthenticationError>; + + fn get_hmac_authentication_data<'payload, 'header, 'prefix>( + &self, + headers: &'header HeaderMap, + raw_payload: &'payload str, + ) -> Result, AuthenticationError>; +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HmacAlgorithm { + Sha1, + Sha256, + Sha512, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Encoding { + Base64, + Base64Uri, + Hex, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SignatureAuthenticationMethod { + algorithm: HmacAlgorithm, + encoding: Encoding, + signature_header_name: String, + signature_prefix: Option, +} + +pub struct SignatureConfigData<'config> { + secret_key: &'config str, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SignatureAuthentication { + signature_provider: WebhookType, + secret_key: String, + authentication_config: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BasicAuthAuthentication { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ApiKeyAuthentication { + pub api_key_header: String, + pub api_key_secret: String, +} + +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] +#[non_exhaustive] +pub enum WebhookType { + Github, + Slack, + Stripe, + TikTok, + Twitch, + Zoom, + Custom, +} + +impl WebhookType { + pub fn get_webhook_handler(&self) -> Option<&'static dyn WebhookHandler> { + let handler: &'static dyn WebhookHandler = match *self { + WebhookType::Github => &Github, + WebhookType::Slack => &Slack, + WebhookType::Stripe => &Stripe, + WebhookType::TikTok => &TikTok, + WebhookType::Twitch => &Twitch, + WebhookType::Zoom => &Zoom, + WebhookType::Custom => return None, + }; + Some(handler) + } +} + +trait TryGetWebhookHeader { + fn try_get_webhook_header<'header>( + &'header self, + header_name: &str, + ) -> Result<&'header str, AuthenticationError>; +} + +impl TryGetWebhookHeader for HeaderMap { + fn try_get_webhook_header<'header>( + &'header self, + header_name: &str, + ) -> Result<&'header str, AuthenticationError> { + let Some(signature_header) = self.get(header_name) else { + return Err(AuthenticationError::MissingHeader(header_name.to_string())); + }; + let Some(signature_header) = signature_header.to_str().ok() else { + return Err(AuthenticationError::InvalidHeader(header_name.to_string())); + }; + + Ok(signature_header) + } +} + +pub fn calculate_hmac_signature(algorithm: HmacAlgorithm, secret: &str, payload: &str) -> Vec { + match algorithm { + HmacAlgorithm::Sha1 => { + let mut mac = + HmacSha1::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size"); + mac.update(payload.as_bytes()); + mac.finalize().into_bytes().to_vec() + } + HmacAlgorithm::Sha256 => { + let mut mac = HmacSha256::new_from_slice(secret.as_bytes()) + .expect("HMAC can take key of any size"); + mac.update(payload.as_bytes()); + mac.finalize().into_bytes().to_vec() + } + HmacAlgorithm::Sha512 => { + let mut mac = HmacSha512::new_from_slice(secret.as_bytes()) + .expect("HMAC can take key of any size"); + mac.update(payload.as_bytes()); + mac.finalize().into_bytes().to_vec() + } + } +} + +pub fn encode_hmac_signature(encoding: Encoding, hmac_signature: &[u8]) -> String { + match encoding { + Encoding::Hex => hex::encode(hmac_signature), + Encoding::Base64 => BASE64_STANDARD.encode(hmac_signature), + Encoding::Base64Uri => BASE64_URL_SAFE.encode(hmac_signature), + } +} + +pub fn verify_hmac_signature( + authentication_data: SignatureAuthenticationData, + webhook_signing_secret: &str, +) -> Result<(), AuthenticationError> { + let hmac_signature = calculate_hmac_signature( + authentication_data.config.algorithm_to_use, + &webhook_signing_secret, + &authentication_data.signed_payload, + ); + + let encoded_signature = encode_hmac_signature( + authentication_data.config.header_key_encoding, + &hmac_signature, + ); + + let final_expected_signature = + if let Some(signature_prefix) = authentication_data.signature_prefix { + format!("{}{}", signature_prefix, encoded_signature) + } else { + encoded_signature + }; + + if !constant_time_eq( + final_expected_signature.as_bytes(), + authentication_data.header_key_value.as_bytes(), + ) { + return Err(AuthenticationError::InvalidSignature); + } + + Ok(()) +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum AuthenticationMethod { + Signature(SignatureAuthentication), + BasicAuth(BasicAuthAuthentication), + ApiKey(ApiKeyAuthentication), +} + +impl AuthenticationMethod { + pub fn authenticate_http_request( + &self, + headers: &HeaderMap, + raw_payload: Option<&String>, + ) -> Result, AuthenticationError> { + match self { + AuthenticationMethod::Signature(SignatureAuthentication { + secret_key, + authentication_config, + signature_provider, + }) => { + let raw_payload = raw_payload.ok_or(AuthenticationError::InvalidPayload)?; + let config_data = SignatureConfigData { secret_key: &secret_key }; + let handler = signature_provider.get_webhook_handler(); + let challenge_response = handler + .map(|handler| { + handler.handle_challenge_request(headers, &config_data, raw_payload) + }) + .transpose()? + .flatten(); + + if let Some(challenge_response) = challenge_response { + return Ok(Some(challenge_response)); + } + + let authentication_data = match handler { + Some(handler) => handler.get_hmac_authentication_data(headers, raw_payload)?, + None => { + let authentication_config = authentication_config + .as_ref() + .ok_or(AuthenticationError::InvalidCustomConfig)?; + let signature_header_value = headers + .try_get_webhook_header(&authentication_config.signature_header_name)?; + SignatureAuthenticationData::new( + Cow::Borrowed(raw_payload), + signature_header_value, + authentication_config.signature_prefix.as_deref(), + SignatureAuthenticationDetails::new( + authentication_config.algorithm, + authentication_config.encoding, + ), + ) + } + }; + + verify_hmac_signature(authentication_data, &secret_key)?; + } + AuthenticationMethod::ApiKey(ApiKeyAuthentication { + api_key_header, + api_key_secret, + }) => { + let api_key_to_cmp = headers + .try_get_webhook_header(&api_key_header) + .map_err(|_| AuthenticationError::InvalidApiKey)?; + if api_key_to_cmp != api_key_secret { + return Err(AuthenticationError::InvalidApiKey); + } + } + AuthenticationMethod::BasicAuth(BasicAuthAuthentication { username, password }) => { + let mut credentials_store = headers + .try_get_webhook_header("Authorization") + .map_err(|_| AuthenticationError::UnauthorizedBasicHttpAuth)? + .split(' '); + + let _ = credentials_store + .next() + .filter(|r#type| *r#type == "Basic") + .ok_or(AuthenticationError::UnauthorizedBasicHttpAuth)?; + + let credentials_as_base64 = credentials_store + .next() + .ok_or(AuthenticationError::UnauthorizedBasicHttpAuth)?; + + let credentials_from_base64_as_bytes = BASE64_STANDARD + .decode(credentials_as_base64.as_bytes()) + .map_err(|_| AuthenticationError::UnauthorizedBasicHttpAuth)?; + + let credentials_separated_with_colon = + String::from_utf8(credentials_from_base64_as_bytes) + .map_err(|_| AuthenticationError::UnauthorizedBasicHttpAuth)?; + + let credentials = credentials_separated_with_colon.split(':').collect_vec(); + + if credentials.len() != 2 { + return Err(AuthenticationError::UnauthorizedBasicHttpAuth); + } + + if credentials.get(0).unwrap() != username + || credentials.get(1).unwrap() != password + { + return Err(AuthenticationError::UnauthorizedBasicHttpAuth); + } + } + } + + Ok(None) + } +} + +#[derive(thiserror::Error, Debug)] +#[allow(unused)] +pub enum AuthenticationError { + #[error("failed to parse timestamp")] + InvalidTimestamp, + + #[error("invalid secret")] + InvalidSecret(#[from] base64::DecodeError), + + #[error("invalid header `{0}`")] + InvalidHeader(String), + + #[error("signature timestamp too old")] + TimestampTooOldError, + + #[error("signature timestamp too far in future")] + FutureTimestampError, + + #[error("missing header {0}")] + MissingHeader(String), + + #[error("signature invalid")] + InvalidSignature, + + #[error("payload invalid")] + InvalidPayload, + + #[error("invalid custom config")] + InvalidCustomConfig, + + #[error("invalid auth header: {0}")] + InvalidAuthHeader(String), + + #[error("invalid api key")] + InvalidApiKey, + + #[error("invalid challenge response: {0}")] + InvalidChallengeResponse(String), + + #[error("")] + UnauthorizedBasicHttpAuth, +} + +impl IntoResponse for AuthenticationError { + fn into_response(self) -> Response { + let (status, error_message) = match &self { + AuthenticationError::InvalidTimestamp + | AuthenticationError::InvalidPayload + | AuthenticationError::InvalidHeader(_) + | AuthenticationError::MissingHeader(_) + | AuthenticationError::TimestampTooOldError + | AuthenticationError::FutureTimestampError + | AuthenticationError::InvalidCustomConfig + | AuthenticationError::InvalidChallengeResponse(_) => { + (StatusCode::BAD_REQUEST, self.to_string()) + } + + AuthenticationError::InvalidSecret(_) + | AuthenticationError::InvalidSignature + | AuthenticationError::InvalidAuthHeader(_) => { + (StatusCode::UNAUTHORIZED, self.to_string()) + } + AuthenticationError::UnauthorizedBasicHttpAuth => { + return ( + StatusCode::UNAUTHORIZED, + [(header::WWW_AUTHENTICATE, r#"Basic realm="Restricted Area""#)], + "Unauthorized", + ) + .into_response() + } + AuthenticationError::InvalidApiKey => { + return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response() + } + }; + + let body = json!({ "error": error_message }); + + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", HeaderValue::from_static("application/json")); + + (status, headers, body.to_string()).into_response() + } +} diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 722a639842..a42bdcac53 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -1,7 +1,11 @@ +#[cfg(feature = "http_trigger")] +use crate::http_trigger_args::{HttpMethod, RawHttpTriggerArgs}; #[cfg(feature = "parquet")] -use crate::job_helpers_ee::get_workspace_s3_resource; +use crate::job_helpers_oss::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}; use crate::{ - args::WebhookArgs, auth::{AuthCache, OptTokened}, db::{ApiAuthed, DB}, jobs::{ @@ -10,6 +14,8 @@ use crate::{ }, users::fetch_api_authed, }; +use anyhow::anyhow; +use axum::response::Response; use axum::{ extract::{Path, Query}, response::IntoResponse, @@ -19,21 +25,29 @@ use axum::{ #[cfg(feature = "parquet")] use http::header::IF_NONE_MATCH; use http::{HeaderMap, StatusCode}; +use quick_cache::sync::Cache; use serde::{Deserialize, Serialize}; use sql_builder::{bind::Bind, SqlBuilder}; use sqlx::prelude::FromRow; +use sqlx::PgConnection; +use std::borrow::Cow; +use std::collections::HashSet; use std::{collections::HashMap, sync::Arc}; +use tokio::sync::{RwLock, RwLockReadGuard}; use tower_http::cors::CorsLayer; -use windmill_audit::{audit_ee::audit_log, ActionKind}; +use windmill_audit::{audit_oss::audit_log, ActionKind}; +use windmill_common::error::{Error, Result as WindmillResult}; #[cfg(feature = "parquet")] use windmill_common::s3_helpers::build_object_store_client; use windmill_common::{ db::UserDB, error::{self, JsonResult}, s3_helpers::S3Object, - utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, - worker::{to_raw_value, CLOUD_HOSTED}, + triggers::TriggerKind, + utils::{empty_as_none, not_found_if_none, paginate, require_admin, Pagination, StripPath}, + worker::CLOUD_HOSTED, }; +use windmill_git_sync::handle_deployment_metadata; lazy_static::lazy_static! { static ref ROUTE_PATH_KEY_RE: regex::Regex = regex::Regex::new(r"/?:[-\w]+").unwrap(); @@ -67,6 +81,7 @@ pub fn routes_global_service() -> Router { pub fn workspaced_service() -> Router { Router::new() .route("/create", post(create_trigger)) + .route("/create_many", post(create_many_http_trigger)) .route("/list", get(list_triggers)) .route("/get/*path", get(get_trigger)) .route("/update/*path", post(update_trigger)) @@ -75,42 +90,35 @@ pub fn workspaced_service() -> Router { .route("/route_exists", post(exists_route)) } -#[derive(Serialize, Deserialize, sqlx::Type, Debug)] -#[sqlx(type_name = "HTTP_METHOD", rename_all = "lowercase")] -#[serde(rename_all = "lowercase")] -pub enum HttpMethod { - Get, - Post, - Put, - Delete, - Patch, +#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[sqlx(type_name = "AUTHENTICATION_METHOD", rename_all = "snake_case")] +#[serde(rename_all(serialize = "snake_case", deserialize = "snake_case"))] +pub enum AuthenticationMethod { + None, + Windmill, + ApiKey, + BasicHttp, + CustomScript, + Signature, } -impl TryFrom<&http::Method> for HttpMethod { - type Error = error::Error; - fn try_from(method: &http::Method) -> Result { - match method { - &http::Method::GET => Ok(HttpMethod::Get), - &http::Method::POST => Ok(HttpMethod::Post), - &http::Method::PUT => Ok(HttpMethod::Put), - &http::Method::DELETE => Ok(HttpMethod::Delete), - &http::Method::PATCH => Ok(HttpMethod::Patch), - _ => Err(error::Error::BadRequest("Invalid HTTP method".to_string())), - } - } -} - -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct NewTrigger { path: String, route_path: String, script_path: String, is_flow: bool, is_async: bool, - requires_auth: bool, - http_method: HttpMethod, + authentication_resource_path: Option, + authentication_method: AuthenticationMethod, static_asset_config: Option>, + http_method: HttpMethod, + workspaced_route: Option, + summary: Option, + description: Option, is_static_website: bool, + wrap_body: Option, + raw_string: Option, } #[derive(FromRow, Serialize)] @@ -126,11 +134,17 @@ pub struct HttpTrigger { pub edited_at: chrono::DateTime, pub extra_perms: serde_json::Value, pub is_async: bool, - pub requires_auth: bool, + pub authentication_method: AuthenticationMethod, pub http_method: HttpMethod, + pub summary: Option, + pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub static_asset_config: Option>, pub is_static_website: bool, + pub authentication_resource_path: Option, + pub workspaced_route: bool, + pub wrap_body: bool, + pub raw_string: bool, } #[derive(Deserialize)] @@ -140,10 +154,17 @@ struct EditTrigger { script_path: String, is_flow: bool, is_async: bool, - requires_auth: bool, + authentication_method: AuthenticationMethod, + #[serde(deserialize_with = "non_empty_str")] + authentication_resource_path: Option, + summary: Option, + description: Option, http_method: HttpMethod, static_asset_config: Option>, + workspaced_route: Option, is_static_website: bool, + wrap_body: Option, + raw_string: Option, } #[derive(Deserialize)] @@ -152,6 +173,7 @@ pub struct ListTriggerQuery { pub per_page: Option, pub path: Option, pub is_flow: Option, + #[serde(default, deserialize_with = "empty_as_none")] pub path_start: Option, } @@ -164,7 +186,29 @@ async fn list_triggers( let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); let mut sqlb = SqlBuilder::select_from("http_trigger") - .field("*") + .fields(&[ + "workspace_id", + "path", + "route_path", + "route_path_key", + "workspaced_route", + "wrap_body", + "raw_string", + "script_path", + "summary", + "description", + "is_flow", + "http_method", + "edited_by", + "email", + "edited_at", + "extra_perms", + "is_async", + "authentication_method", + "static_asset_config", + "is_static_website", + "authentication_resource_path", + ]) .order_by("edited_at", true) .and_where("workspace_id = ?".bind(&w_id)) .offset(offset) @@ -199,9 +243,35 @@ async fn get_trigger( let path = path.to_path(); let trigger = sqlx::query_as!( HttpTrigger, - r#"SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as "http_method: _", edited_by, email, edited_at, extra_perms, is_async, requires_auth, static_asset_config as "static_asset_config: _", is_static_website - FROM http_trigger - WHERE workspace_id = $1 AND path = $2"#, + r#" + SELECT + workspace_id, + path, + route_path, + route_path_key, + workspaced_route, + script_path, + summary, + description, + is_flow, + http_method as "http_method: _", + edited_by, + email, + edited_at, + extra_perms, + is_async, + authentication_method as "authentication_method: _", + static_asset_config as "static_asset_config: _", + is_static_website, + authentication_resource_path, + wrap_body, + raw_string + FROM + http_trigger + WHERE + workspace_id = $1 AND + path = $2 + "#, w_id, path, ) @@ -214,70 +284,266 @@ async fn get_trigger( Ok(Json(trigger)) } -async fn create_trigger( +fn validate_authentication_method( + authentication_method: AuthenticationMethod, + raw_string: Option, +) -> WindmillResult<()> { + match (authentication_method, raw_string) { + (AuthenticationMethod::CustomScript, raw) if !raw.unwrap_or(false) == true => { + return Err(Error::BadRequest( + "To use custom script authentication, please enable the raw body option." + .to_string(), + )); + } + _ => {} + } + + Ok(()) +} + +async fn increase_trigger_version(tx: &mut PgConnection) -> WindmillResult<()> { + sqlx::query!("SELECT nextval('http_trigger_version_seq')",) + .fetch_one(tx) + .await?; + + Ok(()) +} + +async fn create_trigger_inner( + tx: &mut PgConnection, + w_id: &str, + authed: &ApiAuthed, + new_http_trigger: &NewTrigger, + route_path_key: &str, +) -> WindmillResult<()> { + sqlx::query!( + r#" + INSERT INTO http_trigger ( + workspace_id, + path, + route_path, + route_path_key, + workspaced_route, + authentication_resource_path, + wrap_body, + raw_string, + script_path, + summary, + description, + is_flow, + is_async, + authentication_method, + http_method, + static_asset_config, + edited_by, + email, + edited_at, + is_static_website + ) + VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19 + ) + "#, + w_id, + new_http_trigger.path, + new_http_trigger.route_path, + &route_path_key, + new_http_trigger.workspaced_route, + new_http_trigger.authentication_resource_path, + new_http_trigger.wrap_body.unwrap_or(false), + new_http_trigger.raw_string.unwrap_or(false), + new_http_trigger.script_path, + new_http_trigger.summary, + new_http_trigger.description, + new_http_trigger.is_flow, + new_http_trigger.is_async, + new_http_trigger.authentication_method as _, + new_http_trigger.http_method as _, + new_http_trigger.static_asset_config as _, + &authed.username, + &authed.email, + new_http_trigger.is_static_website + ) + .execute(&mut *tx) + .await?; + + audit_log( + &mut *tx, + authed, + "http_triggers.create", + ActionKind::Create, + &w_id, + Some(new_http_trigger.path.as_str()), + None, + ) + .await?; + + increase_trigger_version(tx).await?; + + Ok(()) +} + +fn check_no_duplicates<'trigger>( + new_http_triggers: &[NewTrigger], + route_path_key: &[Cow<'trigger, str>], +) -> Result<(), Error> { + let mut seen = HashSet::with_capacity(new_http_triggers.len()); + + for (i, trigger) in new_http_triggers.iter().enumerate() { + if !seen.insert(( + &route_path_key[i], + trigger.http_method, + trigger.workspaced_route, + )) { + return Err(Error::BadRequest(format!( + "Duplicate HTTP route detected: '{}'. Each HTTP route must have a unique 'route_path'.", + &trigger.route_path + ))); + } + } + + Ok(()) +} + +async fn create_many_http_trigger( authed: ApiAuthed, Extension(db): Extension, Extension(user_db): Extension, Path(w_id): Path, - Json(ct): Json, -) -> error::Result<(StatusCode, String)> { + Json(new_http_triggers): Json>, +) -> WindmillResult<(StatusCode, String)> { require_admin(authed.is_admin, &authed.username)?; - if !VALID_ROUTE_PATH_RE.is_match(&ct.route_path) { + let error_wrapper = |path: &str, error: Error| -> Error { + anyhow!( + "Error occurred for HTTP route at route path: {}, error: {}", + path, + error + ) + .into() + }; + + let mut route_path_keys = Vec::with_capacity(new_http_triggers.len()); + + for new_http_trigger in new_http_triggers.iter() { + let route_path_key = validate_http_trigger(&db, &w_id, new_http_trigger) + .await + .map_err(|err| error_wrapper(&new_http_trigger.route_path, err))?; + + route_path_keys.push(route_path_key); + } + + check_no_duplicates(&new_http_triggers, &route_path_keys)?; + + let mut tx = user_db.begin(&authed).await?; + + for (i, new_http_trigger) in new_http_triggers.iter().enumerate() { + create_trigger_inner( + &mut tx, + &w_id, + &authed, + new_http_trigger, + &route_path_keys[i], + ) + .await + .map_err(|err| error_wrapper(&new_http_trigger.route_path, err))?; + } + + tx.commit().await?; + + for http_trigger in new_http_triggers.into_iter() { + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::HttpTrigger { path: http_trigger.path.clone() }, + Some(format!("HTTP route '{}' created", http_trigger.path)), + true, + ) + .await?; + } + + Ok((StatusCode::CREATED, format!("Created all HTTP routes"))) +} + +async fn validate_http_trigger<'trigger>( + db: &DB, + w_id: &str, + new_http_trigger: &'trigger NewTrigger, +) -> WindmillResult> { + if !VALID_ROUTE_PATH_RE.is_match(&new_http_trigger.route_path) { return Err(error::Error::BadRequest("Invalid route path".to_string())); } + validate_authentication_method( + new_http_trigger.authentication_method, + new_http_trigger.raw_string, + )?; + // route path key is extracted from the route path to check for uniqueness // it replaces /?:{key} with :key // it will also remove the leading / if present, not an issue as we only allow : after slashes - let route_path_key = ROUTE_PATH_KEY_RE.replace_all(ct.route_path.as_str(), ":key"); + let route_path_key = ROUTE_PATH_KEY_RE.replace_all(&new_http_trigger.route_path, ":key"); + + let exists = route_path_key_exists( + &route_path_key, + &new_http_trigger.http_method, + &w_id, + None, + new_http_trigger.workspaced_route, + db, + ) + .await?; - let exists = route_path_key_exists(&route_path_key, &ct.http_method, &w_id, None, &db).await?; if exists { return Err(error::Error::BadRequest( "A route already exists with this path".to_string(), )); } - if *CLOUD_HOSTED && (ct.is_static_website || ct.static_asset_config.is_some()) { + if *CLOUD_HOSTED + && (new_http_trigger.is_static_website || new_http_trigger.static_asset_config.is_some()) + { return Err(error::Error::BadRequest( "Static website and static asset are not supported on cloud".to_string(), )); } - let mut tx = user_db.begin(&authed).await?; - sqlx::query!( - "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, static_asset_config, edited_by, email, edited_at, is_static_website) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), $13)", - w_id, - ct.path, - ct.route_path, - &route_path_key, - ct.script_path, - ct.is_flow, - ct.is_async, - ct.requires_auth, - ct.http_method as _, - ct.static_asset_config as _, - &authed.username, - &authed.email, - ct.is_static_website, - ) - .execute(&mut *tx).await?; + Ok(route_path_key) +} - audit_log( - &mut *tx, - &authed, - "http_triggers.create", - ActionKind::Create, - &w_id, - Some(ct.path.as_str()), - None, - ) - .await?; +async fn create_trigger( + authed: ApiAuthed, + Extension(db): Extension, + Extension(user_db): Extension, + Path(w_id): Path, + Json(new_http_trigger): Json, +) -> WindmillResult<(StatusCode, String)> { + require_admin(authed.is_admin, &authed.username)?; + + let route_path_key = validate_http_trigger(&db, &w_id, &new_http_trigger).await?; + + let mut tx = user_db.begin(&authed).await?; + + let http_trigger_path = new_http_trigger.path.clone(); + + create_trigger_inner(&mut tx, &w_id, &authed, &new_http_trigger, &route_path_key).await?; tx.commit().await?; - Ok((StatusCode::CREATED, format!("{}", ct.path))) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::HttpTrigger { path: new_http_trigger.path.clone() }, + Some(format!("HTTP route '{}' created", new_http_trigger.path)), + true, + ) + .await?; + + Ok((StatusCode::CREATED, format!("{}", http_trigger_path))) } async fn update_trigger( @@ -286,7 +552,7 @@ async fn update_trigger( Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(ct): Json, -) -> error::Result { +) -> WindmillResult { let path = path.to_path(); if *CLOUD_HOSTED && (ct.is_static_website || ct.static_asset_config.is_some()) { @@ -295,6 +561,8 @@ async fn update_trigger( )); } + validate_authentication_method(ct.authentication_method, ct.raw_string)?; + let mut tx; if authed.is_admin { let Some(route_path) = ct.route_path else { @@ -309,9 +577,15 @@ async fn update_trigger( let route_path_key = ROUTE_PATH_KEY_RE.replace_all(&route_path, ":key"); - let exists = - route_path_key_exists(&route_path_key, &ct.http_method, &w_id, Some(&path), &db) - .await?; + let exists = route_path_key_exists( + &route_path_key, + &ct.http_method, + &w_id, + Some(&path), + ct.workspaced_route, + &db, + ) + .await?; if exists { return Err(error::Error::BadRequest( "A route already exists with this path".to_string(), @@ -319,13 +593,40 @@ async fn update_trigger( } tx = user_db.begin(&authed).await?; - sqlx::query!( - "UPDATE http_trigger - SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, static_asset_config = $7, edited_by = $8, email = $9, is_async = $10, requires_auth = $11, edited_at = now(), is_static_website = $12 - WHERE workspace_id = $13 AND path = $14", + r#" + UPDATE + http_trigger + SET + route_path = $1, + route_path_key = $2, + workspaced_route = $3, + wrap_body = $4, + raw_string = $5, + authentication_resource_path = $6, + script_path = $7, + path = $8, + is_flow = $9, + http_method = $10, + static_asset_config = $11, + edited_by = $12, + email = $13, + is_async = $14, + authentication_method = $15, + summary = $16, + description = $17, + edited_at = now(), + is_static_website = $18 + WHERE + workspace_id = $19 AND + path = $20 + "#, route_path, &route_path_key, + ct.workspaced_route, + ct.wrap_body, + ct.raw_string, + ct.authentication_resource_path, ct.script_path, ct.path, ct.is_flow, @@ -334,17 +635,45 @@ async fn update_trigger( &authed.username, &authed.email, ct.is_async, - ct.requires_auth, + ct.authentication_method as _, + ct.summary, + ct.description, ct.is_static_website, w_id, path, ) - .execute(&mut *tx).await?; + .execute(&mut *tx) + .await?; } else { tx = user_db.begin(&authed).await?; sqlx::query!( - "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, static_asset_config = $5, edited_by = $6, email = $7, is_async = $8, requires_auth = $9, edited_at = now(), is_static_website = $10 - WHERE workspace_id = $11 AND path = $12", + r#" + UPDATE + http_trigger + SET + workspaced_route = $1, + wrap_body = $2, + raw_string = $3, + authentication_resource_path = $4, + script_path = $5, + path = $6, + is_flow = $7, + http_method = $8, + static_asset_config = $9, + edited_by = $10, + email = $11, + is_async = $12, + authentication_method = $13, + edited_at = now(), + is_static_website = $14 + WHERE + workspace_id = $15 AND + path = $16 + "#, + ct.workspaced_route, + ct.wrap_body, + ct.raw_string, + ct.authentication_resource_path, ct.script_path, ct.path, ct.is_flow, @@ -353,42 +682,59 @@ async fn update_trigger( &authed.username, &authed.email, ct.is_async, - ct.requires_auth, + ct.authentication_method as _, ct.is_static_website, w_id, path, ) - .execute(&mut *tx).await?; + .execute(&mut *tx) + .await?; } audit_log( &mut *tx, &authed, "http_triggers.update", - ActionKind::Create, + ActionKind::Update, &w_id, - Some(path), + Some(&ct.path), None, ) .await?; + increase_trigger_version(&mut tx).await?; + tx.commit().await?; - Ok(path.to_string()) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::HttpTrigger { path: ct.path.clone() }, + Some(format!("HTTP route '{}' updated", ct.path)), + true, + ) + .await?; + + Ok(ct.path.to_string()) } async fn delete_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, -) -> error::Result { +) -> WindmillResult { require_admin(authed.is_admin, &authed.username)?; let path = path.to_path(); let mut tx = user_db.begin(&authed).await?; sqlx::query!( - "DELETE FROM http_trigger WHERE workspace_id = $1 AND path = $2", + "DELETE FROM http_trigger + WHERE workspace_id = $1 + AND path = $2", w_id, - path, + path ) .execute(&mut *tx) .await?; @@ -404,9 +750,22 @@ async fn delete_trigger( ) .await?; + increase_trigger_version(&mut tx).await?; + tx.commit().await?; - Ok(format!("HTTP trigger {path} deleted")) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::HttpTrigger { path: path.to_string() }, + Some(format!("HTTP route '{}' deleted", path)), + true, + ) + .await?; + + Ok(format!("HTTP route {path} deleted")) } async fn exists_trigger( @@ -415,13 +774,17 @@ async fn exists_trigger( ) -> JsonResult { let path = path.to_path(); let exists = sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE path = $1 AND workspace_id = $2)", + "SELECT EXISTS( + SELECT 1 FROM http_trigger + WHERE path = $1 AND workspace_id = $2 + )", path, - w_id, + w_id ) .fetch_one(&db) .await? .unwrap_or(false); + Ok(Json(exists)) } @@ -430,6 +793,7 @@ struct RouteExists { route_path: String, http_method: HttpMethod, trigger_path: Option, + workspaced_route: Option, } async fn route_path_key_exists( @@ -437,22 +801,47 @@ async fn route_path_key_exists( http_method: &HttpMethod, w_id: &str, trigger_path: Option<&str>, + workspaced_route: Option, db: &DB, -) -> error::Result { +) -> WindmillResult { let exists = if *CLOUD_HOSTED { sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND workspace_id = $2 AND http_method = $3 AND ($4::TEXT IS NULL OR path != $4))", - &route_path_key, - w_id, - http_method as &HttpMethod, - trigger_path - ) - .fetch_one(db) - .await? - .unwrap_or(false) + r#" + SELECT EXISTS( + SELECT 1 + FROM http_trigger + WHERE + route_path_key = $1 + AND workspace_id = $2 + AND http_method = $3 + AND ($4::TEXT IS NULL OR path != $4) + ) + "#, + &route_path_key, + w_id, + http_method as &HttpMethod, + trigger_path + ) + .fetch_one(db) + .await? + .unwrap_or(false) } else { + let route_path_key = match workspaced_route { + Some(true) => Cow::Owned(format!("{}/{}", w_id, route_path_key.trim_matches('/'))), + _ => Cow::Borrowed(route_path_key), + }; sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE route_path_key = $1 AND http_method = $2 AND ($3::TEXT IS NULL OR path != $3))", + r#" + SELECT EXISTS( + SELECT 1 + FROM http_trigger + WHERE + ((workspaced_route IS TRUE AND workspace_id || '/' || route_path_key = $1) + OR (workspaced_route IS FALSE AND route_path_key = $1)) + AND http_method = $2 + AND ($3::TEXT IS NULL OR path != $3) + ) + "#, &route_path_key, http_method as &HttpMethod, trigger_path @@ -461,13 +850,16 @@ async fn route_path_key_exists( .await? .unwrap_or(false) }; + Ok(exists) } async fn exists_route( Extension(db): Extension, Path(w_id): Path, - Json(RouteExists { route_path, http_method, trigger_path }): Json, + Json(RouteExists { route_path, http_method, trigger_path, workspaced_route }): Json< + RouteExists, + >, ) -> JsonResult { let route_path_key = ROUTE_PATH_KEY_RE.replace_all(route_path.as_str(), ":key"); @@ -476,6 +868,7 @@ async fn exists_route( &http_method, &w_id, trigger_path.as_deref(), + workspaced_route, &db, ) .await?; @@ -483,18 +876,158 @@ async fn exists_route( Ok(Json(exists)) } -struct TriggerRoute { +#[derive(Debug, Deserialize, Clone)] +pub struct TriggerRoute { path: String, script_path: String, is_flow: bool, route_path: String, workspace_id: String, is_async: bool, - requires_auth: bool, + authentication_method: AuthenticationMethod, edited_by: String, email: String, static_asset_config: Option>, is_static_website: bool, + authentication_resource_path: Option, + workspaced_route: bool, + wrap_body: bool, + raw_string: bool, +} + +pub struct RoutersCache { + routers: HashMap>, + version: i64, +} + +lazy_static::lazy_static! { + static ref HTTP_ACCESS_CACHE: Cache<(String, String, ApiAuthed), ExpiringCacheEntry<()>> = Cache::new(100); + static ref HTTP_AUTH_CACHE: Cache<(String, String, ApiAuthed), ExpiringCacheEntry> = Cache::new(100); + + static ref HTTP_ROUTERS_CACHE: RwLock = RwLock::new(RoutersCache { + routers: HashMap::new(), + version: 0, + }); +} + +pub async fn refresh_routers_loop( + db: &DB, + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, +) -> () { + match refresh_routers(db).await { + Ok(_) => { + tracing::info!("Loaded HTTP routers"); + } + Err(err) => { + tracing::error!("Error loading HTTP routers: {err:#}"); + } + }; + let db = db.clone(); + tokio::spawn(async move { + loop { + tokio::select! { + _ = killpill_rx.recv() => { + break; + } + _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { + match refresh_routers(&db).await { + Ok((true, _)) => { + tracing::info!("Refreshed HTTP routers"); + } + Err(err) => { + tracing::error!("Error refreshing HTTP routers: {err:#}"); + } + _ => {} + } + } + } + } + }); +} + +pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, RoutersCache>), Error> { + let version = sqlx::query_scalar!("SELECT last_value FROM http_trigger_version_seq",) + .fetch_one(db) + .await?; + let routers_cache = HTTP_ROUTERS_CACHE.read().await; + if routers_cache.version == 0 || version > routers_cache.version { + drop(routers_cache); + let mut routers = HashMap::new(); + + for http_method in [ + HttpMethod::Get, + HttpMethod::Post, + HttpMethod::Put, + HttpMethod::Patch, + HttpMethod::Delete, + ] { + let triggers = sqlx::query_as!( + TriggerRoute, + r#" + SELECT + path, + script_path, + is_flow, + route_path, + authentication_resource_path, + workspace_id, + is_async, + authentication_method AS "authentication_method: _", + edited_by, + email, + static_asset_config AS "static_asset_config: _", + wrap_body, + raw_string, + workspaced_route, + is_static_website + FROM + http_trigger + WHERE + http_method = $1 + "#, + &http_method as &HttpMethod + ) + .fetch_all(db) + .await?; + + let mut router = matchit::Router::new(); + + for trigger in triggers { + let full_path = if trigger.workspaced_route || *CLOUD_HOSTED { + format!("/{}/{}", trigger.workspace_id, trigger.route_path) + } else { + format!("/{}", trigger.route_path) + }; + + if trigger.is_static_website { + router + .insert(format!("{}/*wm_subpath", full_path), trigger.clone()) + .unwrap_or_else(|e| { + tracing::warn!( + "Failed to consider HTTP route {}/*wm_subpath: {:?}", + full_path, + e, + ); + }); + } + router + .insert(full_path.clone(), trigger.clone()) + .unwrap_or_else(|e| { + tracing::warn!("Failed to consider HTTP route {}: {:?}", full_path, e,); + }); + } + + routers.insert(http_method, router); + } + + let mut routers_cache = HTTP_ROUTERS_CACHE.write().await; + *routers_cache = RoutersCache { routers, version }; + + Ok((true, routers_cache.downgrade())) + } else { + tracing::debug!("No HTTP routers refresh needed"); + Ok((false, routers_cache)) + } } async fn get_http_route_trigger( @@ -504,74 +1037,39 @@ async fn get_http_route_trigger( db: &DB, user_db: UserDB, method: &http::Method, -) -> error::Result<(TriggerRoute, String, HashMap, ApiAuthed)> { +) -> WindmillResult<(TriggerRoute, String, HashMap, ApiAuthed)> { let http_method: HttpMethod = method.try_into()?; - let (mut triggers, route_path) = if *CLOUD_HOSTED { - let mut splitted = route_path.split("/"); - let w_id = splitted.next().ok_or_else(|| { - error::Error::BadRequest("Missing workspace id in route path".to_string()) - })?; - let route_path = StripPath(splitted.collect::>().join("/")); - let triggers = sqlx::query_as!( - TriggerRoute, - r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, static_asset_config as "static_asset_config: _", is_static_website FROM http_trigger WHERE workspace_id = $1 AND http_method = $2"#, - w_id, - http_method as HttpMethod - ) - .fetch_all(db) - .await?; - (triggers, route_path) + + let requested_path = format!("/{}", route_path); + + let routers_cache = HTTP_ROUTERS_CACHE.read().await; + + let routers_cache = if routers_cache.routers.is_empty() { + tracing::warn!("HTTP routers are not loaded, loading from db"); + let (_, routers_cache) = refresh_routers(db).await?; + routers_cache } else { - let triggers = sqlx::query_as!( - TriggerRoute, - r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, static_asset_config as "static_asset_config: _", is_static_website FROM http_trigger WHERE http_method = $1"#, - http_method as HttpMethod - ) - .fetch_all(db) - .await?; - (triggers, StripPath(route_path.to_string())) + routers_cache }; - let mut router = matchit::Router::new(); + let router = routers_cache + .routers + .get(&http_method) + .ok_or(error::Error::internal_err( + "HTTP routers could not be loaded".to_string(), + ))?; - for (idx, trigger) in triggers.iter().enumerate() { - let route_path = trigger.route_path.clone(); - if trigger.is_static_website { - router - .insert(format!("/{}/*wm_subpath", route_path), idx) - .unwrap_or_else(|e| { - tracing::warn!( - "Failed to consider http trigger route {}: {:?}", - route_path, - e, - ); - }); - } - router - .insert(format!("/{}", route_path), idx) - .unwrap_or_else(|e| { - tracing::warn!( - "Failed to consider http trigger route {}: {:?}", - route_path, - e, - ); - }); - } + let trigger_match = router.at(requested_path.as_str()).ok(); - let requested_path = format!("/{}", route_path.0); - let trigger_idx = router.at(requested_path.as_str()).ok(); - - let matchit::Match { value: trigger_idx, params } = - not_found_if_none(trigger_idx, "Trigger", requested_path.as_str())?; - - let trigger = triggers.remove(trigger_idx.to_owned()); + let matchit::Match { value: trigger, params } = + not_found_if_none(trigger_match, "Trigger", requested_path.as_str())?; let params: HashMap = params .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); - let username_override = if trigger.requires_auth { + let username_override = if let AuthenticationMethod::Windmill = trigger.authentication_method { let opt_authed = if let Some(token) = token { auth_cache .get_authed(Some(trigger.workspace_id.clone()), token) @@ -581,16 +1079,49 @@ async fn get_http_route_trigger( }; if let Some(authed) = opt_authed { // check that the user has access to the trigger - let mut tx = user_db.begin(&authed).await?; - let exists = sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1 AND path = $2)", - trigger.workspace_id, - trigger.path - ) - .fetch_one(&mut *tx) - .await? - .unwrap_or(false); - tx.commit().await?; + let cache_key = ( + trigger.workspace_id.clone(), + trigger.path.clone(), + authed.clone(), + ); + let exists = match HTTP_ACCESS_CACHE.get(&cache_key) { + Some(cache_entry) if cache_entry.expiry > std::time::Instant::now() => { + tracing::debug!("HTTP access cache hit for route {}", trigger.path); + true + } + _ => { + tracing::debug!("HTTP access cache miss for route {}", trigger.path); + let mut tx = user_db.begin(&authed).await?; + let exists = sqlx::query_scalar!( + r#" + SELECT EXISTS( + SELECT 1 + FROM + http_trigger + WHERE + workspace_id = $1 AND + path = $2 + ) + "#, + trigger.workspace_id, + trigger.path + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(false); + if exists { + HTTP_ACCESS_CACHE.insert( + cache_key, + ExpiringCacheEntry { + value: (), + expiry: std::time::Instant::now() + + std::time::Duration::from_secs(10), + }, + ); + } + exists + } + }; if exists { Some(authed.display_username().to_owned()) } else { @@ -610,37 +1141,11 @@ async fn get_http_route_trigger( trigger.email.clone(), &trigger.workspace_id, &db, - Some(username_override.unwrap_or(format!("http-{}", trigger.path))), + Some(username_override.unwrap_or(format!("HTTP-{}", trigger.path))), ) .await?; - Ok((trigger, route_path.0, params, authed)) -} - -pub async fn build_http_trigger_extra( - route_path: &str, - called_path: &str, - method: &http::Method, - params: &HashMap, - query: &HashMap, - headers: &HeaderMap, -) -> Box { - let headers = headers - .iter() - .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) - .collect::>(); - - to_raw_value(&serde_json::json!({ - "kind": "http", - "http": { - "route": route_path, - "path": called_path, - "method": method.to_string().to_lowercase(), - "params": params, - "query": query, - "headers": headers - }, - })) + Ok((trigger.clone(), route_path.to_string(), params, authed)) } async fn route_job( @@ -649,40 +1154,110 @@ async fn route_job( Extension(auth_cache): Extension>, OptTokened { token }: OptTokened, Path(route_path): Path, - Query(query): Query>, - method: http::Method, headers: HeaderMap, - args: WebhookArgs, -) -> impl IntoResponse { + args: RawHttpTriggerArgs, +) -> Result { let route_path = route_path.to_path().trim_end_matches("/"); - let (trigger, called_path, params, authed) = match get_http_route_trigger( + let (trigger, called_path, params, authed) = get_http_route_trigger( route_path, &auth_cache, token.as_ref(), &db, user_db.clone(), - &method, + &args.0.metadata.method, ) .await - { - Ok(trigger) => trigger, - Err(e) => return e.into_response(), - }; + .map_err(|e| e.into_response())?; - let mut args = match args - .to_push_args_owned(&authed, &db, &trigger.workspace_id) + if trigger.script_path.is_empty() && trigger.static_asset_config.is_none() { + return Err(Error::NotFound(format!( + "Runnable path of HTTP route at path: {}", + trigger.path + )) + .into_response()); + } + + let args = args + .process_args( + &authed, + &db, + &trigger.workspace_id, + match trigger.authentication_method { + AuthenticationMethod::CustomScript | AuthenticationMethod::Signature => true, + _ => trigger.raw_string, + }, + ) .await - { - Ok(args) => args, - Err(e) => return e.into_response(), - }; + .map_err(|e| e.into_response())?; + + match trigger.authentication_method { + AuthenticationMethod::None + | AuthenticationMethod::Windmill + | AuthenticationMethod::CustomScript => {} + _ => { + let resource_path = match trigger.authentication_resource_path { + Some(resource_path) => resource_path, + None => { + return Err(Error::BadRequest( + "Missing authentication resource path".to_string(), + ) + .into_response()) + } + }; + + let cache_key = ( + trigger.workspace_id.clone(), + resource_path.clone(), + authed.clone(), + ); + + let authentication_method = match HTTP_AUTH_CACHE.get(&cache_key) { + Some(cache_entry) if cache_entry.expiry > std::time::Instant::now() => { + tracing::debug!("HTTP auth method cache hit for route {}", trigger.path); + cache_entry.value + } + _ => { + tracing::debug!("HTTP auth method cache miss for route {}", trigger.path); + let auth_method = try_get_resource_from_db_as::< + crate::http_trigger_auth::AuthenticationMethod, + >( + &authed, + Some(user_db.clone()), + &db, + &resource_path, + &trigger.workspace_id, + ) + .await + .map_err(|e| e.into_response())?; + HTTP_AUTH_CACHE.insert( + cache_key, + ExpiringCacheEntry { + value: auth_method.clone(), + expiry: std::time::Instant::now() + std::time::Duration::from_secs(60), + }, + ); + auth_method + } + }; + + let raw_payload = args.0.metadata.raw_string.as_ref(); + + let response = authentication_method + .authenticate_http_request(&headers, raw_payload) + .map_err(|e| e.into_response())?; + + if let Some(response) = response { + return Ok(response); + } + } + } #[cfg(not(feature = "parquet"))] if trigger.static_asset_config.is_some() { - return error::Error::internal_err( + return Err(error::Error::internal_err( "Static asset configuration is not supported in this build".to_string(), ) - .into_response(); + .into_response()); } #[cfg(feature = "parquet")] @@ -782,29 +1357,38 @@ async fn route_job( }; match build_static_response_f.await { Ok((status, headers, body_stream)) => { - return (status, headers, body_stream).into_response() + return Ok((status, headers, body_stream).into_response()) } - Err(e) => return e.into_response(), + Err(e) => return Err(e.into_response()), } } - let extra = args.extra.get_or_insert_with(HashMap::new); - extra.insert( - "wm_trigger".to_string(), - build_http_trigger_extra( + let runnable_format = get_runnable_format( + if trigger.is_flow { + RunnableId::from_flow_path(&trigger.script_path) + } else { + RunnableId::from_script_path(&trigger.script_path) + }, + &trigger.workspace_id, + &db, + &TriggerKind::Http, + ) + .await + .map_err(|e| e.into_response())?; + + let args = args + .to_args_from_format( &trigger.route_path, &called_path, - &method, ¶ms, - &query, - &headers, + runnable_format, + trigger.wrap_body, ) - .await, - ); + .map_err(|e| e.into_response())?; let run_query = RunJobQuery::default(); - if trigger.is_flow { + let response = if trigger.is_flow { if trigger.is_async { run_flow_by_path_inner( authed, @@ -814,7 +1398,6 @@ async fn route_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await .into_response() @@ -827,7 +1410,6 @@ async fn route_job( user_db, args, trigger.workspace_id.clone(), - None, ) .await .into_response() @@ -842,7 +1424,6 @@ async fn route_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await .into_response() @@ -855,10 +1436,11 @@ async fn route_job( user_db, trigger.workspace_id.clone(), args, - None, ) .await .into_response() } - } + }; + + Ok(response) } diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs deleted file mode 100644 index 2ccca92c27..0000000000 --- a/backend/windmill-api/src/indexer_ee.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum::Router; - -pub fn workspaced_service() -> Router { - Router::new() -} - -pub fn global_service() -> Router { - Router::new() -} diff --git a/backend/windmill-api/src/indexer_oss.rs b/backend/windmill-api/src/indexer_oss.rs new file mode 100644 index 0000000000..eee87acdcb --- /dev/null +++ b/backend/windmill-api/src/indexer_oss.rs @@ -0,0 +1,16 @@ +#[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() +} diff --git a/backend/windmill-api/src/inkeep_oss.rs b/backend/windmill-api/src/inkeep_oss.rs new file mode 100644 index 0000000000..ca1ffebd21 --- /dev/null +++ b/backend/windmill-api/src/inkeep_oss.rs @@ -0,0 +1,11 @@ +#[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() +} diff --git a/backend/windmill-api/src/inputs.rs b/backend/windmill-api/src/inputs.rs index ec200b4464..9894275737 100644 --- a/backend/windmill-api/src/inputs.rs +++ b/backend/windmill-api/src/inputs.rs @@ -118,6 +118,8 @@ pub struct CompletedJobMini { #[derive(Deserialize)] struct GetInputHistory { include_preview: Option, + args: Option, + include_non_root: Option, } async fn get_input_history( @@ -132,13 +134,27 @@ async fn get_input_history( let mut tx = user_db.begin(&authed).await?; + let args_query = if let Some(args) = &g.args { + sql_builder::bind::Bind::bind(&"and v2_job.args @> ?", &args.replace("'", "''")) + } else { + "".to_string() + }; + + let include_non_root = if g.include_non_root.unwrap_or(false) { + "" + } else { + "AND parent_job IS NULL" + }; + let sql = &format!( "select id, v2_job.created_at, created_by, 'null'::jsonb as args, status = 'success' as success from v2_job JOIN v2_job_completed USING (id) \ - where {} = $1 and kind = any($2) and v2_job.workspace_id = $3 AND v2_job_completed.status != 'skipped' \ + where v2_job.workspace_id = $3 and {} = $1 and kind = any($2) {args_query} AND v2_job_completed.status != 'skipped' {include_non_root} \ order by v2_job.created_at desc limit $4 offset $5", - r.runnable_type.column_name() + r.runnable_type.column_name(), + ); + // tracing::info!("sql: {}", sql); let query = sqlx::query_as::<_, CompletedJobMini>(sql); let query = match r.runnable_type { @@ -177,9 +193,9 @@ async fn get_input_history( row.created_by ), created_at: row.created_at, - args: row.args.unwrap_or(sqlx::types::Json( + args: sqlx::types::Json( serde_json::value::RawValue::from_string("null".to_string()).unwrap(), - )), + ), created_by: row.created_by, is_public: true, success: row.success, diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_oss.rs similarity index 65% rename from backend/windmill-api/src/job_helpers_ee.rs rename to backend/windmill-api/src/job_helpers_oss.rs index b35f40d661..53d4a796a1 100644 --- a/backend/windmill-api/src/job_helpers_ee.rs +++ b/backend/windmill-api/src/job_helpers_oss.rs @@ -1,48 +1,69 @@ +#[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(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use crate::db::{ApiAuthed, DB}; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use object_store::{ObjectStore, PutMultipartOpts}; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use std::sync::Arc; +#[cfg(not(feature = "private"))] use windmill_common::error; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource}; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use bytes::Bytes; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use futures::Stream; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] use axum::response::Response; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] 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, + #[allow(dead_code)] + pub storage: Option, } #[derive(Deserialize)] +#[cfg(not(feature = "private"))] pub struct DownloadFileQuery { + #[allow(dead_code)] pub file_key: String, + #[allow(dead_code)] + pub storage: Option, + #[allow(dead_code)] + pub s3_resource_path: Option, } +#[cfg(not(feature = "private"))] pub fn workspaced_service() -> Router { Router::new() } -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] pub async fn get_workspace_s3_resource<'c>( _authed: &ApiAuthed, _db: &DB, @@ -55,10 +76,12 @@ 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 { unimplemented!("Not implemented in Windmill's Open Source repository") } +#[cfg(not(feature = "private"))] pub async fn get_s3_resource<'c>( _authed: &ApiAuthed, _db: &DB, @@ -74,7 +97,7 @@ pub async fn get_s3_resource<'c>( )) } -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] pub async fn upload_file_from_req( _s3_client: Arc, _file_key: &str, @@ -86,7 +109,7 @@ pub async fn upload_file_from_req( )) } -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] pub async fn upload_file_internal( _s3_client: Arc, _file_key: &str, @@ -98,7 +121,7 @@ pub async fn upload_file_internal( )) } -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", not(feature = "private")))] pub async fn download_s3_file_internal( _authed: ApiAuthed, _db: &DB, @@ -111,16 +134,3 @@ pub async fn download_s3_file_internal( "Not implemented in Windmill's Open Source repository".to_string(), )) } - -#[cfg(feature = "parquet")] -pub async fn load_image_preview_internal( - _authed: ApiAuthed, - _db: &DB, - _token: &str, - _w_id: &str, - _query: LoadImagePreviewQuery, -) -> error::Result { - Err(error::Error::internal_err( - "Not implemented in Windmill's Open Source repository".to_string(), - )) -} diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index ad0376fe51..c5b5fb14ad 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -7,8 +7,11 @@ */ use axum::body::Body; +use axum::extract::Request; use axum::http::HeaderValue; -use futures::TryFutureExt; +#[cfg(feature = "deno_core")] +use deno_core::{op2, serde_v8, v8, JsRuntime, OpState}; +use futures::{StreamExt, TryFutureExt}; use http::{HeaderMap, HeaderName}; use itertools::Itertools; use quick_cache::sync::Cache; @@ -23,10 +26,11 @@ use tokio::io::AsyncReadExt; #[cfg(feature = "prometheus")] use tokio::time::Instant; use tower::ServiceBuilder; +use windmill_common::auth::is_super_admin_email; use windmill_common::error::JsonResult; use windmill_common::flow_status::{JobResult, RestartedFrom}; use windmill_common::jobs::{format_completed_job_result, format_result, ENTRYPOINT_OVERRIDE}; -use windmill_common::worker::{CLOUD_HOSTED, TMP_DIR}; +use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR}; use windmill_common::scripts::PREVIEW_IS_CODEBASE_HASH; use windmill_common::variables::get_workspace_key; @@ -35,10 +39,11 @@ use crate::add_webhook_allowed_origin; use crate::concurrency_groups::join_concurrency_key; use crate::db::ApiAuthed; +use crate::trigger_helpers::RunnableId; use crate::users::get_scope_tags; use crate::utils::content_plain; use crate::{ - args::{DecodeQueries, WebhookArgs}, + args::{self, RawWebhookArgs}, db::DB, users::{check_scopes, require_owner_of_path, OptAuthed}, utils::require_super_admin, @@ -53,14 +58,14 @@ use axum::{ use base64::Engine; use chrono::Utc; use hmac::Mac; -use hyper::{Request, StatusCode}; +use hyper::StatusCode; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sql_builder::prelude::*; use sqlx::types::JsonRawValue; use sqlx::{types::Uuid, FromRow, Postgres, Transaction}; use tower_http::cors::{Any, CorsLayer}; use urlencoding::encode; -use windmill_audit::audit_ee::{audit_log, AuditAuthor}; +use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE}; use windmill_common::{ @@ -79,12 +84,13 @@ 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}; -use windmill_common::{get_latest_deployed_hash_for_path, BASE_URL}; +use windmill_common::{ + get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, + get_script_info_for_hash, FlowVersionInfo, ScriptHashInfo, BASE_URL, +}; use windmill_queue::{ cancel_job, get_result_and_success_by_id_from_flow, job_is_complete, push, PushArgs, PushArgsOwned, PushIsolationLevel, @@ -140,6 +146,13 @@ pub fn workspaced_service() -> Router { .layer(cors.clone()) .layer(ce_headers.clone()), ) + .route( + "/run/batch_rerun_jobs", + post(batch_rerun_jobs) + .head(|| async { "" }) + .layer(cors.clone()) + .layer(ce_headers.clone()), + ) .route( "/run/workflow_as_code/:job_id/:entrypoint", post(run_workflow_as_code) @@ -203,6 +216,13 @@ pub fn workspaced_service() -> Router { "/list", get(list_jobs).layer(Extension(api_list_jobs_query_duration)), ) + .route( + "/list_selected_job_groups", + // We use post because sending a huge array as a query param can produce + // URLs that may be too long + post(list_selected_job_groups), + ) + .route("/list_filtered_uuids", get(list_filtered_job_uuids)) .route("/queue/list", get(list_queue_jobs)) .route("/queue/count", get(count_queue_jobs)) .route("/queue/list_filtered_uuids", get(list_filtered_uuids)) @@ -372,7 +392,6 @@ async fn cancel_job_api( email: "anonymous".to_string(), }, }; - let (mut tx, job_option) = tokio::time::timeout( std::time::Duration::from_secs(120), windmill_queue::cancel_job( @@ -530,64 +549,14 @@ async fn force_cancel( } } -pub async fn get_path_tag_limits_cache_for_hash( - mut tx: Transaction<'_, Postgres>, - w_id: &str, - hash: i64, -) -> error::Result<( - String, - Option, - Option, - Option, - Option, - Option, - ScriptLang, - Option, - Option, - Option, - Option, - Option, - Option, - String, -)> { - let script = sqlx::query!( - "select path, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by from script where hash = $1 AND workspace_id = $2", - hash, - w_id - ) - .fetch_optional(&mut *tx) - .await - .map_err(|e| { - Error::internal_err(format!( - "querying getting path for hash {hash} in {w_id}: {e:#}" - )) - })?.ok_or_else(|| Error::NotFound(format!( - "deployed script not found at hash {hash} in workspace {w_id}" - )))?; - Ok(( - script.path, - script.tag, - script.concurrency_key, - script.concurrent_limit, - script.concurrency_time_window_s, - script.cache_ttl, - script.language, - script.dedicated_worker, - script.priority, - script.delete_after_use, - script.timeout, - script.has_preprocessor, - script.on_behalf_of_email, - script.created_by, - )) -} - async fn get_flow_job_debug_info( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, Path((w_id, id)): Path<(String, Uuid)>, ) -> error::Result { - let job = GetQuery::new().fetch_queued(&db, id, &w_id).await?; + let job = GetQuery::new() + .fetch_queued((&db).into(), id, &w_id) + .await?; if let Some(job) = job { let is_flow = job.is_flow(); if job.is_flow_step || !is_flow { @@ -644,6 +613,48 @@ async fn get_flow_job_debug_info( } } +async fn list_selected_job_groups( + authed: ApiAuthed, + Extension(user_db): Extension, + Path(w_id): Path, + Json(uuids): Json>, +) -> error::Result { + let mut tx = user_db.begin(&authed).await?; + + let results = sqlx::query_scalar!( + r#"SELECT jsonb_build_object( + 'kind', jb.kind, + 'script_path', jb.runnable_path, + 'latest_schema', COALESCE( + (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC), + (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow') + ), + 'schemas', ARRAY( + SELECT jsonb_build_object( + 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'), + 'job_ids', ARRAY_AGG(DISTINCT j.id), + 'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1] + ) FROM v2_job j + LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script' + LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow' + WHERE j.id = ANY(ARRAY_AGG(jb.id)) + GROUP BY COALESCE(s.hash, f.id) + ) + ) FROM v2_job jb + WHERE (jb.kind = 'flow' OR jb.kind = 'script') + AND jb.workspace_id = $1 AND jb.id = ANY($2) + GROUP BY jb.kind, jb.runnable_path"#, + &w_id, + &uuids + ) + .fetch_all(&mut *tx) + .await?; + + tx.commit().await?; + + Ok(Json(results).into_response()) +} + #[derive(Deserialize)] struct GetJobQuery { pub no_logs: Option, @@ -676,64 +687,167 @@ async fn get_job( } macro_rules! get_job_query { - ("v2_as_completed_job", $($opts:tt)*) => { + ("v2_job_completed", $($opts:tt)*) => { get_job_query!( - @impl "v2_as_completed_job", ($($opts)*), - "duration_ms, success, result, result_columns, deleted, is_skipped, result->'wm_labels' as labels, \ + @impl "v2_job_completed", ($($opts)*), + "v2_job_completed.duration_ms, CASE WHEN status = 'success' OR status = 'skipped' THEN true ELSE false END as success, result_columns, deleted, status = 'skipped' as is_skipped, result->'wm_labels' as labels, \ CASE WHEN result is null or pg_column_size(result) < 90000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result", + "", ) }; - ("v2_as_queue", $($opts:tt)*) => { + ("v2_job_queue", $($opts:tt)*) => { get_job_query!( - @impl "v2_as_queue", ($($opts)*), - "scheduled_for, running, last_ping, suspend, suspend_until, same_worker, pre_run_error, visible_to_owner, \ - root_job, leaf_jobs, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl,\ + @impl "v2_job_queue", ($($opts)*), + "scheduled_for, running, ping as last_ping, suspend, suspend_until, same_worker, pre_run_error, visible_to_owner, \ + flow_innermost_root_job AS root_job, flow_leaf_jobs AS leaf_jobs, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl,\ script_entrypoint_override", + "LEFT JOIN v2_job_runtime ON v2_job_runtime.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id", ) }; - (@impl $table:literal, (with_logs: $with_logs:expr, $($rest:tt)*), $additional_fields:literal, $($args:tt)*) => { + (@impl $table:literal, (with_logs: $with_logs:expr, $($rest:tt)*), $additional_fields:literal, $additional_joins:literal, $($args:tt)*) => { if $with_logs { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, logs = "right(job_logs.logs, 20000)", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, logs = "right(job_logs.logs, 20000)", $($args)*) } else { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, logs = "null", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, logs = "null", $($args)*) } }; - (@impl $table:literal, (with_code: $with_code:expr, $($rest:tt)*), $additional_fields:literal, $($args:tt)*) => { + (@impl $table:literal, (with_code: $with_code:expr, $($rest:tt)*), $additional_fields:literal, $additional_joins:literal, $($args:tt)*) => { if $with_code { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, lock = "raw_lock", code = "raw_code", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, lock = "raw_lock", code = "raw_code", $($args)*) } else { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, lock = "null", code = "null", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, lock = "null", code = "null", $($args)*) } }; - (@impl $table:literal, (with_flow: $with_flow:expr, $($rest:tt)*), $additional_fields:literal, $($args:tt)*) => { + (@impl $table:literal, (with_flow: $with_flow:expr, $($rest:tt)*), $additional_fields:literal, $additional_joins:literal, $($args:tt)*) => { if $with_flow { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, flow = "raw_flow", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, flow = "raw_flow", $($args)*) } else { - get_job_query!(@impl $table, ($($rest)*), $additional_fields, flow = "null", $($args)*) + get_job_query!(@impl $table, ($($rest)*), $additional_fields, $additional_joins, flow = "null", $($args)*) } }; - (@impl $table:literal, (), $additional_fields:literal, $($args:tt)*) => { + (@impl $table:literal, (), $additional_fields:literal, $additional_joins:literal, $($args:tt)*) => { const_format::formatcp!( "SELECT \ - id, {table}.workspace_id, parent_job, created_by, {table}.created_at, started_at, script_hash, script_path, \ + {table}.id, {table}.workspace_id, parent_job, v2_job.created_by, v2_job.created_at, started_at, v2_job.runnable_id as script_hash, v2_job.runnable_path as script_path, \ CASE WHEN args is null THEN NULL WHEN pg_column_size(args) < 90000 THEN CASE WHEN jsonb_typeof(args) = 'object' THEN args ELSE jsonb_build_object('value', args) END - ELSE '{{\"reason\": \"WINDMILL_TOO_BIG\"}}'::jsonb END as args, \ - {logs} as logs, {code} as raw_code, canceled, canceled_by, canceled_reason, job_kind, \ - schedule_path, permissioned_as, flow_status, {flow} as raw_flow, is_flow_step, language, \ - {lock} as raw_lock, email, visible_to_owner, mem_peak, tag, priority, preprocessed, {additional_fields} \ - FROM {table} LEFT JOIN job_logs ON id = job_id \ - WHERE id = $1 AND {table}.workspace_id = $2 AND ($3::text[] IS NULL OR tag = ANY($3)) LIMIT 1", + ELSE '{{\"reason\": \"WINDMILL_TOO_BIG\"}}'::jsonb END as args, COALESCE(flow_status, workflow_as_code_status) AS flow_status, \ + {logs} as logs, {code} as raw_code, canceled_by is not null as canceled, canceled_by, canceled_reason, kind as job_kind, \ + CASE WHEN trigger_kind = 'schedule'::job_trigger_kind THEN trigger END AS schedule_path, permissioned_as, \ + {flow} as raw_flow, flow_step_id IS NOT NULL AS is_flow_step, script_lang as language, \ + {lock} as raw_lock, permissioned_as_email as email, visible_to_owner, memory_peak as mem_peak, v2_job.tag, v2_job.priority, preprocessed, worker,\ + {additional_fields} \ + FROM {table} + INNER JOIN v2_job ON v2_job.id = {table}.id \ + {additional_joins} \ + LEFT JOIN job_logs ON {table}.id = job_id \ + WHERE {table}.id = $1 AND {table}.workspace_id = $2 AND ($3::text[] IS NULL OR v2_job.tag = ANY($3))", table = $table, additional_fields = $additional_fields, + additional_joins = $additional_joins, $($args)* ) } } +// CREATE OR REPLACE VIEW v2_as_queue AS +// SELECT +// j.id, +// j.workspace_id, +// j.parent_job, +// j.created_by, +// j.created_at, +// q.started_at, +// q.scheduled_for, +// q.running, +// j.runnable_id AS script_hash, +// j.runnable_path AS script_path, +// j.args, +// j.raw_code, +// q.canceled_by IS NOT NULL AS canceled, +// q.canceled_by, +// q.canceled_reason, +// r.ping AS last_ping, +// j.kind AS job_kind, +// CASE WHEN j.trigger_kind = 'schedule'::job_trigger_kind THEN j.trigger END +// AS schedule_path, +// j.permissioned_as, +// COALESCE(s.flow_status, s.workflow_as_code_status) AS flow_status, +// j.raw_flow, +// j.flow_step_id IS NOT NULL AS is_flow_step, +// j.script_lang AS language, +// q.suspend, +// q.suspend_until, +// j.same_worker, +// j.raw_lock, +// j.pre_run_error, +// j.permissioned_as_email AS email, +// j.visible_to_owner, +// r.memory_peak AS mem_peak, +// j.flow_innermost_root_job AS root_job, +// s.flow_leaf_jobs AS leaf_jobs, +// j.tag, +// j.concurrent_limit, +// j.concurrency_time_window_s, +// j.timeout, +// j.flow_step_id, +// j.cache_ttl, +// j.priority, +// NULL::TEXT AS logs, +// j.script_entrypoint_override, +// j.preprocessed +// FROM v2_job_queue q +// JOIN v2_job j USING (id) +// LEFT JOIN v2_job_runtime r USING (id) +// LEFT JOIN v2_job_status s USING (id) +// ; + +// -- Add up migration script here +// CREATE OR REPLACE VIEW v2_as_completed_job AS +// SELECT +// j.id, +// j.workspace_id, +// j.parent_job, +// j.created_by, +// j.created_at, +// c.duration_ms, +// c.status = 'success' OR c.status = 'skipped' AS success, +// j.runnable_id AS script_hash, +// j.runnable_path AS script_path, +// j.args, +// c.result, +// FALSE AS deleted, +// j.raw_code, +// c.status = 'canceled' AS canceled, +// c.canceled_by, +// c.canceled_reason, +// j.kind AS job_kind, +// CASE WHEN j.trigger_kind = 'schedule'::job_trigger_kind THEN j.trigger END +// AS schedule_path, +// j.permissioned_as, +// COALESCE(c.flow_status, c.workflow_as_code_status) AS flow_status, +// j.raw_flow, +// j.flow_step_id IS NOT NULL AS is_flow_step, +// j.script_lang AS language, +// c.started_at, +// c.status = 'skipped' AS is_skipped, +// j.raw_lock, +// j.permissioned_as_email AS email, +// j.visible_to_owner, +// c.memory_peak AS mem_peak, +// j.tag, +// j.priority, +// NULL::TEXT AS logs, +// c.result_columns, +// j.script_entrypoint_override, +// j.preprocessed +// FROM v2_job_completed c +// JOIN v2_job j USING (id) +// ; + #[derive(Copy, Clone)] struct GetQuery<'a> { with_logs: bool, @@ -823,8 +937,9 @@ impl<'a> GetQuery<'a> { // Try to fetch the code from the cache, fallback to the preview code. // NOTE: This could check for the job kinds instead of the `or_else` but it's not // necessary as `fetch_script` return early if the job kind is not a preview one. - cache::job::fetch_script(db, kind, hash) - .or_else(|_| cache::job::fetch_preview_script(db, &id, raw_lock, raw_code)) + let conn = Connection::from(db.clone()); + cache::job::fetch_script(db.clone(), kind, hash) + .or_else(|_| cache::job::fetch_preview_script(&conn, &id, raw_lock, raw_code)) .await .ok() .inspect(|data| { @@ -839,7 +954,7 @@ impl<'a> GetQuery<'a> { job_id: Uuid, workspace_id: &str, ) -> error::Result>> { - let query = get_job_query!("v2_as_queue", + let query = get_job_query!("v2_job_queue", with_logs: self.with_logs, with_code: self.with_code, with_flow: self.with_flow, @@ -853,7 +968,7 @@ impl<'a> GetQuery<'a> { self.check_auth(job.as_ref().map(|job| job.created_by.as_str()))?; if let Some(job) = job.as_mut() { - self.resolve_raw_values(db, job.id, job.job_kind, job.script_hash, job) + self.resolve_raw_values(&db, job.id, job.job_kind, job.script_hash, job) .await; } if self.with_flow { @@ -871,11 +986,13 @@ impl<'a> GetQuery<'a> { job_id: Uuid, workspace_id: &str, ) -> error::Result>> { - let query = get_job_query!("v2_as_completed_job", + let query = get_job_query!("v2_job_completed", with_logs: self.with_logs, with_code: self.with_code, with_flow: self.with_flow, ); + + // tracing::info!("query: {}", query); let query = sqlx::query_as::<_, JobExtended>(query) .bind(job_id) .bind(workspace_id) @@ -888,12 +1005,14 @@ impl<'a> GetQuery<'a> { self.resolve_raw_values(db, job.id, job.job_kind, job.script_hash, job) .await; } + if self.with_flow { cjob = resolve_maybe_value(db, workspace_id, self.with_code, cjob, |job| { job.raw_flow.as_mut() }) .await?; } + if let Some(mut cjob) = cjob { cjob.inner = format_completed_job_result(cjob.inner); return Ok(Some(cjob)); @@ -903,7 +1022,7 @@ impl<'a> GetQuery<'a> { async fn fetch(self, db: &DB, job_id: Uuid, workspace_id: &str) -> error::Result { let cjob = self - .fetch_completed(db, job_id, workspace_id) + .fetch_completed(db.into(), job_id, workspace_id) .await? .map(Job::CompletedJob); @@ -911,7 +1030,7 @@ impl<'a> GetQuery<'a> { Some(cjob) => Ok(cjob), None => { let job_maybe = self - .fetch_queued(db, job_id, workspace_id) + .fetch_queued(db.into(), job_id, workspace_id) .await? .map(Job::QueuedJob); // potential race condition here, if the job was in queue and completed right after the fetch completed, so we need to check one last time @@ -919,7 +1038,7 @@ impl<'a> GetQuery<'a> { return Ok(job); } else { let cjob2 = self - .fetch_completed(db, job_id, workspace_id) + .fetch_completed(db.into(), job_id, workspace_id) .await? .map(Job::CompletedJob); not_found_if_none(cjob2, "Job", job_id.to_string()) @@ -938,7 +1057,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) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { tracing::debug!("object store client present, streaming from there"); let logs = logs.to_string(); @@ -1259,6 +1378,7 @@ pub struct ListQueueQuery { pub order_desc: Option, pub job_kinds: Option, pub suspended: Option, + pub worker: Option, // filter by matching a subset of the args using base64 encoded json subset pub args: Option, pub tag: Option, @@ -1268,6 +1388,7 @@ pub struct ListQueueQuery { pub has_null_parent: Option, pub is_not_schedule: Option, pub concurrency_key: Option, + pub allow_wildcards: Option, } impl From for ListQueueQuery { @@ -1283,6 +1404,7 @@ impl From for ListQueueQuery { created_after: lcq.created_after, created_or_started_before: lcq.created_or_started_before, created_or_started_after: lcq.created_or_started_after, + worker: lcq.worker, running: lcq.running, parent_job: lcq.parent_job, order_desc: lcq.order_desc, @@ -1297,6 +1419,7 @@ impl From for ListQueueQuery { has_null_parent: lcq.has_null_parent, is_not_schedule: lcq.is_not_schedule, concurrency_key: lcq.concurrency_key, + allow_wildcards: lcq.allow_wildcards, } } } @@ -1319,6 +1442,14 @@ pub fn filter_list_queue_query( sqlb.and_where_eq("v2_job.workspace_id", "?".bind(&w_id)); } + if let Some(w) = &lq.worker { + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job_queue.worker", w.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job_queue.worker", "?".bind(w)); + } + } + if let Some(ps) = &lq.script_path_start { sqlb.and_where_like_left("runnable_path", ps); } @@ -1336,8 +1467,13 @@ pub fn filter_list_queue_query( sqlb.and_where_eq("created_by", "?".bind(cb)); } if let Some(t) = &lq.tag { - sqlb.and_where_eq("tag", "?".bind(t)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + } } + if let Some(r) = &lq.running { sqlb.and_where_eq("running", &r); } @@ -1428,7 +1564,10 @@ pub fn list_queue_jobs_query( .clone(); if let Some(tags) = tags { - sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::>()); + sqlb.and_where_in( + "v2_job.tag", + &tags.iter().map(|x| quote(x)).collect::>(), + ); } filter_list_queue_query(sqlb, lq, w_id, join_outstanding_wait_times) @@ -1616,6 +1755,37 @@ async fn cancel_selection( cancel_jobs(jobs_to_cancel, &db, authed.username.as_str(), w_id.as_str()).await } +async fn list_filtered_job_uuids( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + Query(lq): Query, +) -> error::JsonResult> { + require_admin(authed.is_admin, &authed.username)?; + check_scopes(&authed, || format!("jobs:listjobs"))?; + + let mut sqlb = list_completed_jobs_query( + w_id.as_str(), + None, + 0, + &lq, + &["v2_job.id"], + false, + get_scope_tags(&authed), + ); + let sqlb2 = list_queue_jobs_query( + w_id.as_str(), + &lq.into(), + &["v2_job.id"], + Pagination { page: None, per_page: None }, + false, + get_scope_tags(&authed), + ); + let query = sqlb.union_all(sqlb2.subquery()?).subquery()?; + let ids = sqlx::query_scalar(query.as_str()).fetch_all(&db).await?; + Ok(Json(ids)) +} + async fn list_filtered_uuids( authed: ApiAuthed, Extension(db): Extension, @@ -1635,7 +1805,10 @@ async fn list_filtered_uuids( .or_where_is_null("v2_job.trigger_kind"); if let Some(tags) = get_scope_tags(&authed) { - sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::>()); + sqlb.and_where_in( + "v2_job.tag", + &tags.iter().map(|x| quote(x)).collect::>(), + ); } sqlb = filter_list_queue_query(sqlb, &lq, w_id.as_str(), false); @@ -1718,7 +1891,7 @@ async fn count_completed_jobs_detail( if let Some(tags) = query.tags { sqlb.and_where_in( - "tag", + "v2_job.tag", &tags .split(",") .map(|t| format!("'{}'", t)) @@ -1769,7 +1942,7 @@ async fn list_jobs( let sqlc = if lq.running.is_none() { Some(list_completed_jobs_query( &w_id, - per_page + offset, + Some(per_page + offset), 0, &ListCompletedQuery { order_desc: Some(true), ..lqc }, UnifiedJob::completed_job_fields(), @@ -1808,7 +1981,9 @@ async fn list_jobs( } else { if sqlc.is_none() { return Err(error::Error::BadRequest( - "cannot specify success, label, created_or_started_before, or started_before with running".to_string(), + "cannot specify success, label, created_or_started_before, or starte + d_before with running" + .to_string(), )); } sqlc.unwrap().limit(per_page).offset(offset).query()? @@ -2444,6 +2619,9 @@ pub struct JobExtended { #[serde(skip_serializing_if = "Option::is_none")] pub raw_flow: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub worker: Option, + #[sqlx(skip)] #[serde(skip_serializing_if = "Option::is_none")] pub self_wait_time_ms: Option, @@ -2463,6 +2641,7 @@ impl JobExtended { raw_code: None, raw_lock: None, raw_flow: None, + worker: None, self_wait_time_ms, aggregate_wait_time_ms, } @@ -2678,6 +2857,7 @@ pub struct UnifiedJob { pub self_wait_time_ms: Option, pub aggregate_wait_time_ms: Option, pub preprocessed: Option, + pub worker: Option, } const CJ_FIELDS: &[&str] = &[ @@ -2716,6 +2896,7 @@ const CJ_FIELDS: &[&str] = &[ "self_wait_time_ms", "aggregate_wait_time_ms", "v2_job.preprocessed", + "v2_job_completed.worker", ]; const QJ_FIELDS: &[&str] = &[ @@ -2754,6 +2935,7 @@ const QJ_FIELDS: &[&str] = &[ "self_wait_time_ms", "aggregate_wait_time_ms", "v2_job.preprocessed", + "v2_job_queue.worker", ]; impl UnifiedJob { @@ -2865,16 +3047,17 @@ struct CancelJob { enum PreviewKind { Code, Identity, - Http, Noop, Bundle, Tarbundle, + ScriptHash, } #[derive(Deserialize)] struct Preview { content: Option, kind: Option, + script_hash: Option, path: Option, args: Option>>, language: Option, @@ -2958,28 +3141,27 @@ pub fn add_raw_string( } async fn check_tag_available_for_workspace( + db: &DB, w_id: &str, tag: &Option, authed: &ApiAuthed, ) -> error::Result<()> { if let Some(tag) = tag { - if tag == "" { + if tag.is_empty() { return Ok(()); } let tags = get_scope_tags(authed); + let mut is_tag_available_in_workspace = None; + let mut is_tag_in_workspace_custom_tags = false; - if let Some(tags) = tags { - if !tags.contains(&tag.as_str()) { - return Err(Error::BadRequest(format!( - "Tag {tag} is not available in your scope" - ))); - } + if let Some(tags) = tags.as_ref() { + is_tag_available_in_workspace = Some(tags.contains(&tag.as_str())); } let custom_tags_per_w = CUSTOM_TAGS_PER_WORKSPACE.read().await; if custom_tags_per_w.0.contains(&tag.to_string()) { - Ok(()) + is_tag_in_workspace_custom_tags = true; } else if custom_tags_per_w.1.contains_key(tag) && custom_tags_per_w .1 @@ -2987,21 +3169,38 @@ async fn check_tag_available_for_workspace( .unwrap() .contains(&w_id.to_string()) { - Ok(()) - } else { + is_tag_in_workspace_custom_tags = true; + } + + match is_tag_available_in_workspace { + Some(true) | None => { + if is_tag_in_workspace_custom_tags { + return Ok(()); + } + } + _ => {} + } + + if !is_super_admin_email(db, &authed.email).await? { + if tags.is_some() && is_tag_available_in_workspace.is_some() { + return Err(Error::BadRequest(format!( + "Tag {tag} is not available in your scope" + ))); + } + return Err(error::Error::BadRequest(format!( - "Tag {tag} cannot be used on workspace {w_id}: (CUSTOM_TAGS: {:?})", + "Only super admins are allowed to use tags that are not included in the allowed CUSTOM_TAGS: {:?}", custom_tags_per_w ))); } - } else { - Ok(()) } + + return Ok(()); } #[cfg(feature = "enterprise")] pub async fn check_license_key_valid() -> error::Result<()> { - use windmill_common::ee::LICENSE_KEY_VALID; + use windmill_common::ee_oss::LICENSE_KEY_VALID; let valid = *LICENSE_KEY_VALID.read().await; if !valid { @@ -3013,17 +3212,288 @@ pub async fn check_license_key_valid() -> error::Result<()> { Ok(()) } +use windmill_common::flows::InputTransform; + +#[derive(Deserialize)] +struct BatchReRunJobsBodyArgs { + job_ids: Vec, + script_options_by_path: HashMap, + flow_options_by_path: HashMap, +} + +#[derive(Deserialize)] +struct BatchReRunOptions { + input_transforms: Option>, + use_latest_version: Option, +} + +#[derive(sqlx::FromRow, Serialize, Clone)] +struct BatchReRunQueryReturnType { + id: Uuid, + kind: JobKind, + script_path: String, + script_hash: ScriptHash, + input: serde_json::Value, + scheduled_for: chrono::DateTime, + #[serde(skip_serializing_if = "Option::is_none")] + schema: Option, +} + +#[cfg(feature = "deno_core")] +#[op2] +#[string] +fn get_deno_core_job_value(state: &mut OpState) -> Option { + let obj = state.borrow::(); + let str = serde_json::to_string(&obj).ok()?; + Some(str) +} + +#[cfg(feature = "deno_core")] +async fn batch_rerun_compute_js_expression( + expr: String, + job: BatchReRunQueryReturnType, +) -> error::Result> { + let ext = deno_core::Extension { + name: "batch_rerun_arg_transform_ext", + ops: vec![get_deno_core_job_value()].into(), + ..Default::default() + }; + let mut isolate = + JsRuntime::new(deno_core::RuntimeOptions { extensions: vec![ext], ..Default::default() }); + + { + let op_state = isolate.op_state(); + let mut op_state = op_state.borrow_mut(); + op_state.put(BatchReRunQueryReturnType { schema: None, ..job }); + } + isolate + .execute_script( + "", + "let job = JSON.parse(Deno.core.ops.get_deno_core_job_value());", + ) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + + // Run user expr + let result = isolate + .execute_script("", expr) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + let mut scope = isolate.handle_scope(); + let result = v8::Local::new(&mut scope, result); + let result: serde_json::Value = + serde_v8::from_v8(&mut scope, result).map_err(|e| Error::ExecutionErr(e.to_string()))?; + let result = JsonRawValue::from_string(result.to_string())?; + Ok(result) +} + +async fn batch_rerun_jobs( + authed: ApiAuthed, + Extension(db): Extension, + Extension(user_db): Extension, + Path(w_id): Path, + Json(body): Json, +) -> Response { + let stream = batch_rerun_jobs_inner(authed, db, user_db, w_id, body); + + let body = axum::body::Body::from_stream(stream.map(Result::<_, std::convert::Infallible>::Ok)); + + Response::builder() + .status(201) + .header("Content-Type", "text/event-stream") + .header("Cache-Control", "no-cache") + .body(body) + .unwrap() +} + +fn batch_rerun_jobs_inner( + authed: ApiAuthed, + db: DB, + user_db: UserDB, + w_id: String, + body: BatchReRunJobsBodyArgs, +) -> impl futures::Stream { + let (tx, rx) = tokio::sync::mpsc::channel(10); + tokio::spawn(async move { + let mut job_stream = sqlx::query_as!( + BatchReRunQueryReturnType, + r#"SELECT + j.id, + j.kind AS "kind: _", + COALESCE(s.path, f.path) AS "script_path!", + COALESCE(s.hash, f.id) AS "script_hash!: _", + COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS "scheduled_for!: _", + args AS input, + COALESCE(s.schema, f.schema) AS "schema: _" + FROM v2_job j + LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script' + LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow' + LEFT JOIN v2_job_completed jc ON jc.id = j.id + LEFT JOIN v2_job_queue jq ON jq.id = j.id + WHERE j.id = ANY($1) + AND j.workspace_id = $2 + AND COALESCE(s.hash, f.id) IS NOT NULL + AND COALESCE(s.path, f.path) IS NOT NULL"#, + &body.job_ids, + w_id + ).fetch(&db); + while let Some(Ok(job)) = job_stream.next().await { + let job_result = + batch_rerun_handle_job(&job, &authed, &db, &user_db, &w_id, &body).await; + let send_to_stream_result = tx + .send(match job_result { + Ok(uuid) => format!("{}\n", uuid), + Err(err) => format!("Error: {}\n", err.to_string()), + }) + .await; + match send_to_stream_result { + Ok(_) => {} + Err(e) => tracing::error!("Couldn't re-run job {}: {}", job.id, e.to_string()), + } + } + }); + tokio_stream::wrappers::ReceiverStream::new(rx) +} + +async fn batch_rerun_handle_job( + job: &BatchReRunQueryReturnType, + authed: &ApiAuthed, + db: &DB, + user_db: &UserDB, + w_id: &String, + body: &BatchReRunJobsBodyArgs, +) -> error::Result { + let options = if matches!(job.kind, JobKind::Script) { + &body.script_options_by_path + } else { + &body.flow_options_by_path + } + .get(&job.script_path); + + let mut args: HashMap> = serde_json::from_value(job.input.clone())?; + let use_latest_version = options.and_then(|o| o.use_latest_version).unwrap_or(false); + let input_transforms = options + .and_then(|o| o.input_transforms.as_ref()) + .map(|t| t.iter()) + .into_iter() + .flatten(); + + let latest_schema; + let schema = if use_latest_version { + latest_schema = sqlx::query_scalar!( + r#"SELECT COALESCE( + (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC), + (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow') + ) FROM v2_job jb + WHERE jb.id = $1 AND jb.workspace_id = $2 + GROUP BY jb.kind, jb.runnable_path"#, + &job.id, + &w_id + ).fetch_optional(db).await?.flatten(); + latest_schema.as_ref() + } else { + job.schema.as_ref() + }; + let schema = schema + .and_then(serde_json::Value::as_object) + .and_then(|s| s.get("properties")) + .and_then(serde_json::Value::as_object); + for (property_name, transform) in input_transforms { + let schema_has_key = schema + .map(|s| s.contains_key(property_name)) + .unwrap_or(false); + if !schema_has_key { + continue; + } + match transform { + InputTransform::Static { value } => { + args.insert(property_name.clone(), value.clone()); + } + InputTransform::Javascript { expr } => { + #[cfg(not(feature = "deno_core"))] + Err(error::Error::ExecutionErr( + format!("deno_core feature is not activated, cannot evaluate: {expr}") + .to_string(), + ))?; + + #[cfg(feature = "deno_core")] + args.insert( + property_name.clone(), + batch_rerun_compute_js_expression(expr.clone(), job.clone()).await?, + ); + } + } + } + + // Call appropriate function to push job to queue + match job.kind { + JobKind::Flow => { + let result = run_flow_by_path_inner( + authed.clone(), + db.clone(), + user_db.clone(), + w_id.clone(), + StripPath(job.script_path.clone()), + RunJobQuery { ..Default::default() }, + PushArgsOwned { extra: None, args }, + ) + .await; + if let Ok((_, uuid)) = result { + return Ok(uuid); + } + } + JobKind::Script => { + let result = if use_latest_version { + run_script_by_path_inner( + authed.clone(), + db.clone(), + user_db.clone(), + w_id.clone(), + StripPath(job.script_path.clone()), + RunJobQuery { ..Default::default() }, + PushArgsOwned { extra: None, args }, + ) + .await + } else { + run_job_by_hash_inner( + authed.clone(), + db.clone(), + user_db.clone(), + w_id.clone(), + job.script_hash, + RunJobQuery { ..Default::default() }, + PushArgsOwned { extra: None, args }, + ) + .await + }; + if let Ok((_, uuid)) = result { + return Ok(uuid); + } + } + _ => {} + } + Err(error::Error::ExecutionErr( + format!("Couldn't re-run job {}", job.id).to_string(), + )) +} + pub async fn run_flow_by_path( authed: ApiAuthed, Extension(db): Extension, Extension(user_db): Extension, Path((w_id, flow_path)): Path<(String, StripPath)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result<(StatusCode, String)> { - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_flow_path(flow_path.to_path()), + run_query.skip_preprocessor, + ) + .await?; - run_flow_by_path_inner(authed, db, user_db, w_id, flow_path, run_query, args, None).await + run_flow_by_path_inner(authed, db, user_db, w_id, flow_path, run_query, args).await } pub async fn run_flow_by_path_inner( @@ -3034,7 +3504,6 @@ pub async fn run_flow_by_path_inner( flow_path: StripPath, run_query: RunJobQuery, args: PushArgsOwned, - label_prefix: Option, ) -> error::Result<(StatusCode, String)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -3042,28 +3511,22 @@ pub async fn run_flow_by_path_inner( check_scopes(&authed, || format!("run:flow/{flow_path}"))?; let mut tx = user_db.clone().begin(&authed).await?; - let (tag, dedicated_worker, has_preprocessor, on_behalf_of_email, edited_by) = sqlx::query!( - "SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by - FROM flow - LEFT JOIN flow_version - ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 and flow.workspace_id = $2", - flow_path, - w_id - ) - .fetch_optional(&mut *tx) - .await? - .map(|x| (x.tag, x.dedicated_worker, x.has_preprocessor, x.on_behalf_of_email, x.edited_by)) - .ok_or_else(|| { - Error::NotFound(format!( - "flow not found at path {flow_path} in workspace {w_id}" - )) - })?; + + let FlowVersionInfo { + version, + tag, + dedicated_worker, + has_preprocessor, + on_behalf_of_email, + edited_by, + .. + } = get_latest_flow_version_info_for_path(&mut *tx, &w_id, &flow_path, true).await?; + drop(tx); let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let scheduled_for = run_query.get_scheduled_for(&db).await?; let (email, permissioned_as, push_authed, tx) = @@ -3090,19 +3553,18 @@ pub async fn run_flow_by_path_inner( JobPayload::Flow { path: flow_path.to_string(), dedicated_worker, + version, apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false) && has_preprocessor.unwrap_or(false), }, PushArgs { args: &args.args, extra: args.extra }, - &label_prefix - .map(|x| x + authed.display_username()) - .unwrap_or_else(|| authed.display_username().to_string()), + authed.display_username(), email, permissioned_as, scheduled_for, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -3195,7 +3657,7 @@ pub async fn restart_flow( scheduled_for, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -3218,20 +3680,19 @@ pub async fn run_script_by_path( Extension(user_db): Extension, Path((w_id, script_path)): Path<(String, StripPath)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result<(StatusCode, String)> { - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; - run_script_by_path_inner( - authed, - db, - user_db, - w_id, - script_path, - run_query, - args, - None, - ) - .await + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_script_path(script_path.to_path()), + run_query.skip_preprocessor, + ) + .await?; + + run_script_by_path_inner(authed, db, user_db, w_id, script_path, run_query, args).await } pub async fn run_script_by_path_inner( @@ -3242,7 +3703,6 @@ pub async fn run_script_by_path_inner( script_path: StripPath, run_query: RunJobQuery, args: PushArgsOwned, - label_prefix: Option, ) -> error::Result<(StatusCode, String)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -3258,7 +3718,7 @@ pub async fn run_script_by_path_inner( let scheduled_for = run_query.get_scheduled_for(&db).await?; let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(on_behalf_of) = on_behalf_of.as_ref() { @@ -3283,15 +3743,13 @@ pub async fn run_script_by_path_inner( &w_id, job_payload, PushArgs { args: &args.args, extra: args.extra }, - &label_prefix - .map(|x| x + authed.display_username()) - .unwrap_or_else(|| authed.display_username().to_string()), + authed.display_username(), email, permissioned_as, scheduled_for, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -3331,7 +3789,7 @@ pub async fn run_workflow_as_code( #[cfg(feature = "enterprise")] check_license_key_valid().await?; - check_tag_available_for_workspace(&w_id, &run_query.tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &run_query.tag, &authed).await?; if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); @@ -3358,7 +3816,7 @@ pub async fn run_workflow_as_code( path: job.script_path, language: job.language.unwrap_or_else(|| ScriptLang::Deno), lock: raw_lock, - custom_concurrency_key: windmill_queue::custom_concurrency_key(&db, job.id) + custom_concurrency_key: windmill_queue::custom_concurrency_key(&db, &job.id) .await .map_err(to_anyhow)?, concurrent_limit: job.concurrent_limit, @@ -3887,7 +4345,7 @@ pub async fn run_wait_result_job_by_path_get( Extension(db): Extension, Path((w_id, script_path)): Path<(String, StripPath)>, Query(run_query): Query, - DecodeQueries(queries): DecodeQueries, + args: RawWebhookArgs, ) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -3899,20 +4357,27 @@ pub async fn run_wait_result_job_by_path_get( x.map_err(|e| Error::internal_err(format!("Impossible to decode query payload: {e:#?}"))) }); - let mut payload_args = if let Some(payload) = payload_r { + let payload_args = if let Some(payload) = payload_r { payload? } else { HashMap::new() }; - queries.iter().for_each(|(k, v)| { - payload_args.insert(k.to_string(), v.clone()); - }); - let inner_args: HashMap> = HashMap::new(); - let args = PushArgs { extra: Some(payload_args), args: &inner_args }; + let mut args = args.process_args(&authed, &db, &w_id, None).await?; + args.body = args::Body::HashMap(payload_args); + + let script_path = script_path.to_path(); + + let args = args + .to_args_from_runnable( + &db, + &w_id, + RunnableId::from_script_path(script_path), + run_query.skip_preprocessor, + ) + .await?; check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?; - let script_path = script_path.to_path(); check_scopes(&authed, || format!("run:script/{script_path}"))?; let mut tx = user_db.clone().begin(&authed).await?; @@ -3921,7 +4386,7 @@ pub async fn run_wait_result_job_by_path_get( drop(tx); let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(on_behalf_of) = on_behalf_authed.as_ref() { @@ -3952,7 +4417,7 @@ pub async fn run_wait_result_job_by_path_get( None, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -3981,7 +4446,7 @@ pub async fn run_wait_result_flow_by_path_get( Extension(db): Extension, Path((w_id, flow_path)): Path<(String, StripPath)>, Query(run_query): Query, - DecodeQueries(queries): DecodeQueries, + args: RawWebhookArgs, ) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -3995,22 +4460,26 @@ pub async fn run_wait_result_flow_by_path_get( }) }); - let mut payload_args = if let Some(payload) = payload_r { + let payload_args = if let Some(payload) = payload_r { payload? } else { HashMap::new() }; - queries.iter().for_each(|(k, v)| { - payload_args.insert(k.to_string(), v.clone()); - }); + let mut args = args.process_args(&authed, &db, &w_id, None).await?; + args.body = args::Body::HashMap(payload_args); - let args = PushArgsOwned { extra: Some(payload_args), args: HashMap::new() }; + let args = args + .to_args_from_runnable( + &db, + &w_id, + RunnableId::from_flow_path(flow_path.to_path()), + run_query.skip_preprocessor, + ) + .await?; - run_wait_result_flow_by_path_internal( - db, run_query, flow_path, authed, user_db, args, w_id, None, - ) - .await + run_wait_result_flow_by_path_internal(db, run_query, flow_path, authed, user_db, args, w_id) + .await } pub async fn run_wait_result_script_by_path( @@ -4019,24 +4488,23 @@ pub async fn run_wait_result_script_by_path( Extension(db): Extension, Path((w_id, script_path)): Path<(String, StripPath)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_script_path(script_path.to_path()), + run_query.skip_preprocessor, + ) + .await?; - run_wait_result_script_by_path_internal( - db, - run_query, - script_path, - authed, - user_db, - w_id, - args, - None, - ) - .await + run_wait_result_script_by_path_internal(db, run_query, script_path, authed, user_db, w_id, args) + .await } pub async fn run_wait_result_script_by_path_internal( @@ -4047,7 +4515,6 @@ pub async fn run_wait_result_script_by_path_internal( user_db: UserDB, w_id: String, args: PushArgsOwned, - label_prefix: Option, ) -> error::Result { check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?; let script_path = script_path.to_path(); @@ -4056,9 +4523,10 @@ pub async fn run_wait_result_script_by_path_internal( let mut tx = user_db.clone().begin(&authed).await?; let (job_payload, tag, delete_after_use, timeout, on_behalf_of) = script_path_to_payload(script_path, &mut *tx, &w_id, run_query.skip_preprocessor).await?; + drop(tx); let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(on_behalf_of) = on_behalf_of.as_ref() { @@ -4083,15 +4551,13 @@ pub async fn run_wait_result_script_by_path_internal( &w_id, job_payload, PushArgs { args: &args.args, extra: args.extra }, - &label_prefix - .map(|x| x + authed.display_username()) - .unwrap_or_else(|| authed.display_username().to_string()), + authed.display_username(), email, permissioned_as, None, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -4119,20 +4585,29 @@ pub async fn run_wait_result_script_by_hash( Extension(db): Extension, Path((w_id, script_hash)): Path<(String, ScriptHash)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_script_hash(script_hash), + run_query.skip_preprocessor, + ) + .await?; check_queue_too_long(&db, run_query.queue_limit).await?; let hash = script_hash.0; - let ( + let mut tx = user_db.clone().begin(&authed).await?; + let ScriptHashInfo { path, tag, - custom_concurrency_key, + concurrency_key, concurrent_limit, concurrency_time_window_s, mut cache_ttl, @@ -4144,15 +4619,15 @@ pub async fn run_wait_result_script_by_hash( has_preprocessor, on_behalf_of_email, created_by, - ) = get_path_tag_limits_cache_for_hash(user_db.clone().begin(&authed).await?, &w_id, hash) - .await?; + .. + } = get_script_info_for_hash(&mut *tx, &w_id, hash).await?; if let Some(run_query_cache_ttl) = run_query.cache_ttl { cache_ttl = Some(run_query_cache_ttl); } check_scopes(&authed, || format!("run:script/{path}"))?; let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(email) = on_behalf_of_email.as_ref() { @@ -4178,7 +4653,7 @@ pub async fn run_wait_result_script_by_hash( JobPayload::ScriptHash { hash: ScriptHash(hash), path: path, - custom_concurrency_key, + custom_concurrency_key: concurrency_key, concurrent_limit: concurrent_limit, concurrency_time_window_s: concurrency_time_window_s, cache_ttl, @@ -4195,7 +4670,7 @@ pub async fn run_wait_result_script_by_hash( None, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -4223,17 +4698,23 @@ pub async fn run_wait_result_flow_by_path( Extension(db): Extension, Path((w_id, flow_path)): Path<(String, StripPath)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_flow_path(flow_path.to_path()), + run_query.skip_preprocessor, + ) + .await?; - run_wait_result_flow_by_path_internal( - db, run_query, flow_path, authed, user_db, args, w_id, None, - ) - .await + run_wait_result_flow_by_path_internal(db, run_query, flow_path, authed, user_db, args, w_id) + .await } pub async fn run_wait_result_flow_by_path_internal( @@ -4244,7 +4725,6 @@ pub async fn run_wait_result_flow_by_path_internal( user_db: UserDB, args: PushArgsOwned, w_id: String, - label_prefix: Option, ) -> error::Result { check_queue_too_long(&db, run_query.queue_limit).await?; @@ -4254,26 +4734,20 @@ pub async fn run_wait_result_flow_by_path_internal( let scheduled_for = run_query.get_scheduled_for(&db).await?; let mut tx = user_db.clone().begin(&authed).await?; - let (tag, dedicated_worker, early_return, has_preprocessor, on_behalf_of_email, edited_by) = sqlx::query!( - "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by - FROM flow - LEFT JOIN flow_version - ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 and flow.workspace_id = $2", - flow_path, - w_id - ) - .fetch_optional(&mut *tx) - .await? - .map(|x| (x.tag, x.dedicated_worker, x.early_return, x.has_preprocessor, x.on_behalf_of_email, x.edited_by)) - .ok_or_else(|| { - Error::NotFound(format!( - "flow not found at path {flow_path} in workspace {w_id}" - )) - })?; + + let FlowVersionInfo { + tag, + dedicated_worker, + early_return, + has_preprocessor, + on_behalf_of_email, + edited_by, + version, + } = get_latest_flow_version_info_for_path(&mut *tx, &w_id, &flow_path, true).await?; + drop(tx); let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(on_behalf_of_email) = on_behalf_of_email.as_ref() { @@ -4299,19 +4773,18 @@ pub async fn run_wait_result_flow_by_path_internal( JobPayload::Flow { path: flow_path.to_string(), dedicated_worker, + version, apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false) && has_preprocessor.unwrap_or(false), }, PushArgs { args: &args.args, extra: args.extra }, - &label_prefix - .map(|x| x + authed.display_username()) - .unwrap_or_else(|| authed.display_username().to_string()), + authed.display_username(), email, permissioned_as, scheduled_for, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -4348,7 +4821,7 @@ async fn run_preview_script( } let scheduled_for = run_query.get_scheduled_for(&db).await?; let tag = run_query.tag.clone().or(preview.tag.clone()); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()); let (uuid, tx) = push( @@ -4359,7 +4832,10 @@ async fn run_preview_script( Some(PreviewKind::Identity) => JobPayload::Identity, Some(PreviewKind::Noop) => JobPayload::Noop, _ => JobPayload::Code(RawCode { - hash: None, + hash: preview + .script_hash + .as_ref() + .and_then(|s| windmill_common::scripts::to_i64(s).ok()), content: preview.content.unwrap_or_default(), path: preview.path, language: preview.language.unwrap_or(ScriptLang::Deno), @@ -4427,7 +4903,7 @@ async fn run_bundle_preview_script( let scheduled_for = run_query.get_scheduled_for(&db).await?; let tag = run_query.tag.clone().or(preview.tag.clone()); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let ltx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()); let args = preview.args.unwrap_or_default(); @@ -4501,10 +4977,7 @@ async fn run_bundle_preview_script( uploaded = true; #[cfg(all(feature = "enterprise", feature = "parquet"))] - let object_store = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone(); + let object_store = windmill_common::s3_helpers::get_object_store().await; #[cfg(not(all(feature = "enterprise", feature = "parquet")))] let object_store: Option<()> = None; @@ -4756,29 +5229,23 @@ async fn add_batch_jobs( "script" => { if let Some(path) = batch_info.path { let mut tx = user_db.clone().begin(&authed).await?; - let ( - script_hash, - _tag, - custom_concurrency_key, + let ScriptHashInfo { + hash: script_hash, + concurrency_key, concurrent_limit, concurrency_time_window_s, - _cache_ttl, language, dedicated_worker, - _priority, - _delete_after_use, timeout, - _, - _, // TODO: consider on_behalf_of_email and created_by for batch jobs - _, // ------------------------------------------ - ) = get_latest_deployed_hash_for_path(&mut *tx, &w_id, &path).await?; + .. // TODO: consider on_behalf_of_email and created_by for batch jobs + } = get_latest_deployed_hash_for_path(&mut *tx, &w_id, &path).await?; ( Some(script_hash), Some(path), JobKind::Script, Some(language), dedicated_worker, - custom_concurrency_key, + concurrency_key, concurrent_limit, concurrency_time_window_s, timeout, @@ -4918,7 +5385,7 @@ async fn add_batch_jobs( raw_lock, raw_flow.map(sqlx::types::Json) as Option>, tag, - hash.map(|h| h.0), + hash, path, job_kind.clone() as JobKind, language as ScriptLang, @@ -4956,6 +5423,21 @@ async fn add_batch_jobs( .execute(&mut *tx) .await?; + sqlx::query!( + "INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) + SELECT unnest($1::uuid[]), $2, $3, $4, $5, $6, $7, $8", + &uuids, + authed.email, + authed.username, + authed.is_admin, + authed.is_operator, + &[], + &[], + w_id, + ) + .execute(&mut *tx) + .await?; + if let Some(flow_status) = flow_status { sqlx::query!( "INSERT INTO v2_job_status (id, flow_status) @@ -4968,6 +5450,13 @@ async fn add_batch_jobs( } if let Some(custom_concurrency_key) = custom_concurrency_key { + sqlx::query!( + "INSERT INTO concurrency_counter(concurrency_id, job_uuids) + VALUES ($1, '{}'::jsonb)", + &custom_concurrency_key + ) + .execute(&mut *tx) + .await?; sqlx::query!( "INSERT INTO concurrency_key (job_id, key) SELECT id, $1 FROM unnest($2::uuid[]) as id", custom_concurrency_key, @@ -4998,7 +5487,7 @@ async fn run_preview_flow_job( } let scheduled_for = run_query.get_scheduled_for(&db).await?; let tag = run_query.tag.clone().or(raw_flow.tag.clone()); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()); let (uuid, tx) = push( @@ -5041,20 +5530,19 @@ pub async fn run_job_by_hash( Extension(user_db): Extension, Path((w_id, script_hash)): Path<(String, ScriptHash)>, Query(run_query): Query, - args: WebhookArgs, + args: RawWebhookArgs, ) -> error::Result<(StatusCode, String)> { - let args = args.to_push_args_owned(&authed, &db, &w_id).await?; - run_job_by_hash_inner( - authed, - db, - user_db, - w_id, - script_hash, - run_query, - args, - None, - ) - .await + let args = args + .to_args_from_runnable( + &authed, + &db, + &w_id, + RunnableId::from_script_hash(script_hash), + run_query.skip_preprocessor, + ) + .await?; + + run_job_by_hash_inner(authed, db, user_db, w_id, script_hash, run_query, args).await } pub async fn run_job_by_hash_inner( @@ -5065,29 +5553,28 @@ pub async fn run_job_by_hash_inner( script_hash: ScriptHash, run_query: RunJobQuery, args: PushArgsOwned, - label_prefix: Option, ) -> error::Result<(StatusCode, String)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; let hash = script_hash.0; - let ( + let mut tx = user_db.clone().begin(&authed).await?; + let ScriptHashInfo { path, tag, - custom_concurrency_key, + concurrency_key, concurrent_limit, concurrency_time_window_s, mut cache_ttl, language, dedicated_worker, priority, - _delete_after_use, // not taken into account in async endpoints timeout, has_preprocessor, on_behalf_of_email, created_by, - ) = get_path_tag_limits_cache_for_hash(user_db.clone().begin(&authed).await?, &w_id, hash) - .await?; + .. // delete_after_use not taken into account in async endpoints + } = get_script_info_for_hash(&mut *tx, &w_id, hash).await?; check_scopes(&authed, || format!("run:script/{path}"))?; if let Some(run_query_cache_ttl) = run_query.cache_ttl { cache_ttl = Some(run_query_cache_ttl); @@ -5095,7 +5582,7 @@ pub async fn run_job_by_hash_inner( let scheduled_for = run_query.get_scheduled_for(&db).await?; let tag = run_query.tag.clone().or(tag); - check_tag_available_for_workspace(&w_id, &tag, &authed).await?; + check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?; let (email, permissioned_as, push_authed, tx) = if let Some(email) = on_behalf_of_email.as_ref() { @@ -5121,7 +5608,7 @@ pub async fn run_job_by_hash_inner( JobPayload::ScriptHash { hash: ScriptHash(hash), path: path, - custom_concurrency_key, + custom_concurrency_key: concurrency_key, concurrent_limit: concurrent_limit, concurrency_time_window_s: concurrency_time_window_s, cache_ttl, @@ -5132,15 +5619,13 @@ pub async fn run_job_by_hash_inner( && has_preprocessor.unwrap_or(false), }, PushArgs { args: &args.args, extra: args.extra }, - &label_prefix - .map(|x| x + authed.display_username()) - .unwrap_or_else(|| authed.display_username().to_string()), + authed.display_username(), email, permissioned_as, scheduled_for, None, run_query.parent_job, - run_query.root_job.or(run_query.parent_job), + run_query.root_job, run_query.job_id, false, false, @@ -5190,7 +5675,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) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { let file = os .get(&object_store::path::Path::from(format!("logs/{file_p}"))) .await; @@ -5315,10 +5800,27 @@ pub fn filter_list_completed_query( } if let Some(label) = &lq.label { - let mut wh = format!("result->'wm_labels' ? "); - wh.push_str(&format!("'{}'", &label.replace("'", "''"))); - sqlb.and_where("result ? 'wm_labels'"); - sqlb.and_where(&wh); + if lq.allow_wildcards.unwrap_or(false) { + let wh = format!( + "EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE jsonb_typeof(result->'wm_labels') = 'array' AND label LIKE '{}')", + &label.replace("*", "%").replace("'", "''") + ); + sqlb.and_where("result ? 'wm_labels'"); + sqlb.and_where(&wh); + } else { + let mut wh = format!("result->'wm_labels' ? "); + wh.push_str(&format!("'{}'", &label.replace("'", "''"))); + sqlb.and_where("result ? 'wm_labels'"); + sqlb.and_where(&wh); + } + } + + if let Some(worker) = &lq.worker { + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job_completed.worker", worker.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job_completed.worker", "?".bind(worker)); + } } if w_id != "admins" || !lq.all_workspaces.is_some_and(|x| x) { @@ -5340,8 +5842,13 @@ pub fn filter_list_completed_query( sqlb.and_where_eq("runnable_id", "?".bind(h)); } if let Some(t) = &lq.tag { - sqlb.and_where_eq("tag", "?".bind(t)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + } } + if let Some(cb) = &lq.created_by { sqlb.and_where_eq("created_by", "?".bind(cb)); } @@ -5368,6 +5875,7 @@ pub fn filter_list_completed_query( sqlb.and_where_le("started_at", "?".bind(&dt.to_rfc3339())); } if let Some(dt) = &lq.created_or_started_after { + sqlb.and_where_ge("created_at", "?".bind(&dt.to_rfc3339())); sqlb.and_where_ge("started_at", "?".bind(&dt.to_rfc3339())); } @@ -5426,7 +5934,7 @@ pub fn filter_list_completed_query( pub fn list_completed_jobs_query( w_id: &str, - per_page: usize, + per_page: Option, offset: usize, lq: &ListCompletedQuery, fields: &[&str], @@ -5437,11 +5945,16 @@ pub fn list_completed_jobs_query( .fields(fields) .order_by("v2_job.created_at", lq.order_desc.unwrap_or(true)) .offset(offset) - .limit(per_page) .clone(); + if let Some(per_page) = per_page { + sqlb.limit(per_page); + } if let Some(tags) = tags { - sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::>()); + sqlb.and_where_in( + "v2_job.tag", + &tags.iter().map(|x| quote(x)).collect::>(), + ); } filter_list_completed_query(sqlb, lq, w_id, join_outstanding_wait_times) @@ -5479,6 +5992,8 @@ pub struct ListCompletedQuery { pub label: Option, pub is_not_schedule: Option, pub concurrency_key: Option, + pub worker: Option, + pub allow_wildcards: Option, } async fn list_completed_jobs( @@ -5494,7 +6009,7 @@ async fn list_completed_jobs( let sql = list_completed_jobs_query( &w_id, - per_page, + Some(per_page), offset, &lq, &[ @@ -5799,6 +6314,7 @@ async fn get_completed_job_result_maybe( async fn delete_completed_job<'a>( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, id)): Path<(String, Uuid)>, ) -> error::Result { check_scopes(&authed, || format!("jobs:deletejob"))?; @@ -5807,9 +6323,8 @@ async fn delete_completed_job<'a>( require_admin(authed.is_admin, &authed.username)?; let tags = get_scope_tags(&authed); - let job_o = sqlx::query_as::<_, CompletedJob>( - "WITH mark_as_deleted AS ( - UPDATE v2_job_completed c SET + let job_o = sqlx::query_scalar!( + "UPDATE v2_job_completed c SET result = NULL, deleted = TRUE FROM v2_job j @@ -5818,15 +6333,15 @@ async fn delete_completed_job<'a>( AND c.workspace_id = $2 AND ($3::TEXT[] IS NULL OR tag = ANY($3)) RETURNING c.id - ) SELECT * FROM v2_as_completed_job WHERE id = (SELECT id FROM mark_as_deleted)", + ", + id, + &w_id, + tags.as_ref().map(|v| v.as_slice()) as Option<&[&str]>, ) - .bind(id) - .bind(&w_id) - .bind(tags.as_ref().map(|v| v.as_slice())) .fetch_optional(&mut *tx) .await?; - let cj = not_found_if_none(job_o, "Completed Job", id.to_string())?; + not_found_if_none(job_o, "Completed Job", id.to_string())?; sqlx::query!("UPDATE v2_job SET args = NULL WHERE id = $1", id) .execute(&mut *tx) @@ -5847,9 +6362,5 @@ async fn delete_completed_job<'a>( .await?; tx.commit().await?; - - let cj = format_completed_job_result(cj); - - let response = Json(cj).into_response(); - Ok(response) + return get_completed_job(OptAuthed(Some(authed)), Extension(db), Path((w_id, id))).await; } diff --git a/backend/windmill-api/src/kafka_triggers_ee.rs b/backend/windmill-api/src/kafka_triggers_oss.rs similarity index 76% rename from backend/windmill-api/src/kafka_triggers_ee.rs rename to backend/windmill-api/src/kafka_triggers_oss.rs index 0a24151ae3..9d698e2fbf 100644 --- a/backend/windmill-api/src/kafka_triggers_ee.rs +++ b/backend/windmill-api/src/kafka_triggers_oss.rs @@ -1,14 +1,24 @@ +#[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<()>, @@ -17,9 +27,11 @@ 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, @@ -39,4 +51,4 @@ pub struct KafkaTrigger { #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, pub enabled: bool, -} \ No newline at end of file +} diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 843a409906..7c274af630 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -8,15 +8,20 @@ use crate::db::ApiAuthed; #[cfg(feature = "enterprise")] -use crate::ee::ExternalJwks; +use crate::ee_oss::ExternalJwks; #[cfg(feature = "embedding")] use crate::embeddings::load_embeddings_db; #[cfg(feature = "oauth2")] -use crate::oauth2_ee::AllClients; +use crate::oauth2_oss::AllClients; #[cfg(feature = "oauth2")] -use crate::oauth2_ee::SlackVerifier; +use crate::oauth2_oss::SlackVerifier; #[cfg(feature = "smtp")] -use crate::smtp_server_ee::SmtpServer; +use crate::smtp_server_oss::SmtpServer; + +#[cfg(feature = "mcp")] +use crate::mcp::{extract_and_store_workspace_id, setup_mcp_server, shutdown_mcp_server}; +#[cfg(feature = "mcp")] +use rmcp::transport::streamable_http_server::session::local::LocalSessionManager; use crate::tracing_init::MyOnFailure; use crate::{ @@ -24,13 +29,17 @@ use crate::{ users::OptAuthed, webhook_util::WebhookShared, }; +#[cfg(feature = "agent_worker_server")] +use agent_workers_oss::AgentCache; use anyhow::Context; use argon2::Argon2; use axum::extract::DefaultBodyLimit; use axum::{middleware::from_extractor, routing::get, routing::post, Extension, Router}; +use axum::response::Response; +use axum::http::HeaderValue; +use axum::body::Body; use db::DB; -use http::HeaderValue; use reqwest::Client; #[cfg(feature = "oauth2")] use std::collections::HashMap; @@ -53,20 +62,26 @@ use windmill_common::db::UserDB; use windmill_common::worker::CLOUD_HOSTED; use windmill_common::{utils::GIT_VERSION, BASE_URL, INSTANCE_NAME}; -use crate::scim_ee::has_scim_token; +use crate::scim_oss::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 ai; mod apps; -mod args; +pub mod args; mod audit; -mod auth; +pub mod auth; mod capture; 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; @@ -74,48 +89,100 @@ mod folders; mod granular_acls; mod groups; #[cfg(feature = "http_trigger")] -mod http_triggers; -mod indexer_ee; +mod http_trigger_args; +#[cfg(feature = "http_trigger")] +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 inputs; mod integration; #[cfg(feature = "postgres_trigger")] mod postgres_triggers; +pub mod openapi; + +mod approvals; +#[cfg(all(feature = "enterprise", feature = "private"))] +pub mod apps_ee; #[cfg(feature = "enterprise")] -mod apps_ee; +mod apps_oss; +#[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))] +pub mod gcp_triggers_ee; +#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] +mod gcp_triggers_oss; +#[cfg(all(feature = "enterprise", feature = "private"))] +pub mod git_sync_ee; +#[cfg(feature = "enterprise")] +mod git_sync_oss; +#[cfg(all(feature = "parquet", feature = "private"))] +pub mod job_helpers_ee; #[cfg(feature = "parquet")] -mod job_helpers_ee; +mod job_helpers_oss; 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_ee; +mod kafka_triggers_oss; #[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_ee; -#[cfg(feature = "oauth2")] +mod nats_triggers_oss; +#[cfg(all(feature = "oauth2", feature = "private"))] pub mod oauth2_ee; -mod oidc_ee; +#[cfg(feature = "oauth2")] +pub mod oauth2_oss; +#[cfg(feature = "private")] +pub mod oidc_ee; +mod oidc_oss; mod raw_apps; mod resources; -mod saml_ee; +#[cfg(feature = "private")] +pub mod saml_ee; +mod saml_oss; mod schedule; -mod scim_ee; +#[cfg(feature = "private")] +pub mod scim_ee; +mod scim_oss; 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_ee; +mod smtp_server_oss; +#[cfg(all(feature = "enterprise", feature = "sqs_trigger", feature = "private"))] +pub mod sqs_triggers_ee; #[cfg(all(feature = "enterprise", feature = "sqs_trigger"))] -mod sqs_triggers_ee; +mod sqs_triggers_oss; +#[cfg(feature = "private")] +pub mod teams_approvals_ee; +mod teams_approvals_oss; +mod trigger_helpers; + mod static_assets; -mod stripe_ee; -mod teams_ee; +#[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 tracing_init; mod triggers; mod users; -mod users_ee; +#[cfg(feature = "private")] +pub mod users_ee; +mod users_oss; mod utils; mod variables; pub mod webhook_util; @@ -123,9 +190,14 @@ pub mod webhook_util; mod websocket_triggers; mod workers; mod workspaces; -mod workspaces_ee; +#[cfg(feature = "private")] +pub mod workspaces_ee; mod workspaces_export; mod workspaces_extra; +mod workspaces_oss; + +#[cfg(feature = "mcp")] +mod mcp; pub const DEFAULT_BODY_LIMIT: usize = 2097152 * 100; // 200MB @@ -166,6 +238,7 @@ lazy_static::lazy_static! { } + // Compliance with cloud events spec. pub async fn add_webhook_allowed_origin( req: axum::extract::Request, @@ -188,6 +261,7 @@ pub async fn add_webhook_allowed_origin( next.run(req).await } + #[cfg(not(feature = "tantivy"))] type IndexReader = (); @@ -195,19 +269,20 @@ type IndexReader = (); type ServiceLogIndexReader = (); #[cfg(feature = "tantivy")] -type IndexReader = windmill_indexer::completed_runs_ee::IndexReader; +type IndexReader = windmill_indexer::completed_runs_oss::IndexReader; #[cfg(feature = "tantivy")] -type ServiceLogIndexReader = windmill_indexer::service_logs_ee::ServiceLogIndexReader; +type ServiceLogIndexReader = windmill_indexer::service_logs_oss::ServiceLogIndexReader; pub async fn run_server( db: DB, job_index_reader: Option, log_index_reader: Option, addr: SocketAddr, - mut rx: tokio::sync::broadcast::Receiver<()>, + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, port_tx: tokio::sync::oneshot::Sender, server_mode: bool, - #[cfg(feature = "smtp")] base_internal_url: String, + mcp_mode: bool, + _base_internal_url: String, ) -> anyhow::Result<()> { let user_db = UserDB::new(db.clone()); @@ -241,7 +316,10 @@ pub async fn run_server( .layer(Extension(log_index_reader)) // .layer(Extension(index_writer)) .layer(CookieManagerLayer::new()) - .layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone()))) + .layer(Extension(WebhookShared::new( + killpill_rx.resubscribe(), + db.clone(), + ))) .layer(DefaultBodyLimit::max( REQUEST_SIZE_LIMIT.read().await.clone(), )); @@ -251,7 +329,7 @@ pub async fn run_server( .allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]) .allow_origin(Any); - let sp_extension = Arc::new(saml_ee::build_sp_extension().await?); + let sp_extension = Arc::new(saml_oss::build_sp_extension().await?); if server_mode { #[cfg(feature = "embedding")] @@ -274,7 +352,7 @@ pub async fn run_server( db: db.clone(), user_db: user_db, auth_cache: auth_cache.clone(), - base_internal_url: base_internal_url.clone(), + base_internal_url: _base_internal_url.clone(), }); if let Err(err) = smtp_server.start_listener_thread(addr).await { tracing::error!("Error starting SMTP server: {err:#}"); @@ -290,7 +368,7 @@ pub async fn run_server( let job_helpers_service = { #[cfg(feature = "parquet")] { - job_helpers_ee::workspaced_service() + job_helpers_oss::workspaced_service() } #[cfg(not(feature = "parquet"))] @@ -302,7 +380,7 @@ pub async fn run_server( let kafka_triggers_service = { #[cfg(all(feature = "enterprise", feature = "kafka"))] { - kafka_triggers_ee::workspaced_service() + kafka_triggers_oss::workspaced_service() } #[cfg(not(all(feature = "enterprise", feature = "kafka")))] @@ -314,7 +392,7 @@ pub async fn run_server( let nats_triggers_service = { #[cfg(all(feature = "enterprise", feature = "nats"))] { - nats_triggers_ee::workspaced_service() + nats_triggers_oss::workspaced_service() } #[cfg(not(all(feature = "enterprise", feature = "nats")))] @@ -335,10 +413,22 @@ pub async fn run_server( } }; + let gcp_triggers_service = { + #[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] + { + gcp_triggers_oss::workspaced_service() + } + + #[cfg(not(all(feature = "enterprise", feature = "gcp_trigger")))] + { + Router::new() + } + }; + let sqs_triggers_service = { #[cfg(all(feature = "enterprise", feature = "sqs_trigger"))] { - sqs_triggers_ee::workspaced_service() + sqs_triggers_oss::workspaced_service() } #[cfg(not(all(feature = "enterprise", feature = "sqs_trigger")))] @@ -367,6 +457,12 @@ pub async fn run_server( Router::new() }; + #[cfg(feature = "http_trigger")] + { + let http_killpill_rx = killpill_rx.resubscribe(); + http_triggers::refresh_routers_loop(&db, http_killpill_rx).await; + } + let postgres_triggers_service = { #[cfg(feature = "postgres_trigger")] { @@ -377,44 +473,86 @@ pub async fn run_server( Router::new() }; - if !*CLOUD_HOSTED { + if !*CLOUD_HOSTED && server_mode && !mcp_mode { #[cfg(feature = "websocket")] { - let ws_killpill_rx = rx.resubscribe(); + let ws_killpill_rx = killpill_rx.resubscribe(); websocket_triggers::start_websockets(db.clone(), ws_killpill_rx); } #[cfg(all(feature = "enterprise", feature = "kafka"))] { - let kafka_killpill_rx = rx.resubscribe(); - kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx); + let kafka_killpill_rx = killpill_rx.resubscribe(); + kafka_triggers_oss::start_kafka_consumers(db.clone(), kafka_killpill_rx); } #[cfg(all(feature = "enterprise", feature = "nats"))] { - let nats_killpill_rx = rx.resubscribe(); - nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx); + let nats_killpill_rx = killpill_rx.resubscribe(); + nats_triggers_oss::start_nats_consumers(db.clone(), nats_killpill_rx); } #[cfg(feature = "postgres_trigger")] { - let db_killpill_rx = rx.resubscribe(); + let db_killpill_rx = killpill_rx.resubscribe(); postgres_triggers::start_database(db.clone(), db_killpill_rx); } - + #[cfg(feature = "mqtt_trigger")] { - let mqtt_killpill_rx = rx.resubscribe(); + let mqtt_killpill_rx = killpill_rx.resubscribe(); mqtt_triggers::start_mqtt_consumer(db.clone(), mqtt_killpill_rx); } #[cfg(all(feature = "enterprise", feature = "sqs_trigger"))] { - let sqs_killpill_rx = rx.resubscribe(); - sqs_triggers_ee::start_sqs(db.clone(), sqs_killpill_rx); + let sqs_killpill_rx = killpill_rx.resubscribe(); + sqs_triggers_oss::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); } } + let listener = tokio::net::TcpListener::bind(addr) + .await + .context("binding main windmill server")?; + let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000); + let ip = listener + .local_addr() + .map(|x| x.ip().to_string()) + .unwrap_or("localhost".to_string()); + + // Setup MCP server + #[allow(unused_variables)] + let (mcp_router, mcp_session_manager) = { + #[cfg(feature = "mcp")] + if server_mode || mcp_mode { + let (mcp_router, mcp_session_manager) = setup_mcp_server().await?; + let mcp_middleware = axum::middleware::from_fn(extract_and_store_workspace_id); + (mcp_router.layer(mcp_middleware), Some(mcp_session_manager)) + } else { + (Router::new(), Option::>::None) + } + + #[cfg(not(feature = "mcp"))] + (Router::new(), Option::<()>::None) + }; + + #[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()) + } else { + (Router::new(), vec![], None) + }; + + #[cfg(feature = "agent_worker_server")] + let agent_cache = Arc::new(AgentCache::new()); + // build our application with a route let app = Router::new() .nest( @@ -445,7 +583,7 @@ pub async fn run_server( .nest("/oauth", { #[cfg(feature = "oauth2")] { - oauth2_ee::workspaced_service() + oauth2_oss::workspaced_service() } #[cfg(not(feature = "oauth2"))] @@ -462,13 +600,15 @@ pub async fn run_server( ) .nest("/variables", variables::workspaced_service()) .nest("/workspaces", workspaces::workspaced_service()) - .nest("/oidc", oidc_ee::workspaced_service()) + .nest("/oidc", oidc_oss::workspaced_service()) + .nest("/openapi", openapi::openapi_service()) .nest("/http_triggers", http_triggers_service) .nest("/websocket_triggers", websocket_triggers_service) .nest("/kafka_triggers", kafka_triggers_service) .nest("/nats_triggers", nats_triggers_service) .nest("/mqtt_triggers", mqtt_triggers_service) .nest("/sqs_triggers", sqs_triggers_service) + .nest("/gcp_triggers", gcp_triggers_service) .nest("/postgres_triggers", postgres_triggers_service), ) .nest("/workspaces", workspaces::global_service()) @@ -487,22 +627,24 @@ pub async fn run_server( .nest("/apps", apps::global_service().layer(cors.clone())) .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::()) .route_layer(from_extractor::()) .nest("/jobs", jobs::global_root_service()) .nest( "/srch/w/:workspace_id/index", - indexer_ee::workspaced_service(), + indexer_oss::workspaced_service(), ) - .nest("/srch/index", indexer_ee::global_service()) - .nest("/oidc", oidc_ee::global_service()) + .nest("/srch/index", indexer_oss::global_service()) + .nest("/oidc", oidc_oss::global_service()) .nest( "/saml", - saml_ee::global_service().layer(Extension(Arc::clone(&sp_extension))), + saml_oss::global_service().layer(Extension(Arc::clone(&sp_extension))), ) .nest( "/scim", - scim_ee::global_service() + scim_oss::global_service() .route_layer(axum::middleware::from_fn(has_scim_token)), ) .nest("/concurrency_groups", concurrency_groups::global_service()) @@ -510,7 +652,7 @@ pub async fn run_server( .nest("/apps_u", { #[cfg(feature = "enterprise")] { - apps_ee::global_unauthed_service() + apps_oss::global_unauthed_service() } #[cfg(not(feature = "enterprise"))] @@ -524,6 +666,28 @@ pub async fn run_server( .layer(from_extractor::()) .layer(cors.clone()), ) + .nest("/mcp/w/:workspace_id/sse", mcp_router) + .layer(from_extractor::()) + .nest("/agent_workers", { + #[cfg(feature = "agent_worker_server")] + { + agent_workers_oss::global_service().layer(Extension(agent_cache.clone())) + } + #[cfg(not(feature = "agent_worker_server"))] + { + Router::new() + } + }) + .nest("/w/:workspace_id/agent_workers", { + #[cfg(feature = "agent_worker_server")] + { + agent_workers_router.layer(Extension(agent_cache.clone())) + } + #[cfg(not(feature = "agent_worker_server"))] + { + Router::new() + } + }) .nest( "/w/:workspace_id/jobs_u", jobs::workspace_unauthed_service().layer(cors.clone()), @@ -532,7 +696,7 @@ pub async fn run_server( .nest("/teams", { #[cfg(feature = "enterprise")] { - teams_ee::teams_service() + teams_oss::teams_service() } #[cfg(not(feature = "enterprise"))] @@ -544,6 +708,28 @@ pub async fn run_server( "/w/:workspace_id/jobs/slack_approval/:job_id", get(slack_approvals::request_slack_approval), ) + .route( + "/w/:workspace_id/jobs/teams_approval/:job_id", + get(teams_approvals_oss::request_teams_approval), + ) + .nest("/w/:workspace_id/github_app", { + #[cfg(feature = "enterprise")] + { + git_sync_oss::workspaced_service() + } + + #[cfg(not(feature = "enterprise"))] + Router::new() + }) + .nest("/github_app", { + #[cfg(feature = "enterprise")] + { + git_sync_oss::global_service() + } + + #[cfg(not(feature = "enterprise"))] + Router::new() + }) .nest( "/w/:workspace_id/resources_u", resources::public_service().layer(cors.clone()), @@ -559,7 +745,7 @@ pub async fn run_server( .nest("/oauth", { #[cfg(feature = "oauth2")] { - oauth2_ee::global_service().layer(Extension(Arc::clone(&sp_extension))) + oauth2_oss::global_service().layer(Extension(Arc::clone(&sp_extension))) } #[cfg(not(feature = "oauth2"))] @@ -580,6 +766,20 @@ pub async fn run_server( } .layer(from_extractor::()), ) + .nest( + "/gcp/w/:workspace_id", + { + #[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] + { + gcp_triggers_oss::gcp_push_route_handler() + } + #[cfg(not(all(feature = "enterprise", feature = "gcp_trigger")))] + { + Router::new() + } + } + .layer(from_extractor::()), + ) .route("/version", get(git_v)) .route("/uptodate", get(is_up_to_date)) .route("/ee_license", get(ee_license)) @@ -600,12 +800,6 @@ pub async fn run_server( .on_failure(MyOnFailure {}), ) }; - let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000); - let ip = listener - .local_addr() - .map(|x| x.ip().to_string()) - .unwrap_or("localhost".to_string()); let server = axum::serve(listener, app.into_make_service()); @@ -621,11 +815,29 @@ pub async fn run_server( .expect("Failed to send port"); let server = server.with_graceful_shutdown(async move { - rx.recv().await.ok(); + killpill_rx.recv().await.ok(); + #[cfg(feature = "agent_worker_server")] + if let Some(agent_workers_killpill_tx) = agent_workers_killpill_tx { + if let Err(e) = agent_workers_killpill_tx.kill().await { + tracing::error!("Error killing agent workers: {e:#}"); + } + } tracing::info!("Graceful shutdown of server"); + + #[cfg(feature = "mcp")] + if let Some(mcp_session_manager) = mcp_session_manager { + shutdown_mcp_server(mcp_session_manager).await; + tracing::info!("MCP server shutdown"); + } }); server.await?; + #[cfg(feature = "agent_worker_server")] + for (i, bg_processor) in agent_workers_bg_processor.into_iter().enumerate() { + tracing::info!("server off. shutting down agent worker bg processor {i}"); + bg_processor.await?; + tracing::info!("agent worker bg processor {i} shut down"); + } Ok(()) } @@ -673,7 +885,7 @@ async fn ee_license() -> &'static str { #[cfg(feature = "enterprise")] async fn ee_license() -> String { - use windmill_common::ee::{LICENSE_KEY_ID, LICENSE_KEY_VALID}; + use windmill_common::ee_oss::{LICENSE_KEY_ID, LICENSE_KEY_VALID}; if *LICENSE_KEY_VALID.read().await { LICENSE_KEY_ID.read().await.clone() @@ -682,12 +894,18 @@ async fn ee_license() -> String { } } -async fn openapi() -> &'static str { - include_str!("../openapi-deref.yaml") +async fn openapi() -> Response { + Response::builder() + .header("content-type", "application/yaml") + .body(Body::from(include_str!("../openapi-deref.yaml"))) + .unwrap() } -async fn openapi_json() -> &'static str { - include_str!("../openapi-deref.json") +async fn openapi_json() -> Response { + Response::builder() + .header("content-type", "application/json") + .body(Body::from(include_str!("../openapi-deref.json"))) + .unwrap() } pub async fn migrate_db(db: &DB) -> anyhow::Result>> { diff --git a/backend/windmill-api/src/mcp.rs b/backend/windmill-api/src/mcp.rs new file mode 100644 index 0000000000..530123ec04 --- /dev/null +++ b/backend/windmill-api/src/mcp.rs @@ -0,0 +1,1220 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; + +use axum::body::to_bytes; +use axum::Router; +use axum::{extract::Path, http::Request, middleware::Next, response::Response}; +use rmcp::{ + handler::server::ServerHandler, + model::*, + service::{RequestContext, RoleServer}, + Error, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sql_builder::prelude::*; +use sqlx::FromRow; +use tokio::try_join; +use windmill_common::db::UserDB; +use windmill_common::worker::to_raw_value; +use windmill_common::{DB, HUB_BASE_URL}; + +use windmill_common::scripts::{get_full_hub_script_by_path, Schema}; + +use crate::db::ApiAuthed; +use crate::jobs::{ + run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, +}; +use crate::HTTP_CLIENT; +use rmcp::transport::streamable_http_server::{ + session::local::LocalSessionManager, SessionManager, StreamableHttpService, +}; +use windmill_common::utils::{query_elems_from_hub, StripPath}; + +/// Transforms the path for workspace scripts/flows. +/// +/// This function takes a path and a type string. +/// It then formats the transformed path with the type prefix. +/// This is used when listing, because we can't have names with slashes. +/// Because we replace slashes with underscores, we also need to escape underscores. +/// +/// # Parameters +/// - `path`: The path to transform. +/// - `type_str`: The type of the item (script or flow). +/// +/// # Returns +/// - `String`: The transformed path. +fn transform_path(path: &str, type_str: &str) -> String { + // Only apply special underscore escaping for paths starting with "f/" + let transformed = if path.starts_with("f/") { + let escaped_path = path.replace('_', "__"); + escaped_path.replace('/', "_") + } else { + path.replace('/', "_") + }; + + // first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit + format!("{}-{}", &type_str[..1], transformed) +} + +fn convert_schema_to_schema_type(schema: Option) -> SchemaType { + let schema_obj = if let Some(ref s) = schema { + match serde_json::from_str::(s.0.get()) { + Ok(val) => val, + Err(_) => SchemaType::default(), + } + } else { + SchemaType::default() + }; + schema_obj +} + +trait ToolableItem { + fn get_path_or_id(&self) -> String; + fn get_summary(&self) -> &str; + fn get_description(&self) -> &str; + fn get_schema(&self) -> SchemaType; + fn is_hub(&self) -> bool; + fn item_type(&self) -> &'static str; + fn get_integration_type(&self) -> Option; +} + +impl ToolableItem for ScriptInfo { + fn get_path_or_id(&self) -> String { + transform_path(&self.path, "script") + } + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + fn get_schema(&self) -> SchemaType { + convert_schema_to_schema_type(self.schema.clone()) + } + fn is_hub(&self) -> bool { + false + } + fn item_type(&self) -> &'static str { + "script" + } + fn get_integration_type(&self) -> Option { + None + } +} + +impl ToolableItem for FlowInfo { + fn get_path_or_id(&self) -> String { + transform_path(&self.path, "flow") + } + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + fn get_schema(&self) -> SchemaType { + convert_schema_to_schema_type(self.schema.clone()) + } + fn is_hub(&self) -> bool { + false + } + fn item_type(&self) -> &'static str { + "flow" + } + fn get_integration_type(&self) -> Option { + None + } +} + +impl ToolableItem for HubScriptInfo { + fn get_path_or_id(&self) -> String { + let id = self.version_id; + let summary = self.summary.as_deref().unwrap_or("No summary"); + format!("hs-{}-{}", id, summary.replace(" ", "_")) + } + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + fn get_schema(&self) -> SchemaType { + match serde_json::from_value::(self.schema.clone().unwrap_or_default()) { + Ok(schema_type) => schema_type, + Err(_) => SchemaType::default(), + } + } + fn is_hub(&self) -> bool { + true + } + fn item_type(&self) -> &'static str { + "script" + } + fn get_integration_type(&self) -> Option { + self.app.clone() + } +} + +#[derive(Clone)] +pub struct Runner {} + +#[derive(Serialize, Deserialize, Debug)] +struct HubResponse { + asks: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +struct HubScriptInfo { + version_id: u64, + summary: Option, + description: Option, + schema: Option, + app: Option, +} + +#[derive(Serialize, FromRow, Deserialize, Debug, Clone)] +struct SchemaType { + r#type: String, + properties: std::collections::HashMap, + required: Vec, +} + +impl Default for SchemaType { + fn default() -> Self { + Self { + r#type: "object".to_string(), + properties: std::collections::HashMap::new(), + required: vec![], + } + } +} + +#[derive(Serialize, FromRow, Debug)] +struct ScriptInfo { + path: String, + summary: Option, + description: Option, + schema: Option, +} + +#[derive(Serialize, FromRow)] +struct ItemSchema { + schema: Option, +} + +#[derive(Serialize, FromRow, Debug)] +struct FlowInfo { + path: String, + summary: Option, + description: Option, + schema: Option, +} + +#[derive(Serialize, FromRow, Debug)] +struct ResourceInfo { + path: String, + description: Option, + resource_type: String, +} + +#[derive(Serialize, FromRow, Debug, Clone)] +struct ResourceType { + name: String, + description: Option, +} + +impl Runner { + pub fn new() -> Self { + Self {} + } + + async fn get_item_schema( + path: &str, + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + item_type: &str, + ) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); + sqlb.fields(&["o.schema"]); + sqlb.and_where("o.path = ?".bind(&path)); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + sqlb.and_where("o.archived = false"); + sqlb.and_where("o.draft_only IS NOT TRUE"); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let item = sqlx::query_as::<_, ItemSchema>(&sql) + .fetch_one(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("failed to fetch item schema: {}", _e); + Error::internal_error("failed to fetch item schema", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(item.schema) + } + + /// Reverses the transformation of a path. + /// + /// This function takes a transformed path and reverses the transformation applied by `transform_path`. + /// It checks if the path starts with "h" (indicating a Hub script) and removes the prefix if present. + /// It then determines the type of the item (script or flow) based on the prefix. + /// This is used in call_tool to get the original path, and the type of the item. + /// + /// # Parameters + /// - `transformed_path`: The transformed path to reverse. + /// + /// # Returns + /// - `Result<(&str, String, bool), String>`: A tuple containing the original path, the type of the item, and a boolean indicating if it's a Hub script. + /// - `Err(String)`: If the path is invalid. + fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool), String> { + let is_hub = transformed_path.starts_with("h"); + let transformed_path = if is_hub { + transformed_path[1..].to_string() + } else { + transformed_path.to_string() + }; + let type_str = if transformed_path.starts_with("s-") { + "script" + } else if transformed_path.starts_with("f-") { + "flow" + } else { + return Err(format!( + "Invalid prefix in transformed path: {}", + transformed_path + )); + }; + + let mangled_path = &transformed_path[2..]; + + // Check if this path was previously transformed with special underscore handling + let is_special_path = mangled_path.starts_with("f_"); + + let original_path = if is_hub { + let parts = mangled_path.split("-").collect::>(); + parts[0].to_string() + } else if is_special_path { + const TEMP_PLACEHOLDER: &str = "@@UNDERSCORE@@"; + let path_with_placeholder = mangled_path.replace("__", TEMP_PLACEHOLDER); + let path_with_slashes = path_with_placeholder.replace('_', "/"); + path_with_slashes.replace(TEMP_PLACEHOLDER, "_") + } else { + mangled_path.replacen('_', "/", 2) + }; + + Ok((type_str, original_path, is_hub)) + } + + async fn inner_get_resources_types( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + ) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from("resource_type as o"); + sqlb.fields(&["o.name", "o.description"]); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, ResourceType>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch resource types: {}", _e); + Error::internal_error("failed to fetch resource types", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(rows) + } + + async fn inner_get_resources( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + resource_type: &str, + ) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from("resource as o"); + sqlb.fields(&["o.path", "o.description", "o.resource_type"]); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + sqlb.and_where("o.resource_type = ?".bind(&resource_type)); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, ResourceInfo>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch resources: {}", _e); + Error::internal_error("failed to fetch resources", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + + Ok(rows) + } + + async fn inner_get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Send + Unpin>( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + scope_type: &str, + item_type: &str, + ) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); + let fields = vec!["o.path", "o.summary", "o.description", "o.schema"]; + sqlb.fields(&fields); + if scope_type == "favorites" { + sqlb.join("favorite") + .on("favorite.favorite_kind = ? AND favorite.workspace_id = o.workspace_id AND favorite.path = o.path AND favorite.usr = ?".bind(&item_type) + .bind(&authed.username)); + } + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)) + .and_where("o.archived = false") + .and_where("o.draft_only IS NOT TRUE"); + + if item_type == "script" { + sqlb.and_where("(o.no_main_func IS NOT TRUE OR o.no_main_func IS NULL)"); + } + + sqlb.order_by( + if item_type == "flow" { + "o.edited_at" + } else { + "o.created_at" + }, + false, + ) + .limit(100); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, T>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch {}: {}", item_type, _e); + Error::internal_error(format!("failed to fetch {}", item_type), None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(rows) + } + + async fn inner_get_scripts_from_hub( + db: &DB, + scope_integrations: Option<&str>, + ) -> Result, Error> { + let query_params = Some(vec![ + ("limit", "100".to_string()), + ("with_schema", "true".to_string()), + ("apps", scope_integrations.unwrap_or("").to_string()), + ]); + let url = format!("{}/scripts/top", *HUB_BASE_URL.read().await); + let (_status_code, _headers, response) = + query_elems_from_hub(&HTTP_CLIENT, &url, query_params, &db) + .await + .map_err(|e| { + tracing::error!("Failed to get items from hub: {}", e); + Error::internal_error(format!("Failed to get items from hub: {}", e), None) + })?; + let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| { + tracing::error!("Failed to read response body: {}", e); + Error::internal_error(format!("Failed to read response body: {}", e), None) + })?; + let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { + tracing::error!("Failed to decode response body: {}", e); + Error::internal_error(format!("Failed to decode response body: {}", e), None) + })?; + let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| { + tracing::error!("Failed to parse hub response: {}", e); + Error::internal_error(format!("Failed to parse hub response: {}", e), None) + })?; + + Ok(hub_response.asks) + } + + /// Transforms a value if it's an object. + /// + /// This function takes a key and a value, and a schema object. + /// If the value is a string that starts with "$res:", it returns the value as is. + /// Otherwise, it checks if the key is defined in the schema and if it's an object type. + /// If it is, it transforms the value to a string. This is because some clients do not support object types. + /// # Parameters + /// - `key`: The key of the value to transform. + /// - `value`: The value to transform. + /// - `schema_obj`: The schema object. + /// + /// # Returns + /// - `Value`: The transformed value. + fn transform_value_if_object( + key: &str, + value: &Value, + schema_obj: &Option, + ) -> Value { + if value.is_string() && value.as_str().unwrap().starts_with("$res:") { + return value.clone(); + } + + let schema_obj = match schema_obj { + Some(s) => s, + None => return value.clone(), + }; + + // Check if property is defined in schema and is an object type + let is_obj_type = match schema_obj.properties.get(key) { + Some(property) => { + let prop_type = property.get("type").and_then(|t| t.as_str()); + prop_type == Some("object") + } + None => false, + }; + + // If it's an object type and we received a string, try to parse it + if is_obj_type && value.is_string() { + if let Some(str_val) = value.as_str() { + if let Ok(obj_val) = serde_json::from_str::(str_val) { + return obj_val; + } + } + } + + value.clone() + } + + /// Reverses the transformation of a key. + /// + /// This function takes a transformed key and a schema object. + /// It then reverses the transformation applied by `apply_key_transformation`. This can be subject to collisions, but it's unlikely and is ok for our use case. + /// # Parameters + /// - `transformed_key`: The transformed key to reverse. + /// - `schema_obj`: The schema object. + /// + /// # Returns + /// - `String`: The original key. + fn reverse_transform_key(transformed_key: &str, schema_obj: &Option) -> String { + let schema_obj = match schema_obj { + Some(s) => s, + None => { + // No schema available, return the key as is (best guess) + return transformed_key.to_string(); + } + }; + + for original_key_in_schema in schema_obj.properties.keys() { + // Apply the SAME forward transformation to the schema key + let potential_transformed_key = + Runner::apply_key_transformation(original_key_in_schema); + + // If it matches the key we received, we found the likely original + if potential_transformed_key == transformed_key { + return original_key_in_schema.clone(); + } + } + + transformed_key.to_string() + } + + /// Applies a key transformation to a key. + /// + /// This function takes a key and replaces spaces with underscores. + /// It also removes any characters that are not alphanumeric or underscores. + /// This is used when listing, because we can't have names with spaces or special characters in the schema properties. + /// # Parameters + /// - `key`: The key to transform. + /// + /// # Returns + /// - `String`: The transformed key. + fn apply_key_transformation(key: &str) -> String { + key.replace(' ', "_") + .chars() + .filter(|c| c.is_alphanumeric() || *c == '_') + .collect::() + } + + /// Transforms the schema for resources. + /// + /// This function takes a schema and a database connection, and attempts to transform the schema for resources. + /// It replaces invalid characters in property keys with underscores and converts object properties to strings. + /// It also fetches resource type information and adds it to the description of resource properties. + /// + /// # Parameters + /// - `schema`: The schema to transform. + /// - `user_db`: The database connection. + /// - `authed`: The authenticated user. + /// - `w_id`: The workspace ID. + /// - `resources_cache`: A mutable reference to the resources cache. + /// - `resources_types`: A reference to the resource types. + /// + /// # Returns + /// - `Result`: The transformed schema. + /// - `Err(Error)`: If the transformation fails. + async fn transform_schema_for_resources( + schema: &SchemaType, + user_db: &UserDB, + authed: &ApiAuthed, + w_id: &str, + resources_cache: &mut HashMap>, + resources_types: &Vec, + ) -> Result { + let mut schema_obj: SchemaType = schema.clone(); + + // replace invalid char in property key with underscore + let replacements: Vec<(String, String, serde_json::Value)> = schema_obj + .properties + .iter() + .filter_map(|(key, value)| { + if key.chars().any(|c| !c.is_alphanumeric() && c != '_') { + let new_key = Runner::apply_key_transformation(key); + Some((key.clone(), new_key, value.clone())) + } else { + None + } + }) + .collect(); + + for (old_key, new_key, value) in replacements { + schema_obj.properties.remove(&old_key); + schema_obj.properties.insert(new_key, value); + } + + for (_key, prop_value) in schema_obj.properties.iter_mut() { + if let serde_json::Value::Object(prop_map) = prop_value { + // transform object properties to string because some client does not support object, might change in the future + if let Some(type_value) = prop_map.get("type") { + if let serde_json::Value::String(type_str) = type_value { + if type_str == "object" { + prop_map.insert( + "type".to_string(), + serde_json::Value::String("string".to_string()), + ); + } + } + } + // if property is a resource, fetch the resource type infos, and add each available resource to the description + if let Some(format_value) = prop_map.get("format") { + if let serde_json::Value::String(format_str) = format_value { + if format_str.starts_with("resource-") { + let resource_type_key = + format_str.split("-").last().unwrap_or_default().to_string(); + let resource_type = resources_types + .iter() + .find(|rt| rt.name == resource_type_key); + let resource_type_obj = resource_type.cloned().unwrap_or_else(|| { + tracing::info!("Resource type not found: {}", resource_type_key); + ResourceType { name: resource_type_key.clone(), description: None } + }); + + if !resources_cache.contains_key(&resource_type_key) { + let available_resources = Runner::inner_get_resources( + user_db, + authed, + &w_id, + &resource_type_key, + ) + .await; + + match available_resources { + Ok(cache_data) => { + resources_cache + .insert(resource_type_key.clone(), cache_data); + } + Err(e) => { + tracing::error!( + "Failed to fetch resource cache data: {}", + e + ); + continue; // Skip this property if fetching failed + } + } + } + + if let Some(resource_cache) = resources_cache.get(&resource_type_key) { + let resources_count = resource_cache.len(); + let description = format!( + "This is a resource named `{}` with the following description: `{}`.\nThe path of the resource should be used to specify the resource.\n{}", + resource_type_obj.name, + resource_type_obj.description.as_deref().unwrap_or("No description"), + if resources_count == 0 { + "This resource does not have any available instances, you should create one from your windmill workspace." + } else if resources_count > 1 { + "This resource has multiple available instances, you should precisely select the one you want to use." + } else { + "There is 1 resource available." + } + ); + prop_map.insert( + "type".to_string(), + serde_json::Value::String("string".to_string()), + ); + prop_map.insert( + "description".to_string(), + serde_json::Value::String(description), + ); + if resources_count > 0 { + let resources_description = resource_cache + .iter() + .map(|resource| { + format!( + "{}: $res:{}", + resource + .description + .as_deref() + .unwrap_or("No title"), + resource.path + ) + }) + .collect::>() + .join("\n"); + + prop_map.insert( + "description".to_string(), + serde_json::Value::String(format!( + "{}\nHere are the available resources, in the format title:path. Title can be empty. Path should be used to specify the resource:\n{}", + prop_map.get("description").unwrap_or(&serde_json::Value::String("No description".to_string())), + resources_description + )), + ); + } + } + } + } + } + } else { + tracing::warn!( + "Schema property value is not a JSON object: {:?}", + prop_value + ); + } + } + + Ok(schema_obj) + } + + /// Fetches the schema for a Hub script. + /// + /// This function takes a script path and a database connection, and attempts to fetch the schema for the script. + /// It strips the path to remove any leading slashes, and then attempts to retrieve the full script using `get_full_hub_script_by_path`. + /// If successful, it converts the schema string to a `Schema` object. + /// If the schema cannot be converted, it logs a warning and returns `None`. + /// + /// # Parameters + /// - `path`: The path of the script to fetch the schema for. + /// - `db`: The database connection. + /// + /// # Returns + /// - `Ok(Option)`: The schema if found, otherwise `None`. + /// - `Err(Error)`: If the request fails. + async fn get_hub_script_schema(path: &str, db: &DB) -> Result, Error> { + let strip_path = StripPath(path.to_string()); + let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db)) + .await + .map_err(|e| { + tracing::error!("Failed to get hub script: {}", e); + Error::internal_error(format!("Failed to get hub script: {}", e), None) + })?; + match serde_json::from_str::(res.schema.get()) { + Ok(schema) => Ok(Some(schema)), + Err(e) => { + tracing::warn!("Failed to convert schema: {}", e); + Ok(None) + } + } + } + + /// Creates a `Tool` from a `ToolableItem`. + /// + /// This function takes an item that implements the `ToolableItem` trait and converts it into an RMCP `Tool`. + /// It handles both workspace scripts/flows and Hub scripts differently, depending on the item type. + /// + /// # Parameters + /// - `item`: The item to convert to a `Tool`. + /// - `user_db`: The database connection. + /// - `authed`: The authenticated user. + /// - `workspace_id`: The workspace ID. + /// - `resources_cache`: A mutable reference to the resources cache. + /// - `resources_types`: A reference to the resource types. + /// + /// # Returns + /// - `Ok(Tool)`: The created `Tool`. + async fn create_tool_from_item( + item: &T, + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + resources_cache: &mut HashMap>, + resources_types: &Vec, + ) -> Result { + let is_hub = item.is_hub(); + let path = item.get_path_or_id(); + let item_type = item.item_type(); + let description = format!( + "This is a {} named `{}` with the following description: `{}`.{}", + item_type, + item.get_summary(), + item.get_description(), + if is_hub { + format!( + " It is a tool used for the following app: {}", + item.get_integration_type() + .unwrap_or("No integration type".to_string()) + ) + } else { + "".to_string() + } + ); + let schema_obj = Runner::transform_schema_for_resources( + &item.get_schema(), + user_db, + authed, + &workspace_id, + resources_cache, + &resources_types, + ) + .await?; + let input_schema_map = match serde_json::to_value(schema_obj) { + Ok(Value::Object(map)) => map, + Ok(_) => { + tracing::warn!("Schema object for tool '{}' did not serialize to a JSON object, using empty schema.", path); + serde_json::Map::new() + } + Err(e) => { + tracing::error!( + "Failed to serialize schema object for tool '{}': {}. Using empty schema.", + path, + e + ); + serde_json::Map::new() + } + }; + Ok(Tool { + name: Cow::Owned(path), + description: Some(Cow::Owned(description)), + input_schema: Arc::new(input_schema_map), + annotations: None, + }) + } +} + +impl ServerHandler for Runner { + /// Handles the `CallTool` request from the MCP client. + /// + /// This involves: + /// 1. Parsing arguments and extracting context (DB, Auth). + /// 2. Reversing the tool name (`request.name`) to get the original path and type using `reverse_transform`. + /// 3. Handling Hub scripts: If identified as a Hub script, searches the Hub for the actual script ID. + /// 4. Fetching the schema for the item (needed for argument transformation). + /// 5. Transforming incoming arguments: + /// - Reversing key transformations (e.g., `user_input` back to `user input`). + /// - Parsing stringified JSON objects back into JSON values based on schema type. + /// 6. Executing the corresponding script or flow using internal Windmill runners. + /// 7. Formatting the execution result into an RMCP `CallToolResult`. + /// + /// # Parameters + /// - `request`: The `CallToolRequestParam` containing the tool name and arguments. + /// - `context`: The `RequestContext` providing access to workspace ID, DB connections, auth info. + /// + /// # Returns + /// - `Ok(CallToolResult)`: On successful execution, containing the output. + /// - `Err(Error)`: If any step fails (parsing, DB access, execution, reversing transform, hub search). + async fn call_tool( + &self, + request: CallToolRequestParam, + context: RequestContext, + ) -> Result { + let parse_args = |args_opt: Option| -> Result { + args_opt.map(Value::Object).ok_or_else(|| { + Error::invalid_params( + "Missing arguments for tool", + Some(request.name.clone().into()), + ) + }) + }; + + let http_parts = context + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("http::request::Parts not found"); + Error::internal_error("http::request::Parts not found", None) + })?; + + let authed = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("ApiAuthed Axum extension not found"); + Error::internal_error("ApiAuthed Axum extension not found", None) + })?; + let db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("DB Axum extension not found"); + Error::internal_error("DB Axum extension not found", None) + })?; + let user_db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("UserDB Axum extension not found"); + Error::internal_error("UserDB Axum extension not found", None) + })?; + let args = parse_args(request.arguments)?; + + let workspace_id = http_parts + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("WorkspaceId not found"); + Error::internal_error("WorkspaceId not found", None) + }) + .map(|w_id| w_id.0.clone())?; + + let (tool_type, path, is_hub) = + Runner::reverse_transform(&request.name).unwrap_or_default(); + + let item_schema = if is_hub { + Runner::get_hub_script_schema(&format!("hub/{}", path), db).await? + } else { + Runner::get_item_schema(&path, user_db, authed, &workspace_id, &tool_type).await? + }; + + let schema_obj = if let Some(ref s) = item_schema { + match serde_json::from_str::(s.0.get()) { + Ok(val) => Some(val), + Err(e) => { + tracing::warn!("Failed to parse schema: {}", e); + None + } + } + } else { + None + }; + + let push_args = if let Value::Object(map) = args.clone() { + let mut args_hash = HashMap::new(); + for (k, v) in map { + // need to transform back the key without invalid characters to the original key + let original_key = Runner::reverse_transform_key(&k, &schema_obj); + + // object properties are transformed to string because some client does not support object, might change in the future + let transformed_v = Runner::transform_value_if_object(&k, &v, &schema_obj); + args_hash.insert(original_key, to_raw_value(&transformed_v)); + } + windmill_queue::PushArgsOwned { extra: None, args: args_hash } + } else { + windmill_queue::PushArgsOwned::default() + }; + let script_or_flow_path = if is_hub { + StripPath(format!("hub/{}", path)) + } else { + StripPath(path) + }; + let run_query = RunJobQuery::default(); + + let result = if tool_type == "script" { + run_wait_result_script_by_path_internal( + db.clone(), + run_query, + script_or_flow_path, + authed.clone(), + user_db.clone(), + workspace_id.clone(), + push_args, + ) + .await + } else { + run_wait_result_flow_by_path_internal( + db.clone(), + run_query, + script_or_flow_path, + authed.clone(), + user_db.clone(), + push_args, + workspace_id.clone(), + ) + .await + }; + + match result { + Ok(response) => { + let body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .map_err(|e| { + Error::internal_error(format!("Failed to read response body: {}", e), None) + })?; + let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { + Error::internal_error(format!("Failed to decode response body: {}", e), None) + })?; + Ok(CallToolResult::success(vec![Content::text(body_str)])) + } + Err(e) => Err(Error::internal_error( + format!("Failed to run script: {}", e), + None, + )), + } + } + + /// Fetches available tools (scripts, flows, hub scripts) based on the user's scope. + /// + /// - Determines scope (all, favorites, hub-specific) from auth token. + /// - Fetches relevant items (workspace scripts/flows, hub scripts) concurrently. + /// - Fetches resource type information needed for schema enrichment. + /// - Transforms each item into an RMCP `Tool` definition, including schema adjustments + /// (like resource description enrichment and object->string conversion). + /// + /// # Parameters + /// - `_request`: Optional pagination parameters (currently ignored). + /// - `_context`: The `RequestContext` providing workspace ID, DB, auth. + /// + /// # Returns + /// - `Ok(ListToolsResult)`: A list of `Tool` definitions. Pagination is not yet implemented. + /// - `Err(Error)`: If fetching data from DB or Hub fails. + async fn list_tools( + &self, + _request: Option, + mut _context: RequestContext, + ) -> Result { + let http_parts = _context + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("http::request::Parts not found"); + Error::internal_error("http::request::Parts not found", None) + })?; + + let db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("DB Axum extension not found"); + Error::internal_error("DB Axum extension not found", None) + })?; + + let user_db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("UserDB Axum extension not found"); + Error::internal_error("UserDB Axum extension not found", None) + })?; + + let authed = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("ApiAuthed Axum extension not found"); + Error::internal_error("ApiAuthed Axum extension not found", None) + })?; + + let workspace_id = http_parts + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("WorkspaceId not found"); + Error::internal_error("WorkspaceId not found", None) + }) + .map(|w_id| w_id.0.clone())?; + + let owned_scope = authed.scopes.as_ref().and_then(|scopes| { + scopes + .iter() + .find(|scope| scope.starts_with("mcp:") && !scope.contains("hub")) + }); + let hub_scope = authed + .scopes + .as_ref() + .and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub"))); + let scope_type = owned_scope.map_or("all", |scope| { + let parts = scope.split(":").collect::>(); + parts[1] + }); + let scope_integrations = hub_scope.and_then(|scope| { + let parts = scope.split(":").collect::>(); + if parts.len() == 3 { + Some(parts[2]) + } else { + None + } + }); + + let scripts_fn = Runner::inner_get_items::( + user_db, + authed, + &workspace_id, + scope_type, + "script", + ); + let flows_fn = + Runner::inner_get_items::(user_db, authed, &workspace_id, scope_type, "flow"); + let resources_types_fn = Runner::inner_get_resources_types(user_db, authed, &workspace_id); + let hub_scripts_fn = Runner::inner_get_scripts_from_hub(db, scope_integrations.as_deref()); + let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() { + let (scripts, flows, resources_types, hub_scripts) = + try_join!(scripts_fn, flows_fn, resources_types_fn, hub_scripts_fn)?; + (scripts, flows, resources_types, hub_scripts) + } else { + let (scripts, flows, resources_types) = + try_join!(scripts_fn, flows_fn, resources_types_fn)?; + (scripts, flows, resources_types, vec![]) + }; + + let mut resources_cache: HashMap> = HashMap::new(); + let mut tools: Vec = Vec::new(); + + for script in scripts { + tools.push( + Runner::create_tool_from_item( + &script, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + for flow in flows { + tools.push( + Runner::create_tool_from_item( + &flow, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + for hub_script in hub_scripts { + tools.push( + Runner::create_tool_from_item( + &hub_script, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + Ok(ListToolsResult { tools, next_cursor: None }) + } + + fn get_info(&self) -> ServerInfo { + ServerInfo { + protocol_version: Default::default(), + capabilities: ServerCapabilities::builder() + .enable_tools() + .enable_tool_list_changed() + .build(), + server_info: Implementation::from_build_env(), + instructions: Some("This server provides a list of scripts and flows the user can run on Windmill. Each flow and script is a tool callable with their respective arguments.".to_string()), + } + } + + async fn initialize( + &self, + _request: InitializeRequestParam, + _context: RequestContext, + ) -> Result { + Ok(self.get_info()) + } + + async fn list_resources( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListResourcesResult { resources: vec![], next_cursor: None }) + } + + async fn list_prompts( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListPromptsResult::default()) + } + + async fn list_resource_templates( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListResourceTemplatesResult::default()) + } +} + +#[derive(Clone, Debug)] +pub struct WorkspaceId(pub String); + +pub async fn extract_and_store_workspace_id( + Path(params): Path, + mut request: Request, + next: Next, +) -> Response { + let workspace_id = params; + request.extensions_mut().insert(WorkspaceId(workspace_id)); + next.run(request).await +} + +pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc)> { + let session_manager = Arc::new(LocalSessionManager::default()); + let service_config = Default::default(); + let service = StreamableHttpService::new( + || Ok(Runner::new()), + session_manager.clone(), + service_config, + ); + + let router = axum::Router::new().nest_service("/", service); + Ok((router, session_manager)) +} + +pub async fn shutdown_mcp_server(session_manager: Arc) { + let session_ids_to_close = { + let sessions_map = session_manager.sessions.read().await; + sessions_map.keys().cloned().collect::>() + }; + + if !session_ids_to_close.is_empty() { + tracing::info!( + "Closing {} active MCP session(s)...", + session_ids_to_close.len() + ); + let close_futures = session_ids_to_close + .iter() + .map(|session_id| { + let manager_clone = session_manager.clone(); + async move { + if let Err(_) = manager_clone.close_session(session_id).await { + tracing::warn!("Error closing MCP session"); + } + } + }) + .collect::>(); + futures::future::join_all(close_futures).await; + } +} diff --git a/backend/windmill-api/src/mqtt_triggers.rs b/backend/windmill-api/src/mqtt_triggers.rs index 5f63e95b9e..7cc3f3c4e0 100644 --- a/backend/windmill-api/src/mqtt_triggers.rs +++ b/backend/windmill-api/src/mqtt_triggers.rs @@ -1,10 +1,13 @@ use crate::{ - capture::{insert_capture_payload, MqttTriggerConfig, TriggerKind}, + capture::{insert_capture_payload, MqttTriggerConfig}, db::{ApiAuthed, DB}, jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery}, resources::try_get_resource_from_db_as, + trigger_helpers::TriggerJobArgs, users::fetch_api_authed, }; +use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; + use axum::{ async_trait, extract::{Path, Query}, @@ -14,7 +17,7 @@ use axum::{ routing::{delete, get, post}, Router, }; -use base64::prelude::*; +use base64::{engine, prelude::*}; use bytes::Bytes; use http::StatusCode; use itertools::Itertools; @@ -36,10 +39,11 @@ use sql_builder::{bind::Bind, SqlBuilder}; use sqlx::{FromRow, Type}; use std::collections::HashMap; use std::time::Duration; -use windmill_audit::{audit_ee::audit_log, ActionKind}; +use windmill_audit::{audit_oss::audit_log, ActionKind}; use windmill_common::{ db::UserDB, error::{self, JsonResult}, + triggers::TriggerKind, utils::{not_found_if_none, paginate, report_critical_error, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, INSTANCE_NAME, @@ -49,8 +53,6 @@ use rand::seq::SliceRandom; use serde_json::value::RawValue; use sqlx::types::Json as SqlxJson; -use windmill_queue::PushArgsOwned; - pub fn workspaced_service() -> Router { Router::new() .route("/create", post(create_mqtt_trigger)) @@ -80,12 +82,20 @@ enum Error { } async fn run_job( - args: Option>>, - extra: Option>>, + payload: &[u8], + trigger_info: HashMap>, db: &DB, trigger: &MqttTrigger, ) -> anyhow::Result<()> { - let args = PushArgsOwned { args: args.unwrap_or_default(), extra }; + let args = MqttTrigger::build_job_args( + &trigger.script_path, + trigger.is_flow, + &trigger.workspace_id, + db, + payload, + trigger_info, + ) + .await?; let authed = fetch_api_authed( trigger.edited_by.clone(), @@ -109,7 +119,6 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await?; } else { @@ -121,7 +130,6 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await?; } @@ -237,25 +245,25 @@ pub struct EditMqttTrigger { #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct MqttTrigger { - mqtt_resource_path: String, - subscribe_topics: Vec>, - v3_config: Option>, - v5_config: Option>, - client_id: Option, + pub mqtt_resource_path: String, + pub subscribe_topics: Vec>, + pub v3_config: Option>, + pub v5_config: Option>, + pub client_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - client_version: Option, - path: String, - script_path: String, - is_flow: bool, - workspace_id: String, - edited_by: String, - email: String, - edited_at: chrono::DateTime, - extra_perms: Option, - error: Option, - server_id: Option, - last_server_ping: Option>, - enabled: bool, + pub client_version: Option, + pub path: String, + pub script_path: String, + pub is_flow: bool, + pub workspace_id: String, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + pub extra_perms: Option, + pub error: Option, + pub server_id: Option, + pub last_server_ping: Option>, + pub enabled: bool, } #[derive(Deserialize, Serialize)] @@ -470,7 +478,7 @@ pub async fn test_mqtt_connection( test_postgres; let mqtt_resource = try_get_resource_from_db_as::( - authed, + &authed, Some(user_db), &db, &mqtt_resource_path, @@ -508,13 +516,14 @@ pub async fn test_mqtt_connection( pub async fn create_mqtt_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path(w_id): Path, Json(new_mqtt_trigger): Json, ) -> error::Result<(StatusCode, String)> { if *CLOUD_HOSTED { return Err(error::Error::BadRequest( - "Mqtt triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), + "MQTT triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), )); } @@ -599,7 +608,18 @@ pub async fn create_mqtt_trigger( tx.commit().await?; - Ok((StatusCode::CREATED, path.to_string())) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::MqttTrigger { path: path.to_string() }, + Some(format!("MQTT trigger '{}' created", path)), + true, + ) + .await?; + + Ok((StatusCode::CREATED, format!("{}", path.to_string()))) } pub async fn list_mqtt_triggers( @@ -712,6 +732,7 @@ pub async fn get_mqtt_trigger( pub async fn update_mqtt_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(mqtt_trigger): Json, @@ -780,7 +801,7 @@ pub async fn update_mqtt_trigger( &mut *tx, &authed, "mqtt_triggers.update", - ActionKind::Create, + ActionKind::Update, &w_id, Some(&path), None, @@ -789,11 +810,23 @@ pub async fn update_mqtt_trigger( tx.commit().await?; - Ok(workspace_path.to_string()) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::MqttTrigger { path: path.clone() }, + Some(format!("MQTT trigger '{}' updated", path)), + true, + ) + .await?; + + Ok(path.to_string()) } pub async fn delete_mqtt_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> error::Result { @@ -827,7 +860,18 @@ pub async fn delete_mqtt_trigger( tx.commit().await?; - Ok(format!("Mqtt trigger {path} deleted")) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::MqttTrigger { path: path.to_string() }, + Some(format!("MQTT trigger '{}' deleted", path)), + true, + ) + .await?; + + Ok(format!("MQTT trigger {path} deleted")) } pub async fn exists_mqtt_trigger( @@ -857,6 +901,7 @@ pub async fn exists_mqtt_trigger( pub async fn set_enabled( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(payload): Json, @@ -906,6 +951,17 @@ pub async fn set_enabled( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::MqttTrigger { path: path.to_string() }, + Some(format!("MQTT trigger '{}' updated", path)), + true, + ) + .await?; + Ok(format!( "successfully updated mqtt trigger at path {} to status {}", path, payload.enabled @@ -1088,31 +1144,27 @@ impl EventLoop for V3EventLoop { } async fn handle_publish_packet(db: &DB, mqtt: &MqttConfig, payload: Bytes, publish: PublishData) { - let args = HashMap::from([("payload".to_string(), to_raw_value(&payload.as_ref()))]); - let extra = Some(HashMap::from([( - "wm_trigger".to_string(), - to_raw_value(&serde_json::json!({ - "kind": "mqtt", - "mqtt": { - "topic": publish.topic, - "retain": publish.retain, - "pkid": publish.pkid, - "qos": publish.qos, - "v5": publish.v5.map(|properties| { - serde_json::json!({ - "payload_format_indicator": properties.payload_format_indicator, - "topic_alias": properties.topic_alias, - "response_topic": properties.response_topic, - "correlation_data": properties.correlation_data.as_deref(), - "user_properties": properties.user_properties, - "subscription_identifiers": properties.subscription_identifiers, - "content_type": properties.content_type, - }) + let trigger_info = HashMap::from([ + ("topic".to_string(), to_raw_value(&publish.topic)), + ("retain".to_string(), to_raw_value(&publish.retain)), + ("pkid".to_string(), to_raw_value(&publish.pkid)), + ("qos".to_string(), to_raw_value(&publish.qos)), + ( + "v5".to_string(), + to_raw_value(&publish.v5.map(|properties| { + serde_json::json!({ + "payload_format_indicator": properties.payload_format_indicator, + "topic_alias": properties.topic_alias, + "response_topic": properties.response_topic, + "correlation_data": properties.correlation_data.as_deref(), + "user_properties": properties.user_properties, + "subscription_identifiers": properties.subscription_identifiers, + "content_type": properties.content_type, }) - } - })), - )])); - mqtt.handle(&db, Some(args), extra).await; + })), + ), + ]); + mqtt.handle(&db, payload.as_ref(), trigger_info).await; } async fn handle_event(db: &DB, mqtt: &MqttConfig, handler: H, mut event_loop: E) -> () @@ -1201,7 +1253,7 @@ impl MqttConfig { } } let mqtt_resource = try_get_resource_from_db_as::( - authed, + &authed, Some(UserDB::new(db.clone())), db, mqtt_resource_path, @@ -1223,12 +1275,12 @@ impl MqttConfig { async fn handle( &self, db: &DB, - args: Option>>, - extra: Option>>, + payload: &[u8], + trigger_info: HashMap>, ) -> () { match self { - MqttConfig::Trigger(trigger) => trigger.handle(&db, args, extra).await, - MqttConfig::Capture(capture) => capture.handle(&db, args, extra).await, + MqttConfig::Trigger(trigger) => trigger.handle(&db, payload, trigger_info).await, + MqttConfig::Capture(capture) => capture.handle(&db, payload, trigger_info).await, } } } @@ -1404,10 +1456,10 @@ impl MqttTrigger { async fn handle( &self, db: &DB, - args: Option>>, - extra: Option>>, + payload: &[u8], + trigger_info: HashMap>, ) -> () { - if let Err(err) = run_job(args, extra, db, self).await { + if let Err(err) = run_job(payload, trigger_info, db, self).await { report_critical_error( format!("Failed to trigger job from mqtt {}: {:?}", self.path, err), db.clone(), @@ -1419,6 +1471,21 @@ impl MqttTrigger { } } +impl TriggerJobArgs<&[u8]> for MqttTrigger { + fn v1_payload_fn(payload: &[u8]) -> HashMap> { + HashMap::from([("payload".to_string(), to_raw_value(&payload))]) + } + + fn v2_payload_fn(payload: &[u8]) -> HashMap> { + let base64_payload = engine::general_purpose::STANDARD.encode(payload); + HashMap::from([("payload".to_string(), to_raw_value(&base64_payload))]) + } + + fn trigger_kind() -> TriggerKind { + TriggerKind::Mqtt + } +} + struct PublishData { topic: String, retain: bool, @@ -1673,19 +1740,19 @@ impl CaptureConfigForMqttTrigger { async fn handle( &self, db: &DB, - args: Option>>, - extra: Option>>, + payload: &[u8], + trigger_info: HashMap>, ) -> () { - let args = PushArgsOwned { args: args.unwrap_or_default(), extra: None }; - let extra = extra.as_ref().map(to_raw_value); + let (main_args, preprocessor_args) = + MqttTrigger::build_capture_payloads(payload, trigger_info); if let Err(err) = insert_capture_payload( db, &self.workspace_id, &self.path, self.is_flow, &TriggerKind::Mqtt, - args, - extra, + main_args, + preprocessor_args, &self.owner, ) .await diff --git a/backend/windmill-api/src/nats_triggers_ee.rs b/backend/windmill-api/src/nats_triggers_oss.rs similarity index 79% rename from backend/windmill-api/src/nats_triggers_ee.rs rename to backend/windmill-api/src/nats_triggers_oss.rs index 649d3a3837..28bf1e70e2 100644 --- a/backend/windmill-api/src/nats_triggers_ee.rs +++ b/backend/windmill-api/src/nats_triggers_oss.rs @@ -1,22 +1,34 @@ +#[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, @@ -40,4 +52,4 @@ pub struct NatsTrigger { #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, pub enabled: bool, -} \ No newline at end of file +} diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_oss.rs similarity index 76% rename from backend/windmill-api/src/oauth2_ee.rs rename to backend/windmill-api/src/oauth2_oss.rs index 49d2155cf3..04e0e1b202 100644 --- a/backend/windmill-api/src/oauth2_ee.rs +++ b/backend/windmill-api/src/oauth2_oss.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::oauth2_ee::*; + /* * Author: Ruben Fiszel * Copyright: Windmill Labs, Inc 2022 @@ -6,39 +10,50 @@ * 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(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] use itertools::Itertools; -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] use oauth2::{Client as OClient, *}; +#[cfg(not(feature = "private"))] use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "private"))] use sqlx::{Postgres, Transaction}; -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] use windmill_common::more_serde::maybe_number_opt; -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] 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(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] #[derive(Debug, Clone)] pub struct ClientWithScopes { _client: OClient, @@ -48,9 +63,10 @@ pub struct ClientWithScopes { _allowed_domains: Option>, _userinfo_url: Option, } -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] pub type BasicClientsMap = HashMap; +#[cfg(not(feature = "private"))] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct OAuthConfig { auth_url: String, @@ -62,6 +78,7 @@ pub struct OAuthConfig { req_body_auth: Option, } +#[cfg(not(feature = "private"))] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct OAuthClient { id: String, @@ -71,7 +88,7 @@ pub struct OAuthClient { login_config: Option, } -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] #[derive(Debug)] pub struct AllClients { pub logins: BasicClientsMap, @@ -79,7 +96,7 @@ pub struct AllClients { pub slack: Option, } -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] pub async fn build_oauth_clients( _base_url: &str, _oauths_from_config: Option>, @@ -93,7 +110,7 @@ pub async fn build_oauth_clients( }); } -#[cfg(feature = "oauth2")] +#[cfg(all(feature = "oauth2", not(feature = "private")))] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct TokenResponse { access_token: AccessToken, @@ -107,17 +124,20 @@ pub struct TokenResponse { scope: Option>, } +#[cfg(not(feature = "private"))] #[derive(Serialize)] struct Logins { oauth: Vec, saml: Option, } +#[cfg(not(feature = "private"))] async fn list_logins() -> error::JsonResult { // Implementation is not open source return Ok(Json(Logins { oauth: vec![], saml: None })); } -#[cfg(feature = "oauth2")] +#[allow(unused)] +#[cfg(all(feature = "oauth2", not(feature = "private")))] async fn list_connects() -> error::JsonResult> { Ok(Json( (&OAUTH_CLIENTS.read().await.connects) @@ -127,12 +147,14 @@ async fn list_connects() -> error::JsonResult> { )) } -#[cfg(not(feature = "oauth2"))] -async fn list_connects() -> error::JsonResult> { +#[allow(unused)] +#[cfg(not(all(feature = "oauth2", not(feature = "private"))))] +async fn list_connects() -> windmill_common::error::JsonResult> { // Implementation is not open source - return Ok(Json(vec![])); + return Ok(axum::Json(vec![])); } +#[cfg(not(feature = "private"))] pub async fn _refresh_token<'c>( _tx: Transaction<'c, Postgres>, _path: &str, @@ -146,6 +168,7 @@ 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'",) @@ -171,10 +194,11 @@ 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>(secret: S) -> anyhow::Result { HmacSha256::new_from_slice(secret.as_ref()) diff --git a/backend/windmill-api/src/oidc_ee.rs b/backend/windmill-api/src/oidc_oss.rs similarity index 68% rename from backend/windmill-api/src/oidc_ee.rs rename to backend/windmill-api/src/oidc_oss.rs index 248b990f54..4042c2a53b 100644 --- a/backend/windmill-api/src/oidc_ee.rs +++ b/backend/windmill-api/src/oidc_oss.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::oidc_ee::*; + /* * Author: Ruben Fiszel * Copyright: Windmill Labs, Inc 2023 @@ -6,12 +10,15 @@ * 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() } diff --git a/backend/windmill-api/src/openapi.rs b/backend/windmill-api/src/openapi.rs new file mode 100644 index 0000000000..95f73f95ba --- /dev/null +++ b/backend/windmill-api/src/openapi.rs @@ -0,0 +1,982 @@ +use std::{ + collections::{HashMap, HashSet}, + fmt::Display, +}; + +use anyhow::anyhow; +use axum::{ + body::Body, extract::Path, http, response::Response, routing::post, Extension, Json, Router, +}; +use http::{header, HeaderValue, Method, StatusCode}; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_json::{to_value, Map, Value}; +use sqlx::PgConnection; +use url::Url; +use windmill_common::{ + db::UserDB, + error::{Error, Result}, + utils::{deserialize_url, empty_as_none, is_empty, RunnableKind}, + DB, +}; + +use crate::db::ApiAuthed; + +#[cfg(feature = "http_trigger")] +use { + crate::{ + http_trigger_args::HttpMethod, http_trigger_auth::ApiKeyAuthentication, + http_triggers::AuthenticationMethod, resources::try_get_resource_from_db_as, + }, + itertools::Itertools, +}; + +lazy_static::lazy_static! { + static ref DEFAULT_OPENAPI_INFO_OBJECT: Info = Info { + title: "Windmill API".to_string(), + version: "1.0.0".to_string(), + ..Default::default() + }; +} + +const DEFAULT_OPENAPI_GENERATED_VERSION: &'static str = "3.1.0"; +const JWT_SECURITY_SCHEME: &'static str = "JwtAuth"; +const BASIC_HTTP_AUTH_SCHEME: &'static str = "BasicHttp"; + +const DEFAULT_REQUEST_KEY: &'static str = "defaultRequest"; +const DEFAULT_ASYNC_RESPONSE_KEY: &'static str = "AsyncResponse"; +const DEFAULT_SYNC_RESPONSE_KEY: &'static str = "SyncResponse"; +const DEFAULT_PAYLOAD_PARAM_KEY: &'static str = "PayloadParam"; + +pub fn openapi_service() -> Router { + Router::new() + .route("/generate", post(generate_openapi_spec)) + .route("/download", post(download_spec)) +} + +#[derive(Debug, Deserialize, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum Format { + JSON, + YAML, +} + +impl Display for Format { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let format = match self { + Format::JSON => "json", + Format::YAML => "yaml", + }; + write!(f, "{}", format) + } +} + +impl Default for Format { + fn default() -> Self { + Self::YAML + } +} + +#[derive(Debug, Default, Deserialize, Serialize)] +struct Contact { + #[serde(skip_serializing_if = "is_empty")] + name: Option, + #[serde( + default, + deserialize_with = "deserialize_url", + skip_serializing_if = "Option::is_none" + )] + url: Option, + #[serde(skip_serializing_if = "is_empty")] + email: Option, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +struct License { + name: String, + #[serde(skip_serializing_if = "is_empty")] + identifier: Option, + #[serde( + default, + deserialize_with = "deserialize_url", + skip_serializing_if = "Option::is_none" + )] + url: Option, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct Info { + title: String, + version: String, + #[serde(skip_serializing_if = "is_empty")] + description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + contact: Option, + #[serde(skip_serializing_if = "Option::is_none")] + license: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Server { + url: String, + #[serde(skip_serializing_if = "is_empty")] + description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + variables: Option>, +} + +#[derive(Debug)] +pub enum SecurityScheme { + BearerJwt, + BasicHttp, + ApiKey(String), +} +#[derive(Debug)] +pub struct WebhookConfig { + runnable_kind: RunnableKind, +} + +impl WebhookConfig { + pub fn new(runnable_kind: RunnableKind) -> Self { + Self { runnable_kind } + } +} + +#[derive(Debug)] +pub struct HttpRouteConfig { + method: Method, +} + +impl HttpRouteConfig { + pub fn new(method: Method) -> Self { + Self { method } + } +} + +#[derive(Debug)] +pub enum Kind { + Webhook(WebhookConfig), + HttpRoute(HttpRouteConfig), +} + +#[derive(Debug)] +pub struct FuturePath { + route_path: String, + kind: Kind, + is_async: Option, + summary: Option, + description: Option, + security_scheme: Option, +} + +impl FuturePath { + pub fn new( + route_path: String, + kind: Kind, + is_async: Option, + summary: Option, + description: Option, + security_scheme: Option, + ) -> FuturePath { + FuturePath { route_path, kind, is_async, summary, description, security_scheme } + } +} + +fn from_route_path_to_openapi_path( + route_path: &str, + kind: &Kind, +) -> Result<(Vec, Option)> { + let mut openapi_path = String::new(); + let mut parameters = Vec::new(); + + for segment in route_path.split('/') { + if segment.starts_with(':') { + let param_name = &segment[1..]; + + if param_name.is_empty() { + return Err(anyhow!("Empty parameter name in path: {}", route_path).into()); + } + + openapi_path.push_str(&format!("/{{{}}}", param_name)); + parameters.push(serde_json::json!({ + "name": param_name, + "in": "path", + "required": true, + "schema": { "type": "string" } + })); + } else if !segment.is_empty() { + openapi_path.push('/'); + openapi_path.push_str(segment); + } else { + openapi_path.push('/'); + } + } + + let parameters_json = if parameters.is_empty() { + None + } else { + Some(Value::Array(parameters)) + }; + + let prefix = match kind { + Kind::HttpRoute(_) => "", + Kind::Webhook(WebhookConfig { runnable_kind }) => match runnable_kind { + RunnableKind::Script => "p", + RunnableKind::Flow => "f", + }, + }; + + let normalized_path = if openapi_path.starts_with('/') { + format!("{prefix}{openapi_path}") + } else { + format!("{}/{}", prefix, openapi_path) + }; + + let route_paths = if prefix.is_empty() { + vec![normalized_path] + } else { + vec![ + format!("/run/{}", &normalized_path), + format!("/run_wait_result/{}", &normalized_path), + ] + }; + + Ok((route_paths, parameters_json)) +} + +fn get_servers_component(url: &str, kind: &Kind) -> Server { + let url = url.trim_end_matches('/'); + + let server = match kind { + Kind::HttpRoute(_) => { + Server { url: format!("{}/api/r", url), description: None, variables: None } + } + Kind::Webhook(_) => Server { + url: format!("{}/api/w/{{workspace}}/jobs", url), + variables: Some(HashMap::from([( + "workspace".to_string(), + serde_json::json!({ + "default": "test", + "description": "Workspace identifier" + }), + )])), + description: None, + }, + }; + + server +} + +fn generate_paths( + paths: Vec, + url: Option<&Url>, +) -> Result>> { + let mut map: IndexMap> = IndexMap::new(); + + let generate_default_request = || { + serde_json::json!({ + "$ref": format!("#/components/requestBodies/{DEFAULT_REQUEST_KEY}") + }) + }; + + let generate_response = |is_async: bool| { + let responses = if is_async { + serde_json::json!({ + "200": { + "$ref": format!("#/components/responses/{DEFAULT_ASYNC_RESPONSE_KEY}") + } + }) + } else { + serde_json::json!(serde_json::json!({ + "200": { + "$ref": format!("#/components/responses/{DEFAULT_SYNC_RESPONSE_KEY}") + } + })) + }; + + responses + }; + + let get_security_scheme = |security_scheme: Option<&SecurityScheme>| -> Vec { + if let Some(security_scheme) = security_scheme { + let scheme = match security_scheme { + SecurityScheme::ApiKey(api_key) => header_to_pascal_case(&api_key), + SecurityScheme::BearerJwt => JWT_SECURITY_SCHEME.to_owned(), + SecurityScheme::BasicHttp => BASIC_HTTP_AUTH_SCHEME.to_owned(), + }; + + vec![serde_json::json!({ + scheme: [] + })] + } else { + vec![] + } + }; + + let mut webhooks = HashSet::new(); + + for path in paths { + if let Kind::Webhook(WebhookConfig { runnable_kind }) = &path.kind { + if !webhooks.insert((path.route_path.clone(), runnable_kind.to_owned())) { + continue; + } + } + + let (route_paths, parameters) = + from_route_path_to_openapi_path(&path.route_path, &path.kind)?; + + for route_path in route_paths { + let path_object = map.entry(route_path.clone()).or_insert_with(|| { + let mut path_object = IndexMap::new(); + + if let Some(url) = url { + let servers = get_servers_component(url.as_str(), &path.kind); + path_object.insert("servers".to_string(), to_value(vec![servers]).unwrap()); + } + + if parameters.is_some() { + path_object.insert( + "parameters".to_string(), + to_value(parameters.clone()).unwrap(), + ); + } + + path_object + }); + + let is_async; + + let (methods, is_webhook) = match &path.kind { + Kind::Webhook(_) => { + is_async = route_path.starts_with("/run/"); + let methods = if is_async { + vec![Method::POST] + } else { + vec![Method::GET, Method::POST] + }; + + (methods, true) + } + Kind::HttpRoute(HttpRouteConfig { method }) => { + if path_object.get(&method.to_string()).is_some() { + return Err(anyhow!( + "Found duplicate {} method, for route at path: {}", + method, + path.route_path + ) + .into()); + } + is_async = path.is_async.unwrap_or(true); + (vec![method.to_owned()], false) + } + }; + + for method in methods { + let mut method_map = IndexMap::new(); + + if let Some(summary) = path.summary.as_ref().filter(|s| !s.is_empty()) { + method_map.insert("summary", Value::String(summary.to_owned())); + } + + if let Some(description) = path.description.as_ref().filter(|s| !s.is_empty()) { + method_map.insert("description", Value::String(description.to_owned())); + } + + method_map.insert( + "security", + to_value(get_security_scheme(path.security_scheme.as_ref()))?, + ); + + if method != Method::GET { + method_map.insert("requestBody", generate_default_request()); + } else if is_webhook { + method_map.insert( + "parameters", + Value::Array(vec![serde_json::json!({ + "$ref": format!("#/components/parameters/{DEFAULT_PAYLOAD_PARAM_KEY}") + })]), + ); + } + + method_map.insert("responses", generate_response(is_async)); + + path_object.insert(method.to_string().to_lowercase(), to_value(&method_map)?); + } + } + } + + return Ok(map); +} + +pub fn transform_to_minified_postgres_regex(glob: &str) -> String { + let mut regex = String::from("^"); + for ch in glob.chars() { + match ch { + '*' => regex.push_str(".*"), + '.' | '+' | '(' | ')' | '|' | '^' | '$' | '{' | '}' | '[' | ']' | '\\' => { + regex.push('\\'); + regex.push(ch); + } + _ => regex.push(ch), + } + } + + regex.push('$'); + regex +} + +#[derive(Debug, Default)] +pub struct ServerToSet { + pub http_route: bool, + pub webhook_flow: bool, + pub webhook_script: bool, +} + +impl ServerToSet { + pub fn new(http_route: bool, webhook_flow: bool, webhook_script: bool) -> ServerToSet { + ServerToSet { http_route, webhook_flow, webhook_script } + } +} + +fn header_to_pascal_case(header: &str) -> String { + header + .split(|c: char| c == '-' || c == '_' || c == ' ') + .filter(|s| !s.is_empty()) + .map(|s| { + let mut chars = s.chars(); + match chars.next() { + Some(first) => { + first.to_ascii_uppercase().to_string() + + chars.as_str().to_ascii_lowercase().as_str() + } + None => String::new(), + } + }) + .collect::() +} + +#[derive(Debug, Default)] +struct SecuritySchemeToAdd { + basic_http: bool, + bearer_jwt: bool, + api_keys: Vec<(String, Value)>, +} + +fn generate_all_security_schemes(future_paths: &[FuturePath]) -> SecuritySchemeToAdd { + let mut to_add = SecuritySchemeToAdd::default(); + + let mut set = HashSet::new(); + for future_path in future_paths { + if !to_add.basic_http + && matches!(future_path.security_scheme, Some(SecurityScheme::BasicHttp)) + { + to_add.basic_http = true + } else if !to_add.bearer_jwt + && matches!(future_path.security_scheme, Some(SecurityScheme::BearerJwt)) + { + to_add.bearer_jwt = true + } else if let Some(SecurityScheme::ApiKey(api_key)) = future_path.security_scheme.as_ref() { + let pascal_case_header = header_to_pascal_case(&api_key); + + if !set.insert(pascal_case_header.clone()) { + continue; + } + + let scheme = serde_json::json!({ + "type": "apiKey", + "in": "header", + "name": api_key + }); + to_add.api_keys.push((pascal_case_header, scheme)); + } + } + + to_add +} + +fn generate_components(future_paths: &[FuturePath]) -> Map { + let mut components = Map::new(); + + if future_paths + .iter() + .any(|path| matches!(path.kind, Kind::Webhook(_))) + { + components.insert( + "parameters".to_owned(), + serde_json::json!({ + "PayloadParam": { + "name": "payload", + "in": "query", + "required": true, + "description": "A URL-safe base64-encoded JSON string payload.", + "schema": { + "type": "string" + } + } + }), + ); + } + + { + let mut security_scheme = Map::new(); + + let SecuritySchemeToAdd { basic_http, bearer_jwt, api_keys } = + generate_all_security_schemes(future_paths); + + if basic_http { + security_scheme.insert( + BASIC_HTTP_AUTH_SCHEME.to_owned(), + serde_json::json!({ + "type": "http", + "scheme": "basic" + }), + ); + } + + if bearer_jwt { + security_scheme.insert( + JWT_SECURITY_SCHEME.to_owned(), + serde_json::json!({ + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }), + ); + } + + for (key, value) in api_keys { + security_scheme.insert(key, value); + } + components.insert("securitySchemes".to_owned(), Value::Object(security_scheme)); + } + + components.insert("requestBodies".to_owned(), serde_json::json!({ + DEFAULT_REQUEST_KEY: { + "description": "This route may or may not require a request body, but its structure and content type are unknown.", + "required": false, + "content": { + "application/json": {} + } + } + })); + + components.insert("responses".to_owned(), serde_json::json!({ + DEFAULT_ASYNC_RESPONSE_KEY: { + "description": "Returns a job ID as a UUID string.", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "uuid", + "examples": [ "550e8400-e29b-41d4-a716-446655440000" ] + } + } + } + }, + DEFAULT_SYNC_RESPONSE_KEY: { + "description": "This route may return a response, but its structure and content type are unknown.", + "content": { + "application/octet-stream": {} + } + }, + + })); + + components +} + +pub fn generate_openapi_document( + info: Option<&Info>, + url: Option<&Url>, + paths: Vec, + format: Format, +) -> Result { + let mut openapi_doc: IndexMap<&'static str, Value> = IndexMap::new(); + + openapi_doc.insert("openapi", to_value(&DEFAULT_OPENAPI_GENERATED_VERSION)?); + openapi_doc.insert( + "info", + to_value(info.unwrap_or(&DEFAULT_OPENAPI_INFO_OBJECT))?, + ); + + openapi_doc.insert("components", Value::Object(generate_components(&paths))); + + openapi_doc.insert("paths", to_value(generate_paths(paths, url)?)?); + + let openapi_document = match format { + Format::YAML => serde_yml::to_string(&openapi_doc).map_err(|err| { + anyhow!( + "Could not generate OpenAPI document in YAML format: {}", + err + ) + })?, + Format::JSON => serde_json::to_string_pretty(&openapi_doc).map_err(|err| { + anyhow!( + "Could not generate OpenAPI document in JSON format: {}", + err + ) + })?, + }; + + Ok(openapi_document) +} + +#[allow(unused)] +#[derive(Debug, Deserialize)] +struct HttpRouteFilter { + folder_regex: String, + path_regex: String, + route_path_regex: String, +} + +#[derive(Debug, Deserialize)] +struct WebhookFilter { + user_or_folder_regex: String, + user_or_folder_regex_value: String, + path: String, + runnable_kind: RunnableKind, +} + +#[derive(Debug, Deserialize)] +struct GenerateOpenAPI { + info: Option, + url: Option, + #[serde(default, deserialize_with = "empty_as_none")] + http_route_filters: Option>, + #[serde(default, deserialize_with = "empty_as_none")] + webhook_filters: Option>, + #[serde(default)] + openapi_spec_format: Format, +} + +#[cfg(feature = "http_trigger")] +async fn http_routes_to_future_paths( + db: &DB, + user_db: UserDB, + authed: &ApiAuthed, + pg_pool: &mut PgConnection, + http_route_filters: Option<&[HttpRouteFilter]>, + w_id: &str, +) -> Result> { + let mut http_routes = Vec::new(); + + if let Some(http_route_filters) = http_route_filters { + let path_regex = http_route_filters + .iter() + .map(|filter| { + transform_to_minified_postgres_regex(&format!( + "f/{}/{}", + filter.folder_regex, filter.path_regex + )) + }) + .collect_vec(); + + let route_path_regex = http_route_filters + .iter() + .map(|filter| transform_to_minified_postgres_regex(&filter.route_path_regex)) + .collect_vec(); + + #[derive(Debug, Deserialize)] + struct MinifiedHttpTrigger { + route_path: String, + http_method: HttpMethod, + is_async: bool, + workspaced_route: bool, + summary: Option, + description: Option, + authentication_method: AuthenticationMethod, + authentication_resource_path: Option, + } + + http_routes = sqlx::query_as!( + MinifiedHttpTrigger, + r#" + SELECT + route_path, + http_method AS "http_method: _", + is_async, + workspaced_route, + summary, + description, + authentication_method AS "authentication_method: _", + authentication_resource_path + FROM + http_trigger + WHERE + path ~ ANY($1) AND + route_path ~ ANY($2) AND + workspace_id = $3 + "#, + &path_regex, + &route_path_regex, + &w_id + ) + .fetch_all(pg_pool) + .await?; + } + + let mut openapi_future_paths = Vec::with_capacity(http_routes.len()); + + for http_route in http_routes { + let auth_method = match http_route.authentication_method { + AuthenticationMethod::BasicHttp => Some(SecurityScheme::BasicHttp), + AuthenticationMethod::Windmill => Some(SecurityScheme::BearerJwt), + AuthenticationMethod::ApiKey => { + let resource_path = match http_route.authentication_resource_path { + Some(resource_path) => resource_path, + None => { + return Err(Error::BadRequest( + "Missing authentication resource path".to_string(), + )); + } + }; + + let api = try_get_resource_from_db_as::( + authed, + Some(user_db.clone()), + db, + &resource_path, + w_id, + ) + .await?; + + Some(SecurityScheme::ApiKey(api.api_key_header)) + } + _ => None, + }; + + let route_path = if http_route.workspaced_route { + format!("{}/{}", w_id, http_route.route_path.trim_start_matches('/')) + } else { + http_route.route_path.clone() + }; + + let method = match http_route.http_method { + HttpMethod::Get => Method::GET, + HttpMethod::Post => Method::POST, + HttpMethod::Put => Method::PUT, + HttpMethod::Patch => Method::PATCH, + HttpMethod::Delete => Method::DELETE, + }; + + let future_path = FuturePath::new( + route_path, + Kind::HttpRoute(HttpRouteConfig::new(method)), + Some(http_route.is_async), + http_route.summary, + http_route.description, + auth_method, + ); + + openapi_future_paths.push(future_path); + } + + Ok(openapi_future_paths) +} + +#[cfg(not(feature = "http_trigger"))] +async fn http_routes_to_future_paths( + _db: &DB, + _user_db: UserDB, + _authed: &ApiAuthed, + _pg_pool: &mut PgConnection, + _http_route_filters: Option<&[HttpRouteFilter]>, + _w_id: &str, +) -> Result> { + Ok(Vec::new()) +} + +async fn webhook_to_future_paths( + pg_pool: &mut PgConnection, + webhook_filters: Option<&[WebhookFilter]>, + w_id: &str, +) -> Result> { + let mut openapi_future_paths = Vec::new(); + if let Some(webhook_filters) = webhook_filters { + let mut script_webhook_filter = Vec::new(); + let mut flow_webhook_filter = Vec::new(); + + for webhook in webhook_filters { + let full_regex = transform_to_minified_postgres_regex(&format!( + "{}/{}/{}", + &webhook.user_or_folder_regex, &webhook.user_or_folder_regex_value, &webhook.path + )); + + match webhook.runnable_kind { + RunnableKind::Script => { + script_webhook_filter.push(full_regex); + } + RunnableKind::Flow => { + flow_webhook_filter.push(full_regex); + } + } + } + + #[derive(Debug, Deserialize, Clone, Hash)] + struct MinifiedWebhook { + path: String, + description: Option, + summary: Option, + } + + let webhook_scripts = sqlx::query_as!( + MinifiedWebhook, + r#"SELECT + path, + summary, + description + FROM + script + WHERE + path ~ ANY($1) AND + workspace_id = $2 AND + archived is FALSE + "#, + &script_webhook_filter, + &w_id + ) + .fetch_all(&mut *pg_pool) + .await?; + + let webhook_flows = sqlx::query_as!( + MinifiedWebhook, + r#"SELECT + path, + summary, + description + FROM + flow + WHERE + path ~ ANY($1) AND + workspace_id = $2 AND + archived is FALSE + "#, + &flow_webhook_filter, + &w_id + ) + .fetch_all(&mut *pg_pool) + .await?; + + openapi_future_paths.reserve_exact(webhook_scripts.len() + webhook_flows.len()); + + for webhook in webhook_scripts { + openapi_future_paths.push(FuturePath::new( + webhook.path, + Kind::Webhook(WebhookConfig::new(RunnableKind::Script)), + None, + webhook.summary, + webhook.description, + Some(SecurityScheme::BearerJwt), + )); + } + + for webhook in webhook_flows { + openapi_future_paths.push(FuturePath::new( + webhook.path, + Kind::Webhook(WebhookConfig::new(RunnableKind::Flow)), + None, + webhook.summary, + webhook.description, + Some(SecurityScheme::BearerJwt), + )); + } + } + + Ok(openapi_future_paths) +} + +async fn generate_openapi_future_path( + db: &DB, + user_db: UserDB, + authed: &ApiAuthed, + http_route_filters: Option<&[HttpRouteFilter]>, + webhook_filters: Option<&[WebhookFilter]>, + w_id: &str, +) -> Result> { + if http_route_filters.is_none() && webhook_filters.is_none() { + return Err(Error::BadRequest( + "Expected http route filter and/or webhook filters".to_string(), + )); + } + + let mut tx = user_db.clone().begin(authed).await?; + + let mut openapi_future_paths = + http_routes_to_future_paths(db, user_db, authed, &mut tx, http_route_filters, w_id).await?; + + openapi_future_paths + .append(&mut webhook_to_future_paths(&mut tx, webhook_filters, w_id).await?); + + tx.commit().await?; + + if openapi_future_paths.is_empty() { + return Err(Error::NotFound( + "No match for the current filter".to_string(), + )); + } + + Ok(openapi_future_paths) +} + +async fn generate_openapi_spec( + Extension(authed): Extension, + Extension(db): Extension, + Extension(user_db): Extension, + Path(w_id): Path, + Json(generate_openapi): Json, +) -> Result { + let openapi_future_paths = generate_openapi_future_path( + &db, + user_db, + &authed, + generate_openapi.http_route_filters.as_deref(), + generate_openapi.webhook_filters.as_deref(), + &w_id, + ) + .await?; + + let openapi_document = generate_openapi_document( + generate_openapi.info.as_ref(), + generate_openapi.url.as_ref(), + openapi_future_paths, + generate_openapi.openapi_spec_format, + ); + + openapi_document +} + +async fn download_spec( + Extension(authed): Extension, + Extension(db): Extension, + Extension(user_db): Extension, + Path(w_id): Path, + Json(generate_openapi): Json, +) -> Result { + let openapi_future_paths = generate_openapi_future_path( + &db, + user_db, + &authed, + generate_openapi.http_route_filters.as_deref(), + generate_openapi.webhook_filters.as_deref(), + &w_id, + ) + .await?; + + let openapi_document = generate_openapi_document( + generate_openapi.info.as_ref(), + generate_openapi.url.as_ref(), + openapi_future_paths, + generate_openapi.openapi_spec_format, + )?; + + let response = Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ) + .body(Body::from(openapi_document)) + .unwrap(); + + Ok(response) +} diff --git a/backend/windmill-api/src/postgres_triggers/handler.rs b/backend/windmill-api/src/postgres_triggers/handler.rs index 4c791eeb8b..73e35c9e2f 100644 --- a/backend/windmill-api/src/postgres_triggers/handler.rs +++ b/backend/windmill-api/src/postgres_triggers/handler.rs @@ -15,24 +15,25 @@ use http::StatusCode; use itertools::Itertools; use pg_escape::{quote_identifier, quote_literal}; use quick_cache::sync::Cache; -use rust_postgres::types::Type; +use rust_postgres::{types::Type, Client}; use serde::{Deserialize, Deserializer, Serialize}; use sql_builder::{bind::Bind, SqlBuilder}; -use sqlx::{postgres::types::Oid, FromRow, PgConnection}; -use windmill_audit::{audit_ee::audit_log, ActionKind}; -use windmill_common::error::Error; +use sqlx::FromRow; +use windmill_audit::{audit_oss::audit_log, ActionKind}; use windmill_common::{ db::UserDB, - error::{self, JsonResult, Result}, - utils::{not_found_if_none, paginate, Pagination, StripPath}, + error::{self, to_anyhow, Error, JsonResult, Result}, + utils::{empty_as_none, not_found_if_none, paginate, Pagination, StripPath}, worker::CLOUD_HOSTED, }; +use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; use super::{ - create_logical_replication_slot_query, create_publication_query, - drop_logical_replication_slot_query, drop_publication_query, generate_random_string, - get_database_connection, ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS, + check_if_valid_publication_for_postgres_version, create_logical_replication_slot, + create_pg_publication, drop_publication, generate_random_string, get_default_pg_connection, + ERROR_PUBLICATION_NAME_NOT_EXISTS, }; +use anyhow::anyhow; use lazy_static::lazy_static; #[derive(FromRow, Serialize, Deserialize, Debug)] @@ -44,21 +45,24 @@ pub struct Postgres { pub dbname: String, #[serde(default)] pub sslmode: String, - pub root_certificate_pem: String, + #[serde(default, deserialize_with = "empty_as_none")] + pub root_certificate_pem: Option, } #[derive(Debug, Clone, FromRow, Serialize, Deserialize)] pub struct TableToTrack { pub table_name: String, + #[serde(default, deserialize_with = "empty_as_none")] pub where_clause: Option, - pub columns_name: Vec, + #[serde(default, deserialize_with = "empty_as_none")] + pub columns_name: Option>, } impl TableToTrack { fn new( table_name: String, where_clause: Option, - columns_name: Vec, + columns_name: Option>, ) -> TableToTrack { TableToTrack { table_name, where_clause, columns_name } } @@ -96,7 +100,6 @@ pub struct EditPostgresTrigger { } #[derive(Deserialize, Serialize, Debug)] - pub struct NewPostgresTrigger { path: String, script_path: String, @@ -121,7 +124,7 @@ pub async fn test_postgres_connection( Json(test_postgres): Json, ) -> Result<()> { let connect_f = async { - get_database_connection( + get_default_pg_connection( authed, Some(user_db), &db, @@ -186,7 +189,7 @@ where )); } - if !track_specific_columns_in_table && !table_to_track.columns_name.is_empty() { + if !track_specific_columns_in_table && table_to_track.columns_name.is_some() { track_specific_columns_in_table = true; } } @@ -264,39 +267,18 @@ 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( - connection: &mut PgConnection, + pg_connection: &mut Client, replication_slot_name: &str, -) -> 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(()) +) -> Result { + let row = pg_connection + .query_opt( + "SELECT slot_name FROM pg_replication_slots WHERE slot_name = $1", + &[&replication_slot_name], + ) + .await + .map_err(to_anyhow)?; + Ok(row.is_some()) } async fn create_custom_slot_and_publication_inner( @@ -307,33 +289,31 @@ async fn create_custom_slot_and_publication_inner( w_id: &str, publication: &PublicationData, ) -> Result { - 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( + let mut pg_connection = get_default_pg_connection( authed.clone(), - Some(user_db.clone()), + Some(user_db), &db, &postgres_resource_path, &w_id, ) + .await + .map_err(to_anyhow)?; + + let tx = pg_connection.transaction().await.map_err(to_anyhow)?; + + let publication_name = format!("windmill_trigger_{}", generate_random_string()); + let replication_slot_name = publication_name.clone(); + + create_logical_replication_slot(tx.client(), &replication_slot_name).await?; + create_pg_publication( + &tx.client(), + &publication_name, + publication.table_to_track.as_deref(), + &publication.transaction_to_track, + ) .await?; - sqlx::query(&query).execute(&mut connection).await?; - - let query = create_logical_replication_slot_query(&replication_slot_name); - - sqlx::query(&query).execute(&mut connection).await?; + tx.commit().await.map_err(to_anyhow)?; Ok(PostgresPublicationReplication::new( publication_name, @@ -341,6 +321,38 @@ async fn create_custom_slot_and_publication_inner( )) } +pub async fn get_postgres_version_internal(pg_connection: &Client) -> Result { + let row = pg_connection + .query_one("SHOW server_version;", &[]) + .await + .map_err(to_anyhow)?; + + let postgres_version: String = row.get(0); + + Ok(postgres_version) +} + +pub async fn get_postgres_version( + authed: ApiAuthed, + Extension(db): Extension, + Extension(user_db): Extension, + Path((w_id, postgres_resource_path)): Path<(String, String)>, +) -> Result { + let pg_connection = get_default_pg_connection( + authed.clone(), + Some(user_db), + &db, + &postgres_resource_path, + &w_id, + ) + .await + .map_err(to_anyhow)?; + + let postgres_version = get_postgres_version_internal(&pg_connection).await?; + + Ok(postgres_version) +} + pub async fn create_postgres_trigger( authed: ApiAuthed, Extension(user_db): Extension, @@ -374,6 +386,7 @@ 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(), @@ -394,7 +407,7 @@ pub async fn create_postgres_trigger( "Missing replication slot name".to_string(), )); } - (replication_slot_name.unwrap(), publication_name.unwrap()) + (publication_name.unwrap(), replication_slot_name.unwrap()) }; let mut tx = user_db.begin(&authed).await?; @@ -452,6 +465,17 @@ pub async fn create_postgres_trigger( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::PostgresTrigger { path: path.to_string() }, + Some(format!("Postgres trigger '{}' created", path)), + true, + ) + .await?; + Ok((StatusCode::CREATED, path.to_string())) } @@ -563,7 +587,7 @@ impl PublicationData { } } -#[derive(Debug, Serialize)] +#[derive(FromRow, Debug, Serialize)] pub struct SlotList { slot_name: Option, active: Option, @@ -575,30 +599,37 @@ pub async fn list_slot_name( Extension(db): Extension, Path((w_id, postgres_resource_path)): Path<(String, String)>, ) -> Result>> { - let mut connection = get_database_connection( + let pg_connection: Client = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let slots = sqlx::query_as!( - SlotList, - r#" - SELECT - slot_name, - active - FROM - pg_replication_slots - WHERE - plugin = 'pgoutput' AND - slot_type = 'logical'; - "# - ) - .fetch_all(&mut connection) - .await?; + let rows = pg_connection + .query( + r#" + SELECT + slot_name, + active + FROM + pg_replication_slots + WHERE + plugin = 'pgoutput' AND + slot_type = 'logical'; + "#, + &[], + ) + .await + .map_err(to_anyhow)?; + + let slots = rows + .into_iter() + .map(|row| SlotList { slot_name: row.get("slot_name"), active: row.get("active") }) + .collect(); Ok(Json(slots)) } @@ -615,20 +646,52 @@ pub async fn create_slot( Path((w_id, postgres_resource_path)): Path<(String, String)>, Json(Slot { name }): Json, ) -> Result { - let mut connection = get_database_connection( + let pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let query = create_logical_replication_slot_query(&name); + create_logical_replication_slot(&pg_connection, &name).await?; - sqlx::query(&query).execute(&mut connection).await?; + Ok(format!("Replication slot {} created!", name)) +} - Ok(format!("Slot {} created!", name)) +pub async fn drop_logical_replication_slot(pg_connection: &Client, slot_name: &str) -> Result<()> { + let row = pg_connection + .query_opt( + r#" + SELECT + active_pid + FROM + pg_replication_slots + WHERE + slot_name = $1 + "#, + &[&slot_name], + ) + .await + .map_err(to_anyhow)?; + + let active_pid = row.map(|r| r.get::<_, Option>(0)).flatten(); + + if let Some(pid) = active_pid { + pg_connection + .execute("SELECT pg_terminate_backend($1)", &[&pid]) + .await + .map_err(to_anyhow)?; + } + + pg_connection + .execute("SELECT pg_drop_replication_slot($1)", &[&slot_name]) + .await + .map_err(to_anyhow)?; + + Ok(()) } pub async fn drop_slot_name( @@ -638,23 +701,16 @@ pub async fn drop_slot_name( Path((w_id, postgres_resource_path)): Path<(String, String)>, Json(Slot { name }): Json, ) -> Result { - let mut connection = get_database_connection( - authed.clone(), - Some(user_db.clone()), - &db, - &postgres_resource_path, - &w_id, - ) - .await?; + let pg_connection = + get_default_pg_connection(authed, Some(user_db), &db, &postgres_resource_path, &w_id) + .await + .map_err(to_anyhow)?; - let query = drop_logical_replication_slot_query(&name); - sqlx::query(&query).execute(&mut connection).await?; + drop_logical_replication_slot(&pg_connection, &name) + .await + .map_err(to_anyhow)?; - Ok(format!("Slot name {} deleted!", name)) -} -#[derive(Debug, Serialize)] -struct PublicationName { - publication_name: String, + Ok(format!("Replication slot {} deleted!", name)) } pub async fn list_database_publication( @@ -663,25 +719,27 @@ pub async fn list_database_publication( Extension(db): Extension, Path((w_id, postgres_resource_path)): Path<(String, String)>, ) -> Result>> { - let mut connection = get_database_connection( + let pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let publication_names = sqlx::query_as!( - PublicationName, - "SELECT pubname AS publication_name FROM pg_publication;" - ) - .fetch_all(&mut connection) - .await?; + let rows = pg_connection + .query( + "SELECT pubname AS publication_name FROM pg_publication;", + &[], + ) + .await + .map_err(to_anyhow)?; - let publications = publication_names - .iter() - .map(|publication| publication.publication_name.to_owned()) + let publications = rows + .into_iter() + .map(|row| row.get::<_, String>("publication_name")) .collect_vec(); Ok(Json(publications)) @@ -693,21 +751,22 @@ pub async fn get_publication_info( Extension(db): Extension, Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>, ) -> Result> { - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; let publication_data = - get_publication_scope_and_transaction(&mut connection, &publication_name).await; + get_publication_scope_and_transaction(&mut pg_connection, &publication_name).await; let (all_table, transaction_to_track) = match publication_data { - Ok(pub_data) => pub_data, - Err(Error::SqlErr { error: sqlx::Error::RowNotFound, .. }) => { + Ok(Some(pub_data)) => pub_data, + Ok(None) => { return Err(Error::NotFound( ERROR_PUBLICATION_NAME_NOT_EXISTS.to_string(), )) @@ -716,7 +775,7 @@ pub async fn get_publication_info( }; let table_to_track = if !all_table { - Some(get_tracked_relations(&mut connection, &publication_name).await?) + Some(get_tracked_relations(&mut pg_connection, &publication_name).await?) } else { None }; @@ -733,24 +792,29 @@ pub async fn create_publication( Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>, Json(publication_data): Json, ) -> Result { - let PublicationData { table_to_track, transaction_to_track } = publication_data; - - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let query = create_publication_query( + let PublicationData { table_to_track, transaction_to_track } = publication_data; + + let tx = pg_connection.transaction().await.map_err(to_anyhow)?; + + create_pg_publication( + tx.client(), &publication_name, table_to_track.as_deref(), - &transaction_to_track.iter().map(AsRef::as_ref).collect_vec(), - ); + &transaction_to_track, + ) + .await?; - sqlx::query(&query).execute(&mut connection).await?; + tx.commit().await.map_err(to_anyhow)?; Ok(format!( "Publication {} successfully created!", @@ -764,18 +828,17 @@ pub async fn delete_publication( Extension(db): Extension, Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>, ) -> Result { - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let query = drop_publication_query(&publication_name); - - sqlx::query(&query).execute(&mut connection).await?; + drop_publication(&mut pg_connection, &publication_name).await?; Ok(format!( "Publication {} successfully deleted!", @@ -783,57 +846,68 @@ pub async fn delete_publication( )) } -pub fn get_update_publication_query( +pub async fn update_pg_publication( + pg_connection: &Client, publication_name: &str, PublicationData { table_to_track, transaction_to_track }: PublicationData, - all_table: bool, -) -> Vec { - let quoted_publication_name = quote_identifier(&publication_name); - + all_table: Option, +) -> Result<()> { + 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 { - queries.push(drop_publication_query(&publication_name)); - queries.push(create_publication_query( - &publication_name, + // If all_table is None, the publication does not exist yet + if all_table.unwrap_or(true) { + if all_table.is_some_and(|all_table| all_table) { + drop_publication(pg_connection, publication_name) + .await + .map_err(to_anyhow)?; + } + create_pg_publication( + pg_connection, + publication_name, table_to_track.as_deref(), - &transaction_to_track.iter().map(AsRef::as_ref).collect_vec(), - )); + &transaction_to_track, + ) + .await + .map_err(to_anyhow)?; } else { - let mut query = String::from(""); + let pg_14 = check_if_valid_publication_for_postgres_version( + pg_connection, + table_to_track.as_deref(), + ) + .await + .map_err(to_anyhow)?; + + let mut query = format!("ALTER PUBLICATION {} SET ", quoted_publication_name); + let mut first = true; - query.push_str("ALTER PUBLICATION "); - query.push_str("ed_publication_name); - query.push_str(" SET"); for (i, schema) in relations.iter().enumerate() { if schema.table_to_track.is_empty() { - query.push_str(" TABLES IN SCHEMA "); - let quoted_schema = quote_identifier(&schema.schema_name); - query.push_str("ed_schema); + query.push_str("TABLES IN SCHEMA "); + query.push_str("e_identifier(&schema.schema_name)); } else { - query.push_str(" TABLE ONLY "); + if pg_14 && first { + query.push_str("TABLE ONLY "); + first = false; + } else if !pg_14 { + 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); + let full_name = format!("{}.{}", schema_name, table_name); query.push_str(&full_name); - if !table.columns_name.is_empty() { - query.push_str(" ("); - let columns = table - .columns_name - .iter() - .map(|column| quote_identifier(column)) - .join(", "); - query.push_str(&columns); - query.push_str(") "); + + if let Some(columns) = table.columns_name.as_ref() { + let cols = + columns.iter().map(|col| quote_identifier(col)).join(", "); + query.push_str(&format!(" ({})", cols)); } if let Some(where_clause) = &table.where_clause { - query.push_str(" WHERE ("); - query.push_str(where_clause); - query.push(')'); + query.push_str(&format!(" WHERE ({})", where_clause)); } if j + 1 != schema.table_to_track.len() { @@ -841,39 +915,43 @@ pub fn get_update_publication_query( } } } - if i < relations.len() - 1 { - query.push(','); + + if i + 1 != relations.len() { + query.push_str(", "); } } - query.push(';'); - queries.push(query); + pg_connection + .execute(&query, &[]) + .await + .map_err(to_anyhow)?; - let mut query = String::new(); - - query.push_str("ALTER PUBLICATION "); - query.push_str("ed_publication_name); - query.push_str(&format!( - " SET (publish = '{}');", - transaction_to_track_as_str - )); - queries.push(query); + let publish_query = format!( + "ALTER PUBLICATION {} SET (publish = '{}');", + quoted_publication_name, transaction_to_track_as_str + ); + pg_connection + .execute(&publish_query, &[]) + .await + .map_err(to_anyhow)?; } } _ => { - queries.push(drop_publication_query(&publication_name)); - let to_execute = format!( - r#" - CREATE - PUBLICATION {} FOR ALL TABLES WITH (publish = '{}'); - "#, + drop_publication(pg_connection, publication_name) + .await + .map_err(to_anyhow)?; + let create_all_query = format!( + "CREATE PUBLICATION {} FOR ALL TABLES WITH (publish = '{}');", quoted_publication_name, transaction_to_track_as_str ); - queries.push(to_execute); + pg_connection + .execute(&create_all_query, &[]) + .await + .map_err(to_anyhow)?; } - }; + } - queries + Ok(()) } pub async fn alter_publication( @@ -883,25 +961,32 @@ pub async fn alter_publication( Path((w_id, publication_name, postgres_resource_path)): Path<(String, String, String)>, Json(publication_data): Json, ) -> Result { - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - check_if_publication_exist(&mut connection, &publication_name).await?; + let tx = pg_connection.transaction().await.map_err(to_anyhow)?; - let (all_table, _) = - get_publication_scope_and_transaction(&mut connection, &publication_name).await?; + let publication = get_publication_scope_and_transaction(tx.client(), &publication_name) + .await + .map_err(to_anyhow)?; - let queries = get_update_publication_query(&publication_name, publication_data, all_table); + update_pg_publication( + tx.client(), + &publication_name, + publication_data, + publication.map(|publication| publication.0), + ) + .await + .map_err(to_anyhow)?; - for query in queries { - sqlx::query(&query).execute(&mut connection).await?; - } + tx.commit().await.map_err(to_anyhow)?; Ok(format!( "Publication {} updated with success", @@ -909,104 +994,126 @@ pub async fn alter_publication( )) } -async fn get_publication_scope_and_transaction( - connection: &mut PgConnection, +pub async fn get_publication_scope_and_transaction( + pg_connection: &Client, publication_name: &str, -) -> std::result::Result<(bool, Vec), Error> { - #[derive(Debug, Deserialize, FromRow)] - struct PublicationTransaction { - all_table: bool, - insert: bool, - update: bool, - delete: bool, - } +) -> Result)>> { + let row_opt = pg_connection + .query_opt( + r#" + SELECT + puballtables AS all_table, + pubinsert AS insert, + pubupdate AS update, + pubdelete AS delete + FROM + pg_publication + WHERE + pubname = $1 + "#, + &[&publication_name], + ) + .await + .map_err(to_anyhow)?; - let transaction = sqlx::query_as!( - PublicationTransaction, - r#" - SELECT - puballtables AS all_table, - pubinsert AS insert, - pubupdate AS update, - pubdelete AS delete - FROM - pg_publication - WHERE - pubname = $1 - "#, - publication_name - ) - .fetch_one(&mut *connection) - .await?; + let row = match row_opt { + Some(r) => r, + None => return Ok(None), + }; + + let all_table: bool = row.get("all_table"); + let pub_insert: bool = row.get("insert"); + let pub_update: bool = row.get("update"); + let pub_delete: bool = row.get("delete"); let mut transaction_to_track = Vec::with_capacity(3); - - if transaction.insert { + if pub_insert { transaction_to_track.push("insert".to_string()); } - if transaction.update { + if pub_update { transaction_to_track.push("update".to_string()); } - if transaction.delete { + if pub_delete { transaction_to_track.push("delete".to_string()); } - Ok((transaction.all_table, transaction_to_track)) + Ok(Some((all_table, transaction_to_track))) } -async fn get_tracked_relations( - connection: &mut PgConnection, +pub async fn get_tracked_relations( + pg_connection: &Client, publication_name: &str, ) -> Result> { - #[derive(Debug, Deserialize, FromRow)] - struct PublicationData { - schema_name: Option, - table_name: Option, - columns: Option>, - where_clause: Option, - } + let pg_version = get_postgres_version_internal(pg_connection).await?; - let publications = sqlx::query_as!( - PublicationData, + let query = if pg_version.starts_with("14") { r#" - SELECT + SELECT schemaname AS schema_name, tablename AS table_name, - 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, + 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, rowfilter AS where_clause - FROM - pg_publication_tables - WHERE - pubname = $1; - "#, - publication_name - ) - .fetch_all(&mut *connection) - .await?; + FROM + pg_publication_tables + WHERE + pubname = $1; + "# + }; + + let rows = pg_connection + .query(query, &[&publication_name]) + .await + .map_err(to_anyhow)?; let mut table_to_track: HashMap = HashMap::new(); - for publication in publications { - let schema_name = publication.schema_name.unwrap(); + for row in rows { + let schema_name: Option = row.get("schema_name"); + let table_name: Option = row.get("table_name"); + let columns: Option> = row.get("columns"); + let where_clause: Option = row.get("where_clause"); + + let schema_name = schema_name.ok_or_else::( || { + anyhow!( + "Unexpected NULL `schema_name` in publication entry (pubname: `{}`). This should never happen unless PostgreSQL internals are corrupted.", + publication_name, + ).into() + } + )?; + + let table_name = table_name.ok_or_else::(|| { + anyhow!( + "Unexpected NULL `table_name` for schema `{}` in publication `{}`. This should never happen unless PostgreSQL internals are corrupted.", + schema_name, + publication_name, + ).into() + })?; + let entry = table_to_track.entry(schema_name.clone()); - let table_to_track = TableToTrack::new( - publication.table_name.unwrap(), - publication.where_clause, - publication.columns.unwrap_or_default(), - ); + let table_to_track = TableToTrack::new(table_name, where_clause, columns); + match entry { - Occupied(mut occuped) => { + std::collections::hash_map::Entry::Occupied(mut occuped) => { occuped.get_mut().add_new_table(table_to_track); } - Vacant(vacant) => { + std::collections::hash_map::Entry::Vacant(vacant) => { vacant.insert(Relations::new(schema_name, vec![table_to_track])); } } } + Ok(table_to_track.into_values().collect_vec()) } @@ -1073,27 +1180,49 @@ pub async fn update_postgres_trigger( publication, } = postgres_trigger; - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - check_if_logical_replication_slot_exist(&mut connection, &replication_slot_name).await?; + let exists = + check_if_logical_replication_slot_exist(&mut pg_connection, &replication_slot_name).await?; + + let tx = pg_connection.transaction().await.map_err(to_anyhow)?; + + if !exists { + tracing::debug!( + "Logical replication slot named: {} does not exists creating it...", + &replication_slot_name + ); + create_logical_replication_slot(tx.client(), &replication_slot_name) + .await + .map_err(to_anyhow)?; + } if let Some(publication) = publication { - check_if_publication_exist(&mut connection, &publication_name).await?; - let (all_table, _) = - get_publication_scope_and_transaction(&mut connection, &publication_name).await?; + let publication_data = + get_publication_scope_and_transaction(tx.client(), &publication_name) + .await + .map_err(to_anyhow)?; - let queries = get_update_publication_query(&publication_name, publication, all_table); - for query in queries { - sqlx::query(&query).execute(&mut connection).await?; - } + update_pg_publication( + tx.client(), + &publication_name, + publication, + publication_data.map(|publication| publication.0), + ) + .await + .map_err(to_anyhow)?; } + + tx.commit().await.map_err(to_anyhow)?; + let mut tx = user_db.begin(&authed).await?; sqlx::query!( @@ -1133,7 +1262,7 @@ pub async fn update_postgres_trigger( &mut *tx, &authed, "postgres_triggers.update", - ActionKind::Create, + ActionKind::Update, &w_id, Some(&path), None, @@ -1142,11 +1271,23 @@ pub async fn update_postgres_trigger( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::PostgresTrigger { path: path.to_string() }, + Some(format!("Postgres trigger '{}' updated", path)), + true, + ) + .await?; + Ok(workspace_path.to_string()) } pub async fn delete_postgres_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> Result { @@ -1178,6 +1319,17 @@ pub async fn delete_postgres_trigger( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::PostgresTrigger { path: path.to_string() }, + Some(format!("Postgres trigger '{}' deleted", path)), + true, + ) + .await?; + Ok(format!("Postgres trigger {path} deleted")) } @@ -1206,6 +1358,7 @@ pub async fn exists_postgres_trigger( pub async fn set_enabled( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(payload): Json, @@ -1254,6 +1407,17 @@ pub async fn set_enabled( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + DeployedObject::PostgresTrigger { path: path.to_string() }, + Some(format!("Postgres trigger '{}' updated", path)), + true, + ) + .await?; + Ok(format!( "succesfully updated postgres trigger at path {} to status {}", path, payload.enabled @@ -1277,52 +1441,40 @@ pub async fn create_template_script( Json(template_script): Json, ) -> Result { let TemplateScript { postgres_resource_path, relations, language } = template_script; - if relations.is_none() { - return Err(Error::BadRequest( - "You must at least choose schema to fetch table from".to_string(), - )); - } - let mut connection = get_database_connection( + let relations = match relations { + Some(r) => r, + None => return Err(anyhow!("You must at least choose schema to fetch table from").into()), + }; + + let pg_connection: Client = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - #[derive(Debug, FromRow, Deserialize)] - struct ColumnInfo { - table_schema: Option, - table_name: Option, - column_name: Option, - oid: Oid, - is_nullable: bool, - } - - let relations = relations.unwrap(); let mut schema_or_fully_qualified_name = Vec::with_capacity(relations.len()); - let mut columns_list = Vec::new(); + let mut columns_list = Vec::with_capacity(relations.len()); + for relation in relations { if !relation.table_to_track.is_empty() { - for table_to_track in relation.table_to_track { - let fully_qualified_name = - format!("{}.{}", &relation.schema_name, table_to_track.table_name); + for table in relation.table_to_track { + let fully_qualified_name = format!("{}.{}", relation.schema_name, table.table_name); schema_or_fully_qualified_name.push(quote_literal(&fully_qualified_name)); - - let columns = if !table_to_track.columns_name.is_empty() { - quote_literal(&table_to_track.columns_name.join(",")) - } else { - "''".to_string() - }; + let columns = table + .columns_name + .map(|c| quote_literal(&c.join(","))) + .unwrap_or_else(|| "''".to_string()); columns_list.push(columns); } - continue; + } else { + schema_or_fully_qualified_name.push(quote_literal(&relation.schema_name)); + columns_list.push("''".to_string()); } - - schema_or_fully_qualified_name.push(quote_literal(&relation.schema_name)); - columns_list.push(String::from("''")); } let tables_name = schema_or_fully_qualified_name.join(","); @@ -1342,8 +1494,7 @@ pub async fn create_template_script( WHEN tcm.column_list = '' THEN NULL ELSE string_to_array(tcm.column_list, ',') END AS columns - FROM - table_column_mapping tcm + FROM table_column_mapping tcm ) SELECT ns.nspname AS table_schema, @@ -1351,22 +1502,16 @@ pub async fn create_template_script( attr.attname AS column_name, attr.atttypid AS oid, attr.attnotnull AS is_nullable - FROM - pg_attribute attr - JOIN - pg_class cls - ON attr.attrelid = cls.oid - JOIN - pg_namespace ns - ON cls.relnamespace = ns.oid - JOIN - parsed_columns pc + FROM pg_attribute attr + JOIN pg_class cls ON attr.attrelid = cls.oid + JOIN pg_namespace ns ON cls.relnamespace = ns.oid + JOIN parsed_columns pc ON ns.nspname || '.' || cls.relname = pc.table_name OR ns.nspname = pc.table_name WHERE - attr.attnum > 0 -- Exclude system columns - AND NOT attr.attisdropped -- Exclude dropped columns - AND cls.relkind = 'r' -- Restrict to base tables + attr.attnum > 0 + AND NOT attr.attisdropped + AND cls.relkind = 'r' AND ( pc.columns IS NULL OR attr.attname = ANY(pc.columns) @@ -1375,55 +1520,52 @@ pub async fn create_template_script( tables_name, columns_list ); - let rows: Vec = sqlx::query_as(&query) - .fetch_all(&mut connection) - .await - .map_err(|e| error::Error::SqlErr { error: e, location: "pg_trigger".to_string() })?; + let rows = pg_connection.query(&query, &[]).await.map_err(to_anyhow)?; - let mut mapper: HashMap>> = HashMap::new(); + let mut schema_map: HashMap>> = HashMap::new(); + + #[derive(Debug)] + struct ColumnInfo { + table_schema: String, + table_name: String, + column_name: String, + oid: u32, + is_nullable: bool, + } for row in rows { - let ColumnInfo { table_schema, table_name, column_name, oid, is_nullable } = row; - - let entry = mapper.entry(table_schema.unwrap()); + let info = ColumnInfo { + table_schema: row.get("table_schema"), + table_name: row.get("table_name"), + column_name: row.get("column_name"), + oid: row.get::<_, u32>("oid"), + is_nullable: row.get::<_, bool>("is_nullable"), + }; let mapped_info = - MappingInfo::new(column_name.unwrap(), Type::from_oid(oid.0), is_nullable); + MappingInfo::new(info.column_name, Type::from_oid(info.oid), info.is_nullable); - match entry { - Occupied(mut occupied) => { - let entry = occupied.get_mut().entry(table_name.unwrap()); - match entry { - Occupied(mut occuped) => { - let mapping_info = occuped.get_mut(); - mapping_info.push(mapped_info); - } - Vacant(vacant) => { - let mut mapping_info = Vec::with_capacity(10); - mapping_info.push(mapped_info); - vacant.insert(mapping_info); - } + match schema_map.entry(info.table_schema) { + Occupied(mut schema_entry) => match schema_entry.get_mut().entry(info.table_name) { + Occupied(mut table_entry) => { + table_entry.get_mut().push(mapped_info); } - } - Vacant(vacant) => { - let mut mapping_info = Vec::with_capacity(10); - mapping_info.push(mapped_info); - vacant.insert(HashMap::from([(table_name.unwrap(), mapping_info)])); + Vacant(v) => { + v.insert(vec![mapped_info]); + } + }, + Vacant(schema_vacant) => { + let mut table_map = HashMap::new(); + table_map.insert(info.table_name, vec![mapped_info]); + schema_vacant.insert(table_map); } } } - let mapper = Mapper::new(mapper, language); - - let create_template_id = |w_id: &str| -> String { - let uuid = uuid::Uuid::new_v4().to_string(); - let id = format!("{}-{}", &w_id, &uuid); - - id - }; - + let mapper = Mapper::new(schema_map, language); let template = mapper.get_template(); - let id = create_template_id(&w_id); + + let id = format!("{}-{}", w_id, uuid::Uuid::new_v4()); TEMPLATE.insert(id.clone(), template); @@ -1436,24 +1578,24 @@ pub async fn is_database_in_logical_level( Extension(db): Extension, Path((w_id, postgres_resource_path)): Path<(String, String)>, ) -> error::JsonResult { - let mut connection = get_database_connection( + let pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, &postgres_resource_path, &w_id, ) - .await?; + .await + .map_err(to_anyhow)?; - let wal_level = sqlx::query_scalar!("SHOW WAL_LEVEL;") - .fetch_optional(&mut connection) - .await? - .flatten(); + let row_opt = pg_connection + .query_opt("SHOW wal_level;", &[]) + .await + .map_err(to_anyhow)?; - let is_logical = match wal_level.as_deref() { - Some("logical") => true, - _ => false, - }; + let wal_level: Option = row_opt.map(|row| row.get(0)); + + let is_logical = matches!(wal_level.as_deref(), Some("logical")); Ok(Json(is_logical)) } diff --git a/backend/windmill-api/src/postgres_triggers/mapper.rs b/backend/windmill-api/src/postgres_triggers/mapper.rs index ec3626c7f7..bbc7b9c011 100644 --- a/backend/windmill-api/src/postgres_triggers/mapper.rs +++ b/backend/windmill-api/src/postgres_triggers/mapper.rs @@ -26,8 +26,8 @@ fn postgres_to_typescript_type(postgres_type: Option) -> String { Type::DATE_ARRAY => "Array", Type::TIME => "string", Type::TIME_ARRAY => "Array", - Type::TIMESTAMPTZ | Type::TIMESTAMP => "Date", - Type::TIMESTAMPTZ_ARRAY | Type::TIMESTAMP_ARRAY => "Array", + Type::TIMESTAMPTZ | Type::TIMESTAMP => "string", + Type::TIMESTAMPTZ_ARRAY | Type::TIMESTAMP_ARRAY => "Array", Type::UUID => "string", Type::UUID_ARRAY => "Array", Type::JSON | Type::JSONB | Type::JSON_ARRAY | Type::JSONB_ARRAY => "unknown", @@ -124,11 +124,13 @@ export async function main( transaction_type: "insert" | "update" | "delete", schema_name: string, table_name: string, - row: {} + row: {}, + old_row?: {} ) {{ }} "#, - struct_definition + &struct_definition, + &struct_definition ) } } diff --git a/backend/windmill-api/src/postgres_triggers/mod.rs b/backend/windmill-api/src/postgres_triggers/mod.rs index d041f1f508..eff341436f 100644 --- a/backend/windmill-api/src/postgres_triggers/mod.rs +++ b/backend/windmill-api/src/postgres_triggers/mod.rs @@ -2,19 +2,18 @@ use crate::{ db::{ApiAuthed, DB}, jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery}, resources::try_get_resource_from_db_as, + trigger_helpers::TriggerJobArgs, users::fetch_api_authed, }; use chrono::Utc; use itertools::Itertools; -use pg_escape::{quote_identifier, quote_literal}; +use native_tls::{Certificate, TlsConnector}; +use pg_escape::quote_identifier; use rand::Rng; +use rust_postgres::{config::SslMode, Client, Config, NoTls}; +use rust_postgres_native_tls::MakeTlsConnector; use serde_json::value::RawValue; -use sqlx::{ - postgres::{PgConnectOptions, PgSslMode}, - Connection, PgConnection, -}; use std::collections::HashMap; -use std::str::FromStr; use axum::{ routing::{delete, get, post}, @@ -24,13 +23,17 @@ 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_publication_info, get_template_script, + exists_postgres_trigger, get_postgres_trigger, get_postgres_version, + get_postgres_version_internal, 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, utils::StripPath}; -use windmill_queue::PushArgsOwned; +use windmill_common::{ + db::UserDB, + error::{to_anyhow, Error, Result}, + utils::StripPath, +}; mod bool; mod converter; mod handler; @@ -47,77 +50,184 @@ 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_database_connection( +fn build_tls_connector( + ssl_mode: SslMode, + root_certificate_pem: Option<&String>, +) -> Result> { + let get_tls_builder_for_verify = |root_certificate: Option<&String>| { + let mut builder = TlsConnector::builder(); + if let Some(root_certificate) = root_certificate { + let root_certificate_pem = + Certificate::from_pem(root_certificate.as_bytes()).map_err(to_anyhow)?; + builder.add_root_certificate(root_certificate_pem); + } + Ok::<_, Error>(builder) + }; + let connector = match ssl_mode { + SslMode::Disable => return Ok(None), + SslMode::Require | SslMode::Prefer => { + let mut builder = TlsConnector::builder(); + builder.danger_accept_invalid_certs(true); + builder.danger_accept_invalid_hostnames(true); + builder + } + + SslMode::VerifyCa => { + let mut builder = get_tls_builder_for_verify(root_certificate_pem)?; + builder.danger_accept_invalid_hostnames(true); + builder + } + + SslMode::VerifyFull => { + let builder = get_tls_builder_for_verify(root_certificate_pem)?; + builder + } + _ => unreachable!(), + }; + + Ok(Some(MakeTlsConnector::new( + connector.build().map_err(to_anyhow)?, + ))) +} + +pub async fn get_raw_postgres_connection( + database: &Postgres, + logical_mode: bool, +) -> Result { + let ssl_mode = match database.sslmode.as_ref() { + "disable" => SslMode::Disable, + "" | "prefer" | "allow" => SslMode::Prefer, + "require" => SslMode::Require, + "verify-ca" => SslMode::VerifyCa, + "verify-full" => SslMode::VerifyFull, + ssl_mode => { + return Err(Error::BadRequest( + format!("Invalid ssl mode for postgres: {}, please put a valid ssl_mode among the following available ssl mode: ['disable', 'allow', 'prefer', 'verify-ca', 'verify-full']", ssl_mode), + )) + } + }; + + let mut config = Config::new(); + config + .dbname(&database.dbname) + .host(&database.host) + .user(&database.user) + .ssl_mode(ssl_mode); + + if logical_mode { + config.replication_mode(rust_postgres::config::ReplicationMode::Logical); + } + + if let Some(port) = database.port { + config.port(port); + }; + + if !database.password.is_empty() { + config.password(&database.password); + } + + let connector = build_tls_connector(ssl_mode, database.root_certificate_pem.as_ref())?; + + let client = if let Some(connector) = connector { + let (client, connection) = config.connect(connector).await.map_err(to_anyhow)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + tracing::debug!("{:#?}", e); + }; + tracing::info!("Successfully Connected into database"); + }); + client + } else { + let (client, connection) = config.connect(NoTls).await.map_err(to_anyhow)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + tracing::debug!("{:#?}", e); + }; + tracing::info!("Successfully Connected into database"); + }); + client + }; + + Ok(client) +} + +pub async fn get_pg_connection( authed: ApiAuthed, user_db: Option, db: &DB, postgres_resource_path: &str, w_id: &str, -) -> std::result::Result { + logical_mode: bool, +) -> Result { let database = - try_get_resource_from_db_as::(authed, user_db, db, postgres_resource_path, w_id) + try_get_resource_from_db_as::(&authed, user_db, db, postgres_resource_path, w_id) .await?; - Ok(get_raw_postgres_connection(&database).await?) + Ok(get_raw_postgres_connection(&database, logical_mode).await?) } -pub async fn get_raw_postgres_connection( - db: &Postgres, -) -> std::result::Result { - let options = { - let sslmode = if !db.sslmode.is_empty() { - PgSslMode::from_str(&db.sslmode)? - } else { - PgSslMode::Prefer - }; - let options = { - let inner_options = PgConnectOptions::new() - .host(&db.host) - .database(&db.dbname) - .ssl_mode(sslmode) - .username(&db.user); +pub async fn get_default_pg_connection( + authed: ApiAuthed, + user_db: Option, + db: &DB, + postgres_resource_path: &str, + w_id: &str, +) -> Result { + get_pg_connection(authed, user_db, db, postgres_resource_path, w_id, false).await +} - if let Some(port) = db.port { - inner_options.port(port) - } else { - inner_options - } - }; +pub async fn create_logical_replication_slot(tx: &Client, slot_name: &str) -> Result<()> { + tx.execute( + &format!("SELECT * FROM pg_create_logical_replication_slot($1, 'pgoutput')"), + &[&slot_name], + ) + .await + .map_err(to_anyhow)?; + Ok(()) +} - let options = if !db.root_certificate_pem.is_empty() { - options.ssl_root_cert_from_pem(db.root_certificate_pem.as_bytes().to_vec()) - } else { - options - }; +async fn check_if_valid_publication_for_postgres_version( + pg_connection: &Client, + table_to_track: Option<&[Relations]>, +) -> Result { + let postgres_version = get_postgres_version_internal(pg_connection).await?; - if !db.password.is_empty() { - options.password(&db.password) - } else { - options + 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(PgConnection::connect_with(&options).await?) + } + Ok(pg_14) } -pub fn create_logical_replication_slot_query(name: &str) -> String { - let query = format!( - r#" - SELECT - * - FROM - pg_create_logical_replication_slot({}, 'pgoutput');"#, - quote_literal(&name) - ); - - query -} - -pub fn create_publication_query( +pub async fn create_pg_publication( + pg_connection: &Client, publication_name: &str, table_to_track: Option<&[Relations]>, - transaction_to_track: &[&str], -) -> String { + transaction_to_track: &[String], +) -> Result<()> { + let pg_14 = + check_if_valid_publication_for_postgres_version(pg_connection, table_to_track).await?; let mut query = String::from("CREATE PUBLICATION "); query.push_str("e_identifier(publication_name)); @@ -125,21 +235,26 @@ pub fn create_publication_query( 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 { - query.push_str(" TABLE ONLY "); + if pg_14 && first { + query.push_str(" TABLE ONLY "); + first = false + } else if !pg_14 { + 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 !table.columns_name.is_empty() { + if let Some(columns) = table.columns_name.as_ref() { query.push_str(" ("); - let columns = table - .columns_name + let columns = columns .iter() .map(|column| quote_identifier(column)) .join(", "); @@ -175,22 +290,24 @@ pub fn create_publication_query( query.push_str("');"); } - query + pg_connection + .execute(&query, &[]) + .await + .map_err(to_anyhow)?; + Ok(()) } -pub fn drop_publication_query(publication_name: &str) -> String { +pub async fn drop_publication(pg_connection: &Client, publication_name: &str) -> Result<()> { 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 -} -pub fn drop_logical_replication_slot_query(replication_slot_name: &str) -> String { - format!( - "SELECT pg_drop_replication_slot({});", - quote_literal(&replication_slot_name) - ) + pg_connection + .execute(&query, &[]) + .await + .map_err(to_anyhow)?; + + Ok(()) } pub fn generate_random_string() -> String { @@ -229,6 +346,10 @@ 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)) @@ -247,15 +368,23 @@ pub fn workspaced_service() -> Router { ) .nest("/publication", publication_service()) .nest("/slot", slot_service()) + .nest("/postgres", postgres_service()) } async fn run_job( - args: Option>>, - extra: Option>>, + payload: HashMap>, db: &DB, trigger: &PostgresTrigger, ) -> anyhow::Result<()> { - let args = PushArgsOwned { args: args.unwrap_or_default(), extra }; + let args = PostgresTrigger::build_job_args( + &trigger.script_path, + trigger.is_flow, + &trigger.workspace_id, + db, + payload, + HashMap::new(), + ) + .await?; let authed = fetch_api_authed( trigger.edited_by.clone(), @@ -279,7 +408,6 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await?; } else { @@ -291,7 +419,6 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - None, ) .await?; } diff --git a/backend/windmill-api/src/postgres_triggers/relation.rs b/backend/windmill-api/src/postgres_triggers/relation.rs index f893f0ed5b..f313efb893 100644 --- a/backend/windmill-api/src/postgres_triggers/relation.rs +++ b/backend/windmill-api/src/postgres_triggers/relation.rs @@ -47,7 +47,7 @@ impl RelationConverter { .ok_or(RelationConversionError::FailToFindMatchingTable) } - pub fn body_to_json( + pub fn row_to_json( &self, to_decode: (Oid, Vec), ) -> Result, RelationConversionError> { diff --git a/backend/windmill-api/src/postgres_triggers/trigger.rs b/backend/windmill-api/src/postgres_triggers/trigger.rs index 81f439d0ae..27313879b2 100644 --- a/backend/windmill-api/src/postgres_triggers/trigger.rs +++ b/backend/windmill-api/src/postgres_triggers/trigger.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, pin::Pin}; use crate::{ - capture::{insert_capture_payload, PostgresTriggerConfig, TriggerKind}, + capture::{insert_capture_payload, PostgresTriggerConfig}, db::{ApiAuthed, DB}, postgres_triggers::{ relation::RelationConverter, @@ -12,30 +12,34 @@ use crate::{ run_job, }, resources::try_get_resource_from_db_as, + trigger_helpers::TriggerJobArgs, users::fetch_api_authed, }; + use bytes::{BufMut, Bytes, BytesMut}; use chrono::TimeZone; use futures::{pin_mut, SinkExt, StreamExt}; -use native_tls::TlsConnector; use pg_escape::{quote_identifier, quote_literal}; use rand::seq::SliceRandom; -use rust_postgres::{config::SslMode, Client, Config, CopyBothDuplex, SimpleQueryMessage}; -use rust_postgres_native_tls::MakeTlsConnector; +use rust_postgres::{Client, CopyBothDuplex, SimpleQueryMessage}; use serde::Deserialize; use serde_json::value::RawValue; use sqlx::types::Json as SqlxJson; use windmill_common::{ - db::UserDB, error, utils::report_critical_error, worker::to_raw_value, INSTANCE_NAME, + db::UserDB, + error::{self, to_anyhow}, + triggers::TriggerKind, + utils::report_critical_error, + worker::to_raw_value, + INSTANCE_NAME, }; -use windmill_queue::PushArgsOwned; use super::{ - drop_logical_replication_slot_query, drop_publication_query, get_database_connection, - handler::{Postgres, PostgresTrigger}, + drop_publication, get_default_pg_connection, get_raw_postgres_connection, + handler::{drop_logical_replication_slot, Postgres, PostgresTrigger}, replication_message::PrimaryKeepAliveBody, - ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS, + Error, ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS, }; pub struct LogicalReplicationSettings { @@ -67,63 +71,11 @@ impl RowExist for Vec { } } -#[derive(thiserror::Error, Debug)] -enum Error { - #[error("Error from database: {0}")] - Postgres(#[from] rust_postgres::Error), - #[error("Error : {0}")] - Common(#[from] windmill_common::error::Error), - #[error("Tls Error: {0}")] - Tls(#[from] native_tls::Error), -} - pub struct PostgresSimpleClient(Client); impl PostgresSimpleClient { async fn new(database: &Postgres) -> Result { - let ssl_mode = match database.sslmode.as_ref() { - "disable" => SslMode::Disable, - "" | "prefer" | "allow" => SslMode::Prefer, - "require" => SslMode::Require, - "verify-ca" => SslMode::VerifyCa, - "verify-full" => SslMode::VerifyFull, - ssl_mode => { - return Err(Error::Common(windmill_common::error::Error::BadRequest( - format!("Invalid ssl mode for postgres: {}, please put a valid ssl_mode among the following avalible ssl mode: ['disable', 'allow', 'prefer', 'verify-ca', 'verify-full']", ssl_mode), - ))) - } - }; - - let mut config = Config::new(); - config - .dbname(&database.dbname) - .host(&database.host) - .user(&database.user) - .ssl_mode(ssl_mode) - .replication_mode(rust_postgres::config::ReplicationMode::Logical); - - if let Some(port) = database.port { - config.port(port); - }; - - if !database.password.is_empty() { - config.password(&database.password); - } - - if !database.root_certificate_pem.is_empty() { - config.ssl_root_cert(database.root_certificate_pem.as_bytes()); - } - - let connector = MakeTlsConnector::new(TlsConnector::new()?); - - let (client, connection) = config.connect(connector).await?; - - tokio::spawn(async move { - if let Err(e) = connection.await { - tracing::debug!("{:#?}", e); - }; - tracing::info!("Successfully Connected into database"); - }); + let client = get_raw_postgres_connection(database, true).await?; Ok(PostgresSimpleClient(client)) } @@ -154,7 +106,8 @@ impl PostgresSimpleClient { Ok(( self.0 .copy_both_simple::(query.as_str()) - .await?, + .await + .map_err(to_anyhow)?, LogicalReplicationSettings::new(false), )) } @@ -367,13 +320,8 @@ impl PostgresTrigger { .await } - async fn handle( - &self, - db: &DB, - args: Option>>, - extra: Option>>, - ) -> () { - if let Err(err) = run_job(args, extra, db, self).await { + async fn handle(&self, db: &DB, payload: HashMap>) -> () { + if let Err(err) = run_job(payload, db, self).await { report_critical_error( format!( "Failed to trigger job from postgres {}: {:?}", @@ -388,6 +336,20 @@ impl PostgresTrigger { } } +impl TriggerJobArgs>> for PostgresTrigger { + fn v1_payload_fn(payload: HashMap>) -> HashMap> { + payload + } + + fn v2_payload_fn(payload: HashMap>) -> HashMap> { + payload + } + + fn trigger_kind() -> TriggerKind { + TriggerKind::Postgres + } +} + struct PgInfo<'a> { postgres_resource_path: &'a str, publication_name: &'a str, @@ -455,7 +417,7 @@ impl PostgresConfig { }; let database = try_get_resource_from_db_as::( - authed, + &authed, Some(UserDB::new(db.clone())), &db, postgres_resource_path, @@ -470,12 +432,13 @@ impl PostgresConfig { "SELECT pubname FROM pg_publication WHERE pubname = {}", quote_literal(&publication_name) )) - .await?; + .await + .map_err(to_anyhow)?; if !publication.row_exist() { - return Err(Error::Common(error::Error::BadConfig( + return Err(Error::BadConfig( ERROR_PUBLICATION_NAME_NOT_EXISTS.to_string(), - ))); + )); } let replication_slot = client @@ -483,17 +446,19 @@ impl PostgresConfig { "SELECT slot_name FROM pg_replication_slots WHERE slot_name = {}", quote_literal(&replication_slot_name) )) - .await?; + .await + .map_err(to_anyhow)?; if !replication_slot.row_exist() { - return Err(Error::Common(error::Error::BadConfig( + return Err(Error::BadConfig( ERROR_REPLICATION_SLOT_NOT_EXISTS.to_string(), - ))); + )); } let (logical_replication_stream, logical_replication_settings) = client .get_logical_replication_stream(&publication_name, &replication_slot_name) - .await?; + .await + .map_err(to_anyhow)?; Ok((logical_replication_stream, logical_replication_settings)) } @@ -505,15 +470,10 @@ impl PostgresConfig { } } - async fn handle( - &self, - db: &DB, - args: Option>>, - extra: Option>>, - ) -> () { + async fn handle(&self, db: &DB, payload: HashMap>) -> () { match self { - PostgresConfig::Trigger(trigger) => trigger.handle(&db, args, extra).await, - PostgresConfig::Capture(capture) => capture.handle(&db, args, extra).await, + PostgresConfig::Trigger(trigger) => trigger.handle(&db, payload).await, + PostgresConfig::Capture(capture) => capture.handle(&db, payload).await, } } @@ -533,7 +493,7 @@ impl PostgresConfig { let user_db = UserDB::new(db.clone()); - let mut connection = get_database_connection( + let mut pg_connection = get_default_pg_connection( authed.clone(), Some(user_db.clone()), &db, @@ -542,13 +502,12 @@ impl PostgresConfig { ) .await?; - let query = drop_logical_replication_slot_query(replication_slot_name); + if capture.trigger_config.basic_mode.unwrap_or(false) { + drop_logical_replication_slot(&mut pg_connection, replication_slot_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; + drop_publication(&mut pg_connection, publication_name).await?; + } Ok(()) } @@ -603,6 +562,7 @@ async fn listen_to_transactions( } }; + let message = match message { Ok(message) => message, Err(err) => { @@ -646,37 +606,75 @@ async fn listen_to_transactions( None } Insert(insert) => { - Some((insert.o_id, relations.body_to_json((insert.o_id, insert.tuple)), "insert")) + Some((insert.o_id, Ok(None), relations.row_to_json((insert.o_id, insert.tuple)), "insert")) } Update(update) => { - Some((update.o_id, relations.body_to_json((update.o_id, update.new_tuple)), "update")) + let old_row = update.old_tuple.map(|old_tuple| relations.row_to_json((update.o_id, old_tuple))).transpose(); + let row = relations.row_to_json((update.o_id, update.new_tuple)); + Some((update.o_id, old_row, row, "update")) } Delete(delete) => { - let body = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap()); - Some((delete.o_id, relations.body_to_json((delete.o_id, body)), "delete")) + let row = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap()); + Some((delete.o_id, Ok(None), relations.row_to_json((delete.o_id, row)), "delete")) } }; - if let Some((o_id, Ok(body), transaction_type)) = json { - let relation = match relations.get_relation(o_id) { - Ok(relation) => relation, - Err(err) => { - tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string()); - continue; + match json { + Some((o_id, Ok(old_row), Ok(row), transaction_type)) => { + let relation = match relations.get_relation(o_id) { + Ok(relation) => relation, + Err(err) => { + tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string()); + continue; + } + }; + let database_info = HashMap::from([ + ("schema_name".to_string(), to_raw_value(&relation.namespace)), + ("table_name".to_string(), to_raw_value(&relation.name)), + ("transaction_type".to_string(), to_raw_value(&transaction_type)), + ("old_row".to_string(), to_raw_value(&old_row)), + ("row".to_string(), to_raw_value(&row)), + ]); + + + let _ = pg.handle(&db, database_info).await; + } + Some((o_id, old_row, row, transaction_type)) => { + let relation = match relations.get_relation(o_id) { + Ok(relation) => relation, + Err(err) => { + tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string()); + continue; + } + }; + + if let Err(err) = old_row { + tracing::error!( + transaction_type = ?transaction_type, + schema = %relation.namespace, + table = %relation.name, + error = %err, + "Failed to decode OLD row for {} transaction on {}.{}", + transaction_type, + relation.namespace, + relation.name, + ); } - }; - let database_info = HashMap::from([ - ("schema_name".to_string(), to_raw_value(&relation.namespace)), - ("table_name".to_string(), to_raw_value(&relation.name)), - ("transaction_type".to_string(), to_raw_value(&transaction_type)), - ("row".to_string(), to_raw_value(&body)), - ]); - let extra = Some(HashMap::from([( - "wm_trigger".to_string(), - to_raw_value(&serde_json::json!({"kind": "postgres", })), - )])); + if let Err(err) = row { + tracing::error!( + transaction_type = ?transaction_type, + schema = %relation.namespace, + table = %relation.name, + error = %err, + "Failed to decode NEW row for {} transaction on {}.{}", + transaction_type, + relation.namespace, + relation.name, + ); + } - let _ = pg.handle(&db, Some(database_info), extra).await; + } + _ => {} } } @@ -873,22 +871,17 @@ impl CaptureConfigForPostgresTrigger { } } - async fn handle( - &self, - db: &DB, - args: Option>>, - extra: Option>>, - ) -> () { - let args = PushArgsOwned { args: args.unwrap_or_default(), extra: None }; - let extra = extra.as_ref().map(to_raw_value); + async fn handle(&self, db: &DB, payload: HashMap>) -> () { + let main_args = PostgresTrigger::build_job_args_v2(false, payload.clone(), HashMap::new()); + let preprocessor_args = PostgresTrigger::build_job_args_v2(true, payload, HashMap::new()); if let Err(err) = insert_capture_payload( db, &self.workspace_id, &self.path, self.is_flow, &TriggerKind::Postgres, - args, - extra, + main_args, + preprocessor_args, &self.owner, ) .await diff --git a/backend/windmill-api/src/raw_apps.rs b/backend/windmill-api/src/raw_apps.rs index bb2763b73d..3174102932 100644 --- a/backend/windmill-api/src/raw_apps.rs +++ b/backend/windmill-api/src/raw_apps.rs @@ -22,13 +22,14 @@ use serde::{Deserialize, Serialize}; use sql_builder::{bind::Bind, SqlBuilder}; use sqlx::FromRow; use std::str; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::{ apps::ListAppQuery, db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, + worker::CLOUD_HOSTED, }; pub fn workspaced_service() -> Router { @@ -149,9 +150,29 @@ async fn create_app( authed: ApiAuthed, Extension(user_db): Extension, Extension(webhook): Extension, + Extension(db): Extension, Path(w_id): Path, Json(app): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_apps = sqlx::query_scalar!( + "SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_apps.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if app.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + } let mut tx = user_db.begin(&authed).await?; if &app.path == "" { return Err(Error::BadRequest("App path cannot be empty".to_string())); diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index 2a3331ef9a..7999356344 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -26,13 +26,14 @@ 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_ee::{audit_log, AuditAuthor}; +use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, variables, + worker::CLOUD_HOSTED, }; pub fn workspaced_service() -> Router { @@ -570,7 +571,7 @@ pub async fn transform_json_value<'c>( }; let variables = variables::get_reserved_variables( - db, + &db.into(), workspace, token, &job.email, @@ -583,7 +584,6 @@ pub async fn transform_json_value<'c>( job.schedule_path.clone(), job.flow_step_id.clone(), job.root_job.map(|x| x.to_string()), - None, Some(job.scheduled_for.clone()), ) .await; @@ -599,11 +599,10 @@ pub async fn transform_json_value<'c>( } Value::Object(mut m) => { for (a, b) in m.clone().into_iter() { - m.insert( - a.clone(), + let v = transform_json_value(authed, user_db.clone(), db, workspace, b, job_id, token) - .await?, - ); + .await?; + m.insert(a.clone(), v); } Ok(Value::Object(m)) } @@ -658,6 +657,20 @@ async fn create_resource( Query(q): Query, Json(resource): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_resources = sqlx::query_scalar!( + "SELECT COUNT(*) FROM resource WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_resources.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of resources (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } let authed = maybe_refresh_folders(&resource.path, &w_id, authed, &db).await; let mut tx = user_db.begin(&authed).await?; @@ -1208,9 +1221,17 @@ async fn update_resource_type( Ok(format!("resource_type {} updated", name)) } -#[cfg(any(feature = "postgres_trigger", feature = "mqtt_trigger", all(feature = "sqs_trigger", feature = "enterprise")))] +#[cfg(any( + feature = "http_trigger", + feature = "postgres_trigger", + feature = "mqtt_trigger", + all( + feature = "enterprise", + any(feature = "sqs_trigger", feature = "gcp_trigger") + ) +))] pub async fn try_get_resource_from_db_as( - authed: ApiAuthed, + authed: &ApiAuthed, user_db: Option, db: &DB, resource_path: &str, diff --git a/backend/windmill-api/src/saml_ee.rs b/backend/windmill-api/src/saml_oss.rs similarity index 75% rename from backend/windmill-api/src/saml_ee.rs rename to backend/windmill-api/src/saml_oss.rs index b3f1d4653c..0f4f4aa7f6 100644 --- a/backend/windmill-api/src/saml_ee.rs +++ b/backend/windmill-api/src/saml_oss.rs @@ -7,18 +7,27 @@ */ #![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 { 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() diff --git a/backend/windmill-api/src/schedule.rs b/backend/windmill-api/src/schedule.rs index a397eadf05..3cb13798d9 100644 --- a/backend/windmill-api/src/schedule.rs +++ b/backend/windmill-api/src/schedule.rs @@ -22,13 +22,14 @@ use serde::{Deserialize, Serialize}; use sql_builder::{prelude::Bind, SqlBuilder}; use sqlx::{Postgres, Transaction}; use std::str::FromStr; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, schedule::Schedule, utils::{not_found_if_none, paginate, Pagination, ScheduleType, StripPath}, + worker::to_raw_value, }; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; use windmill_queue::schedule::push_scheduled_job; @@ -57,6 +58,7 @@ pub struct NewSchedule { pub schedule: String, pub timezone: String, pub summary: Option, + pub description: Option, pub no_flow_overlap: Option, pub script_path: String, pub is_flow: bool, @@ -120,6 +122,12 @@ async fn check_path_conflict<'c>( return Ok(()); } +fn to_json_raw_opt( + value: Option<&serde_json::Value>, +) -> Option>> { + value.map(|v| sqlx::types::Json(to_raw_value(&v))) +} + async fn create_schedule( authed: ApiAuthed, Extension(db): Extension, @@ -159,41 +167,90 @@ async fn create_schedule( check_path_conflict(&mut tx, &w_id, &ns.path).await?; check_flow_conflict(&mut tx, &w_id, &ns.path, ns.is_flow, &ns.script_path).await?; - let schedule = sqlx::query_as::<_, Schedule>( - "INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, \ - is_flow, args, enabled, email, on_failure, on_failure_times, on_failure_exact, \ - on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, \ - on_success, on_success_extra_args, \ - ws_error_handler_muted, retry, summary, no_flow_overlap, tag, paused_until, cron_version \ - ) VALUES ( \ - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26 \ - ) RETURNING *") - .bind(&w_id) - .bind(&ns.path) - .bind(&ns.schedule) - .bind(&ns.timezone) - .bind(&authed.username) - .bind(&ns.script_path) - .bind(&ns.is_flow) - .bind(&ns.args) - .bind(&ns.enabled.unwrap_or(false)) - .bind(&authed.email) - .bind(&ns.on_failure) - .bind(&ns.on_failure_times) - .bind(&ns.on_failure_exact) - .bind(&ns.on_failure_extra_args) - .bind(&ns.on_recovery) - .bind(&ns.on_recovery_times) - .bind(&ns.on_recovery_extra_args) - .bind(&ns.on_success) - .bind(&ns.on_success_extra_args) - .bind(&ns.ws_error_handler_muted.unwrap_or(false)) - .bind(&ns.retry) - .bind(&ns.summary) - .bind(&ns.no_flow_overlap.unwrap_or(false)) - .bind(&ns.tag) - .bind(&ns.paused_until) - .bind(&ns.cron_version.unwrap_or("v2".to_string())) + let schedule = sqlx::query_as!( + Schedule, + r#" + INSERT INTO schedule ( + workspace_id, path, schedule, timezone, edited_by, script_path, + is_flow, args, enabled, email, + on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, + on_recovery, on_recovery_times, on_recovery_extra_args, + on_success, on_success_extra_args, + ws_error_handler_muted, retry, summary, no_flow_overlap, + tag, paused_until, cron_version, description + ) VALUES ( + $1, $2, $3, $4, $5, $6, + $7, $8, $9, $10, + $11, $12, $13, $14, + $15, $16, $17, + $18, $19, + $20, $21, $22, $23, + $24, $25, $26, $27 + ) + RETURNING + workspace_id, + path, + edited_by, + edited_at, + schedule, + timezone, + enabled, + script_path, + is_flow, + args AS "args: _", + extra_perms, + email, + error, + on_failure, + on_failure_times, + on_failure_exact, + on_failure_extra_args AS "on_failure_extra_args: _", + on_recovery, + on_recovery_times, + on_recovery_extra_args AS "on_recovery_extra_args: _", + on_success, + on_success_extra_args AS "on_success_extra_args: _", + ws_error_handler_muted, + retry, + no_flow_overlap, + summary, + description, + tag, + paused_until, + cron_version + "#, + w_id, + ns.path, + ns.schedule, + ns.timezone, + authed.username, + ns.script_path, + ns.is_flow, + to_json_raw_opt(ns.args.as_ref()) + as Option>>, + ns.enabled.unwrap_or(false), + authed.email, + ns.on_failure, + ns.on_failure_times, + ns.on_failure_exact, + to_json_raw_opt(ns.on_failure_extra_args.as_ref()) + as Option>>, + ns.on_recovery, + ns.on_recovery_times, + to_json_raw_opt(ns.on_recovery_extra_args.as_ref()) + as Option>>, + ns.on_success, + to_json_raw_opt(ns.on_success_extra_args.as_ref()) + as Option>>, + ns.ws_error_handler_muted.unwrap_or(false), + ns.retry, + ns.summary, + ns.no_flow_overlap.unwrap_or(false), + ns.tag, + ns.paused_until, + ns.cron_version.clone().unwrap_or_else(|| "v2".to_string()), + ns.description + ) .fetch_one(&mut *tx) .await .map_err(|e| Error::internal_err(format!("inserting schedule in {w_id}: {e:#}")))?; @@ -252,34 +309,92 @@ async fn edit_schedule( ScheduleType::from_str(&es.schedule, es.cron_version.as_deref(), true)?; clear_schedule(&mut tx, path, &w_id).await?; - let schedule = sqlx::query_as::<_, Schedule>( - "UPDATE schedule SET schedule = $1, timezone = $2, args = $3, on_failure = $4, on_failure_times = $5, \ - on_failure_exact = $6, on_failure_extra_args = $7, on_recovery = $8, on_recovery_times = $9, \ - on_recovery_extra_args = $10, on_success = $11, on_success_extra_args = $12, \ - ws_error_handler_muted = $13, retry = $14, summary = $15, \ - no_flow_overlap = $16, tag = $17, paused_until = $18, cron_version = COALESCE($21, cron_version) \ - WHERE path = $19 AND workspace_id = $20 RETURNING *") - .bind(&es.schedule) - .bind(&es.timezone) - .bind(&es.args) - .bind(&es.on_failure) - .bind(&es.on_failure_times) - .bind(&es.on_failure_exact) - .bind(&es.on_failure_extra_args) - .bind(&es.on_recovery) - .bind(&es.on_recovery_times) - .bind(&es.on_recovery_extra_args) - .bind(&es.on_success) - .bind(&es.on_success_extra_args) - .bind(&es.ws_error_handler_muted.unwrap_or(false)) - .bind(&es.retry) - .bind(&es.summary) - .bind(&es.no_flow_overlap.unwrap_or(false)) - .bind(&es.tag) - .bind(&es.paused_until) - .bind(&path) - .bind(&w_id) - .bind(&es.cron_version) + let schedule = sqlx::query_as!( + Schedule, + r#" + UPDATE schedule SET + schedule = $1, + timezone = $2, + args = $3, + on_failure = $4, + on_failure_times = $5, + on_failure_exact = $6, + on_failure_extra_args = $7, + on_recovery = $8, + on_recovery_times = $9, + on_recovery_extra_args = $10, + on_success = $11, + on_success_extra_args = $12, + ws_error_handler_muted = $13, + retry = $14, + summary = $15, + no_flow_overlap = $16, + tag = $17, + paused_until = $18, + path = $19, + workspace_id = $20, + cron_version = COALESCE($21, cron_version), + description = $22 + WHERE path = $19 AND workspace_id = $20 + RETURNING + workspace_id, + path, + edited_by, + edited_at, + schedule, + timezone, + enabled, + script_path, + is_flow, + args AS "args: _", + extra_perms, + email, + error, + on_failure, + on_failure_times, + on_failure_exact, + on_failure_extra_args AS "on_failure_extra_args: _", + on_recovery, + on_recovery_times, + on_recovery_extra_args AS "on_recovery_extra_args: _", + on_success, + on_success_extra_args AS "on_success_extra_args: _", + ws_error_handler_muted, + retry, + no_flow_overlap, + summary, + description, + tag, + paused_until, + cron_version + "#, + es.schedule, + es.timezone, + to_json_raw_opt(es.args.as_ref()) + as Option>>, + es.on_failure, + es.on_failure_times, + es.on_failure_exact, + to_json_raw_opt(es.on_failure_extra_args.as_ref()) + as Option>>, + es.on_recovery, + es.on_recovery_times, + to_json_raw_opt(es.on_recovery_extra_args.as_ref()) + as Option>>, + es.on_success, + to_json_raw_opt(es.on_success_extra_args.as_ref()) + as Option>>, + es.ws_error_handler_muted.unwrap_or(false), + es.retry, + es.summary, + es.no_flow_overlap.unwrap_or(false), + es.tag, + es.paused_until, + path, + w_id, + es.cron_version, + es.description + ) .fetch_one(&mut *tx) .await .map_err(|e| Error::internal_err(format!("updating schedule in {w_id}: {e:#}")))?; @@ -352,7 +467,19 @@ async fn list_schedule( let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lsq.per_page, page: lsq.page }); let mut sqlb = SqlBuilder::select_from("schedule") - .field("workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, summary, extra_perms") + .fields(&[ + "workspace_id", + "path", + "edited_by", + "edited_at", + "schedule", + "timezone", + "enabled", + "script_path", + "is_flow", + "summary", + "extra_perms", + ]) .order_by("edited_at", true) .and_where("workspace_id = ?".bind(&w_id)) .offset(offset) @@ -488,12 +615,50 @@ pub async fn set_enabled( ) -> Result { let mut tx = user_db.begin(&authed).await?; let path = path.to_path(); - let schedule_o = sqlx::query_as::<_, Schedule>( - "UPDATE schedule SET enabled = $1, email = $2 WHERE path = $3 AND workspace_id = $4 RETURNING *") - .bind(&payload.enabled) - .bind(&authed.email) - .bind(&path) - .bind(&w_id) + let schedule_o = sqlx::query_as!( + Schedule, + r#" + UPDATE schedule SET + enabled = $1, + email = $2 + WHERE path = $3 AND workspace_id = $4 + RETURNING + workspace_id, + path, + edited_by, + edited_at, + schedule, + timezone, + enabled, + script_path, + is_flow, + args AS "args: _", + extra_perms, + email, + error, + on_failure, + on_failure_times, + on_failure_exact, + on_failure_extra_args AS "on_failure_extra_args: _", + on_recovery, + on_recovery_times, + on_recovery_extra_args AS "on_recovery_extra_args: _", + on_success, + on_success_extra_args AS "on_success_extra_args: _", + ws_error_handler_muted, + retry, + no_flow_overlap, + summary, + description, + tag, + paused_until, + cron_version + "#, + payload.enabled, + authed.email, + path, + w_id + ) .fetch_optional(&mut *tx) .await?; @@ -819,6 +984,7 @@ pub struct EditSchedule { pub timezone: String, pub args: Option, pub summary: Option, + pub description: Option, pub on_failure: Option, pub on_failure_times: Option, pub on_failure_exact: Option, diff --git a/backend/windmill-api/src/scim_ee.rs b/backend/windmill-api/src/scim_oss.rs similarity index 72% rename from backend/windmill-api/src/scim_ee.rs rename to backend/windmill-api/src/scim_oss.rs index f11097f874..5210411466 100644 --- a/backend/windmill-api/src/scim_ee.rs +++ b/backend/windmill-api/src/scim_oss.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::scim_ee::*; + /* * Author: Ruben Fiszel * Copyright: Windmill Labs, Inc 2023 @@ -6,17 +10,22 @@ * 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(_request: Request, _next: Next) -> Response { //Not implemented in open-source version todo!() diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index d1c6e43902..6b74c6381b 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -38,16 +38,18 @@ use std::{ hash::{Hash, Hasher}, sync::Arc, }; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; +use windmill_worker::process_relative_imports; -use windmill_common::error::to_anyhow; +use windmill_common::{error::to_anyhow, worker::CLOUD_HOSTED}; use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, jobs::JobPayload, schedule::Schedule, + schema::should_validate_schema, scripts::{ to_i64, HubScript, ListScriptQuery, ListableScript, NewScript, Schema, Script, ScriptHash, ScriptHistory, ScriptHistoryUpdate, ScriptKind, ScriptLang, ScriptWithStarred, @@ -155,6 +157,10 @@ pub fn workspaced_service() -> Router { ) .route("/history/p/*path", get(get_script_history)) .route("/get_latest_version/*path", get(get_latest_version)) + .route( + "/list_paths_from_workspace_runnable/*path", + get(list_paths_from_workspace_runnable), + ) .route( "/history_update/h/:hash/p/*path", post(update_script_history), @@ -200,7 +206,6 @@ async fn list_scripts( Query(lq): Query, ) -> JsonResult> { let (per_page, offset) = paginate(pagination); - let mut sqlb = SqlBuilder::select_from("script as o") .fields(&[ "hash", @@ -217,7 +222,8 @@ async fn list_scripts( "draft_only", "ws_error_handler_muted", "no_main_func", - "codebase IS NOT NULL as use_codebase" + "codebase IS NOT NULL as use_codebase", + "kind" ]) .left() .join("favorite") @@ -259,9 +265,12 @@ async fn list_scripts( if lq.show_archived.unwrap_or(false) { sqlb.and_where_eq( - "o.created_at", - "(select max(created_at) from script where o.path = path - AND workspace_id = ?)" + "o.ctid", + "(SELECT ctid FROM script + WHERE path = o.path + AND workspace_id = ? + ORDER BY created_at DESC + LIMIT 1)" .bind(&w_id), ); sqlb.and_where_eq("archived", true); @@ -289,7 +298,9 @@ async fn list_scripts( if let Some(it) = &lq.is_template { sqlb.and_where_eq("is_template", it); } - if let Some(lowercased_kinds) = lowercased_kinds { + if authed.is_operator { + sqlb.and_where_eq("kind", quote("script")); + } else if let Some(lowercased_kinds) = lowercased_kinds { let safe_kinds = lowercased_kinds .into_iter() .map(sql_builder::quote) @@ -309,6 +320,16 @@ async fn list_scripts( .fields(&["dm.deployment_msg"]); } + if let Some(languages) = lq.languages { + sqlb.and_where_in( + "language", + &languages + .iter() + .map(|language| quote(language.as_str())) + .collect_vec(), + ); + } + let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?; let mut tx = user_db.begin(&authed).await?; let rows = sqlx::query_as::<_, ListableScript>(&sql) @@ -398,10 +419,7 @@ async fn create_snapshot_script( uploaded = true; #[cfg(all(feature = "enterprise", feature = "parquet"))] - let object_store = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone(); + let object_store = windmill_common::s3_helpers::get_object_store().await; #[cfg(not(all(feature = "enterprise", feature = "parquet")))] let object_store: Option<()> = None; @@ -413,10 +431,10 @@ async fn create_snapshot_script( std::fs::create_dir_all( windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR.clone(), )?; - windmill_common::worker::write_file( + windmill_common::worker::write_file_bytes( &windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR, &hash, - &String::from_utf8_lossy(&data), + &data, )?; } else { #[cfg(not(all(feature = "enterprise", feature = "parquet")))] @@ -455,6 +473,24 @@ async fn create_snapshot_script( return Ok((StatusCode::CREATED, format!("{}", script_hash.unwrap()))); } +async fn list_paths_from_workspace_runnable( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult> { + let mut tx = user_db.begin(&authed).await?; + let runnables = sqlx::query_scalar!( + r#"SELECT importer_path FROM dependency_map + WHERE workspace_id = $1 AND imported_path = $2"#, + w_id, + path.to_path(), + ) + .fetch_all(&mut *tx) + .await?; + tx.commit().await?; + Ok(Json(runnables)) +} + async fn create_script( authed: ApiAuthed, Extension(user_db): Extension, @@ -484,6 +520,29 @@ async fn create_script_internal<'c>( .to_string(), )); } + if *CLOUD_HOSTED { + let nb_scripts = + sqlx::query_scalar!("SELECT COUNT(*) FROM script WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_scripts.unwrap_or(0) >= 5000 { + return Err(Error::BadRequest( + "You have reached the maximum number of scripts (5000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + + if ns.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + if ns.description.len() > 3000 { + return Err(Error::BadRequest( + "Description must be less than 3000 characters on cloud".to_string(), + )); + } + } let script_path = ns.path.clone(); let hash = ScriptHash(hash_script(&ns)); let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await; @@ -610,20 +669,29 @@ async fn create_script_internal<'c>( .unwrap_or(json!({})); let lock = if ns.codebase.is_some() { Some(String::new()) - } else if !(ns.language == ScriptLang::Python3 - || ns.language == ScriptLang::Go - || ns.language == ScriptLang::Bun - || ns.language == ScriptLang::Bunnative - || ns.language == ScriptLang::Deno - || ns.language == ScriptLang::Rust - || ns.language == ScriptLang::Ansible - || ns.language == ScriptLang::CSharp - || ns.language == ScriptLang::Php) - { + } else if !( + ns.language == ScriptLang::Python3 + || ns.language == ScriptLang::Go + || ns.language == ScriptLang::Bun + || ns.language == ScriptLang::Bunnative + || ns.language == ScriptLang::Deno + || ns.language == ScriptLang::Rust + || ns.language == ScriptLang::Ansible + || ns.language == ScriptLang::CSharp + || ns.language == ScriptLang::Nu + || ns.language == ScriptLang::Php + || ns.language == ScriptLang::Java + // for related places search: ADD_NEW_LANG + ) { Some(String::new()) } else { - ns.lock - .and_then(|e| if e.is_empty() { None } else { Some(e) }) + ns.lock.as_ref().and_then(|e| { + if e.is_empty() { + None + } else { + Some(e.to_string()) + } + }) }; let needs_lock_gen = lock.is_none() && codebase.is_none(); @@ -645,16 +713,42 @@ async fn create_script_internal<'c>( ns.language.clone() }; - let (no_main_func, has_preprocessor) = match lang { - ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { - let args = windmill_parser_ts::parse_deno_signature(&ns.content, true, true, None)?; - (args.no_main_func, args.has_preprocessor) + let validate_schema = should_validate_schema(&ns.content, &ns.language); + + let (no_main_func, has_preprocessor) = if matches!(ns.kind, Some(ScriptKind::Preprocessor)) { + (ns.no_main_func, ns.has_preprocessor) + } else { + match lang { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature(&ns.content, true, true, None); + match args { + Ok(args) => (args.no_main_func, args.has_preprocessor), + Err(e) => { + tracing::warn!( + "Error parsing deno signature when deploying script {}: {:?}", + ns.path, + e + ); + (None, None) + } + } + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature(&ns.content, None, true); + match args { + Ok(args) => (args.no_main_func, args.has_preprocessor), + Err(e) => { + tracing::warn!( + "Error parsing python signature when deploying script {}: {:?}", + ns.path, + e + ); + (None, None) + } + } + } + _ => (ns.no_main_func, ns.has_preprocessor), } - ScriptLang::Python3 => { - let args = windmill_parser_py::parse_python_signature(&ns.content, None, true)?; - (args.no_main_func, args.has_preprocessor) - } - _ => (ns.no_main_func, ns.has_preprocessor), }; sqlx::query!( @@ -662,8 +756,8 @@ async fn create_script_internal<'c>( content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ - delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)", + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation) \ + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33)", &w_id, &hash.0, ns.path, @@ -699,7 +793,8 @@ async fn create_script_internal<'c>( Some(&authed.email) } else { None - } + }, + validate_schema, ) .execute(&mut *tx) .await?; @@ -864,6 +959,40 @@ async fn create_script_internal<'c>( .await?; Ok((hash, new_tx)) } else { + let db2 = db.clone(); + let w_id2 = w_id.clone(); + let authed2 = authed.clone(); + let permissioned_as2 = permissioned_as.clone(); + let script_path2 = script_path.clone(); + let parent_path = p_path_opt.clone(); + let lock = ns.lock.clone(); + let deployment_message = ns.deployment_message.clone(); + let content = ns.content.clone(); + let language = ns.language.clone(); + tokio::spawn(async move { + // wait for 10 seconds to make sure the script is deployed and that the CLI sync that pushed it (f one) is complete + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + if let Err(e) = process_relative_imports( + &db2, + None, + None, + &w_id2, + &script_path2, + parent_path, + deployment_message, + &content, + &Some(language), + &authed2.email, + &authed2.username, + &permissioned_as2, + lock, + ) + .await + { + tracing::error!(%e, "error processing relative imports"); + } + }); + handle_deployment_metadata( &authed.email, &authed.username, @@ -919,7 +1048,7 @@ async fn get_script_by_path( AND favorite.usr = $3 WHERE s.path = $1 AND s.workspace_id = $2 - AND s.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)", + ORDER BY s.created_at DESC LIMIT 1", ) .bind(path) .bind(w_id) @@ -928,9 +1057,7 @@ async fn get_script_by_path( .await? } else { sqlx::query_as::<_, ScriptWithStarred>( - "SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 \ - AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \ - workspace_id = $2)", + "SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", ) .bind(path) .bind(w_id) @@ -970,9 +1097,8 @@ async fn get_script_by_path_w_draft( let script_o = sqlx::query_as::<_, ScriptWDraft>( "SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, has_preprocessor, on_behalf_of_email FROM script LEFT JOIN draft ON script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script' - WHERE script.path = $1 AND script.workspace_id = $2 \ - AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \ - workspace_id = $2)", + WHERE script.path = $1 AND script.workspace_id = $2 + ORDER BY script.created_at DESC LIMIT 1", ) .bind(path) .bind(w_id) @@ -994,7 +1120,7 @@ async fn get_script_history( "SELECT s.hash as hash, dm.deployment_msg as deployment_msg FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash WHERE s.workspace_id = $1 AND s.path = $2 - ORDER by created_at DESC", + ORDER by s.created_at DESC", w_id, path.to_path(), ) @@ -1022,7 +1148,7 @@ async fn get_latest_version( "SELECT s.hash as hash, dm.deployment_msg as deployment_msg FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash WHERE s.workspace_id = $1 AND s.path = $2 - ORDER by created_at DESC", + ORDER by s.created_at DESC LIMIT 1", w_id, path.to_path(), ) @@ -1118,7 +1244,15 @@ async fn toggle_workspace_error_handler( match error_handler_maybe { Some(_) => { sqlx::query_scalar!( - "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)", + "UPDATE script + SET ws_error_handler_muted = $3 + WHERE ctid = ( + SELECT ctid FROM script + WHERE path = $1 AND workspace_id = $2 + ORDER BY created_at DESC + LIMIT 1 + ) +", path.to_path(), w_id, req.muted, @@ -1139,6 +1273,7 @@ async fn toggle_workspace_error_handler( async fn get_tokened_raw_script_by_path( Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, token, path)): Path<(String, String, StripPath)>, Extension(cache): Extension>, ) -> Result { @@ -1146,7 +1281,13 @@ async fn get_tokened_raw_script_by_path( .get_authed(Some(w_id.clone()), &token) .await .ok_or_else(|| Error::NotAuthorized("Invalid token".to_string()))?; - return raw_script_by_path(authed, Extension(user_db), Path((w_id, path))).await; + return raw_script_by_path( + authed, + Extension(user_db), + Extension(db), + Path((w_id, path)), + ) + .await; } async fn get_empty_ts_script_by_path() -> String { @@ -1156,22 +1297,30 @@ async fn get_empty_ts_script_by_path() -> String { async fn raw_script_by_path( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> Result { - raw_script_by_path_internal(path, user_db, authed, w_id, false).await + raw_script_by_path_internal(path, user_db, db, authed, w_id, false).await } async fn raw_script_by_path_unpinned( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> Result { - raw_script_by_path_internal(path, user_db, authed, w_id, true).await + raw_script_by_path_internal(path, user_db, db, authed, w_id, true).await +} + +lazy_static::lazy_static! { + static ref DEBUG_RAW_SCRIPT_ENDPOINTS: bool = + std::env::var("DEBUG_RAW_SCRIPT_ENDPOINTS").is_ok(); } async fn raw_script_by_path_internal( path: StripPath, user_db: UserDB, + db: DB, authed: ApiAuthed, w_id: String, unpin: bool, @@ -1197,10 +1346,7 @@ async fn raw_script_by_path_internal( let mut tx = user_db.begin(&authed).await?; let content_o = sqlx::query_scalar!( - "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 \ - AND - created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND \ - workspace_id = $2)", + "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1", path, w_id ) @@ -1208,6 +1354,44 @@ async fn raw_script_by_path_internal( .await?; tx.commit().await?; + if content_o.is_none() { + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1)", + path, + w_id + ) + .fetch_one(&db) + .await? + .unwrap_or(false); + + if exists { + return Err(Error::NotFound(format!( + "Script {path} exists but {} does not have permissions to access it", + authed.username + ))); + } else { + if *DEBUG_RAW_SCRIPT_ENDPOINTS { + let other_script_o = sqlx::query_scalar!( + "SELECT path FROM script WHERE workspace_id = $1 AND archived = false", + w_id + ) + .fetch_all(&db) + .await?; + let other_script_archived = sqlx::query_scalar!( + "SELECT distinct(path) FROM script WHERE workspace_id = $1 AND archived = true", + w_id + ) + .fetch_all(&db) + .await?; + tracing::warn!( + "Script {path} does not exist in workspace {w_id} but these paths do, non-archived: {:?} | archived: {:?}", + other_script_o.join(", "), + other_script_archived.join(", ") + ) + } + } + } + let content = not_found_if_none(content_o, "Script", path)?; if unpin { @@ -1224,8 +1408,7 @@ async fn exists_script_by_path( let path = path.to_path(); let exists = sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND - created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))", + "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)", path, w_id ) @@ -1342,9 +1525,7 @@ pub async fn require_is_writer(authed: &ApiAuthed, path: &str, w_id: &str, db: D path, w_id, db, - "SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 \ - AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \ - workspace_id = $2)", + "SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", "script", ) .await; @@ -1481,12 +1662,18 @@ async fn delete_script_by_hash( Ok(Json(script)) } +#[derive(Deserialize)] +struct DeleteScriptQuery { + keep_captures: Option, +} + async fn delete_script_by_path( authed: ApiAuthed, Extension(user_db): Extension, Extension(webhook): Extension, Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, + Query(query): Query, ) -> JsonResult { let path = path.to_path(); @@ -1540,21 +1727,23 @@ async fn delete_script_by_path( .execute(&db) .await?; - sqlx::query!( - "DELETE FROM capture_config WHERE path = $1 AND workspace_id = $2 AND is_flow IS FALSE", - path, - w_id - ) - .execute(&db) - .await?; + if !query.keep_captures.unwrap_or(false) { + sqlx::query!( + "DELETE FROM capture_config WHERE path = $1 AND workspace_id = $2 AND is_flow IS FALSE", + path, + w_id + ) + .execute(&db) + .await?; - sqlx::query!( - "DELETE FROM capture WHERE path = $1 AND workspace_id = $2 AND is_flow IS FALSE", - path, - w_id - ) - .execute(&db) - .await?; + sqlx::query!( + "DELETE FROM capture WHERE path = $1 AND workspace_id = $2 AND is_flow IS FALSE", + path, + w_id + ) + .execute(&db) + .await?; + } audit_log( &mut *tx, diff --git a/backend/windmill-api/src/service_logs.rs b/backend/windmill-api/src/service_logs.rs index b11646fbe0..0b3ca56af7 100644 --- a/backend/windmill-api/src/service_logs.rs +++ b/backend/windmill-api/src/service_logs.rs @@ -98,10 +98,7 @@ 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::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone(); + let s3_client = windmill_common::s3_helpers::get_object_store().await; #[cfg(feature = "parquet")] if let Some(s3_client) = s3_client { let path = format!("{}{}", windmill_common::tracing_init::LOGS_SERVICE, path); diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 37a33ee81a..9dfb687e51 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -10,7 +10,7 @@ use std::time::Duration; use crate::{ db::{ApiAuthed, DB}, - ee::validate_license_key, + ee_oss::validate_license_key, utils::{generate_instance_username_for_all_users, require_super_admin}, HTTP_CLIENT, }; @@ -29,13 +29,13 @@ use crate::utils::require_devops_role; use serde::Deserialize; #[cfg(feature = "enterprise")] -use windmill_common::ee::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel}; +use windmill_common::ee_oss::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel}; use windmill_common::{ - email_ee::send_email, + email_oss::send_email, error::{self, JsonResult, Result}, global_settings::{ - AUTOMATE_USERNAME_CREATION_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, - HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING, + AUTOMATE_USERNAME_CREATION_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, EMAIL_DOMAIN_SETTING, + ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING, }, server::Smtp, }; @@ -120,12 +120,15 @@ use windmill_common::s3_helpers::build_object_store_from_settings; #[cfg(feature = "parquet")] pub async fn test_s3_bucket( _authed: ApiAuthed, + Extension(db): Extension, Json(test_s3_bucket): Json, ) -> error::Result { use bytes::Bytes; use futures::StreamExt; - let client = build_object_store_from_settings(test_s3_bucket).await?; + let client = build_object_store_from_settings(test_s3_bucket, Some(&db)) + .await? + .store; let mut list = client.list(Some(&object_store::path::Path::from("".to_string()))); let first_file = list.next().await; @@ -239,6 +242,13 @@ pub async fn set_global_setting_internal( })?; } } + CRITICAL_ALERT_MUTE_UI_SETTING => { + if value.clone().as_bool().unwrap_or(false) { + sqlx::query!("UPDATE alerts SET acknowledged = true") + .execute(db) + .await?; + } + } _ => {} } @@ -316,10 +326,10 @@ async fn list_global_settings() -> JsonResult { pub async fn send_stats(Extension(db): Extension, authed: ApiAuthed) -> Result { require_super_admin(&db, &authed.email).await?; - windmill_common::stats_ee::send_stats( + windmill_common::stats_oss::send_stats( &HTTP_CLIENT, &db, - windmill_common::stats_ee::SendStatsReason::Manual, + windmill_common::stats_oss::SendStatsReason::Manual, ) .await?; @@ -380,11 +390,11 @@ pub async fn renew_license_key( authed: ApiAuthed, ) -> Result { require_super_admin(&db, &authed.email).await?; - let result = windmill_common::ee::renew_license_key( + let result = windmill_common::ee_oss::renew_license_key( &HTTP_CLIENT, &db, license_key, - windmill_common::ee::RenewReason::Manual, + windmill_common::ee_oss::RenewReason::Manual, ) .await; @@ -414,7 +424,7 @@ pub async fn create_customer_portal_session( Query(LicenseQuery { license_key }): Query, ) -> Result { let url = - windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, license_key).await?; + windmill_common::ee_oss::create_customer_portal_session(&HTTP_CLIENT, license_key).await?; return Ok(url); } diff --git a/backend/windmill-api/src/slack_approvals.rs b/backend/windmill-api/src/slack_approvals.rs index 652c7b1721..019f83ecb2 100644 --- a/backend/windmill-api/src/slack_approvals.rs +++ b/backend/windmill-api/src/slack_approvals.rs @@ -3,28 +3,21 @@ use axum::{ Extension, }; use hyper::StatusCode; -use serde::{Deserialize, Serialize}; -use serde_json::value::{RawValue, Value}; - -use sqlx::types::Uuid; -use std::{collections::HashMap, str::FromStr}; - -use regex::Regex; use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sqlx::types::Uuid; +use std::collections::HashMap; +use windmill_common::error::Error; +use windmill_common::variables::get_secret_value_as_admin; +use crate::approvals::{ + extract_w_id_from_resume_url, handle_resume_action, ApprovalFormDetails, FieldType, + MessageFormat, QueryDefaultArgsJson, QueryDynamicEnumJson, QueryFlowStepId, QueryMessage, + ResumeFormField, ResumeSchema, +}; use crate::db::{ApiAuthed, DB}; -use crate::jobs::{ - cancel_suspended_job, get_resume_urls_internal, resume_suspended_job, QueryApprover, - QueryOrBody, ResumeUrls, -}; - -use windmill_common::{ - cache, - error::{self, Error}, - jobs::JobKind, - scripts::ScriptHash, - variables::{build_crypt, decrypt}, -}; +use crate::jobs::{QueryApprover, ResumeUrls}; #[derive(Deserialize, Debug)] pub struct SlackFormData { @@ -91,53 +84,6 @@ struct SelectedOption { value: String, } -#[derive(Debug, Deserialize, Serialize)] -struct ResumeSchema { - schema: Schema, -} - -#[derive(Debug, Deserialize)] -struct ResumeFormRow { - resume_form: Option, - hide_cancel: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Schema { - order: Vec, - required: Vec, - properties: HashMap, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] -enum FieldType { - Boolean, - String, - Number, - Integer, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Deserialize, Serialize)] -struct ResumeFormField { - r#type: FieldType, - format: Option, - default: Option, - description: Option, - title: Option, - r#enum: Option>, - #[serde(rename = "enumLabels")] - enum_labels: Option>, - nullable: Option, -} - -#[derive(Deserialize)] -pub struct QueryMessage { - message: Option, -} - #[derive(Deserialize)] pub struct QueryResourcePath { slack_resource_path: String, @@ -148,21 +94,6 @@ pub struct QueryChannelId { channel_id: String, } -#[derive(Deserialize)] -pub struct QueryFlowStepId { - flow_step_id: String, -} - -#[derive(Deserialize, Debug)] -pub struct QueryDefaultArgsJson { - default_args_json: Option, -} - -#[derive(Deserialize, Debug)] -pub struct QueryDynamicEnumJson { - dynamic_enums_json: Option, -} - #[derive(Deserialize, Debug)] struct ModalActionValue { w_id: String, @@ -362,73 +293,21 @@ async fn handle_submission( return Ok(()); } - // Use regex to extract information from private_metadata - let re = Regex::new(r"/api/w/(?P[^/]+)/jobs_u/(?Presume|cancel)/(?P[^/]+)/(?P[^/]+)/(?P[a-fA-F0-9]+)(?:\?approver=(?P[^&]+))?").unwrap(); - let captures = re.captures(resume_url.as_str()).ok_or_else(|| { - tracing::error!("Resume URL does not match the pattern."); - Error::BadRequest("Invalid URL format.".to_string()) - })?; + // Use the common handler to process the resume/cancel action + handle_resume_action(authed, db.clone(), &resume_url, state_json, action).await?; - let (w_id, job_id, resume_id, secret, approver) = ( - captures.name("w_id").map_or("", |m| m.as_str()), - captures.name("job_id").map_or("", |m| m.as_str()), - captures.name("resume_id").map_or("", |m| m.as_str()), - captures.name("secret").map_or("", |m| m.as_str()), - captures.name("approver").map(|m| m.as_str().to_string()), - ); - - let approver = QueryApprover { approver: approver }; - - // Convert job_id and resume_id to appropriate types - let job_uuid = Uuid::from_str(job_id) - .map_err(|_| Error::BadRequest("Invalid job ID format.".to_string()))?; - - let resume_id_parsed = resume_id - .parse::() - .map_err(|_| Error::BadRequest("Invalid resume ID format.".to_string()))?; - - // Call the appropriate function based on the action - let res = if action == "resume" { - resume_suspended_job( - authed, - Extension(db.clone()), - Path(( - w_id.to_string(), - job_uuid, - resume_id_parsed, - secret.to_string(), - )), - Query(approver), - QueryOrBody(Some(state_json)), - ) - .await - } else { - cancel_suspended_job( - authed, - Extension(db.clone()), - Path(( - w_id.to_string(), - job_uuid, - resume_id_parsed, - secret.to_string(), - )), - Query(approver), - QueryOrBody(Some(state_json)), - ) - .await - }; - tracing::debug!("Resume job action result: {:#?}", res); - let slack_token = get_slack_token(&db, &resource_path, &w_id).await?; + let w_id = extract_w_id_from_resume_url(&resume_url)?; + let slack_token = get_slack_token(&db, &resource_path, w_id).await?; update_original_slack_message(action, slack_token, container).await?; Ok(()) } async fn transform_schemas( text: &str, - properties: Option<&HashMap>, + properties: Option>, urls: &ResumeUrls, - order: Option<&Vec>, - required: Option<&Vec>, + order: Option>, + required: Option>, default_args_json: Option<&serde_json::Value>, dynamic_enums_json: Option<&serde_json::Value>, ) -> Result { @@ -443,16 +322,16 @@ async fn transform_schemas( })]; if let Some(properties) = properties { - for key in order.unwrap() { - if let Some(schema) = properties.get(key) { - let is_required = required.unwrap().contains(key); + for key in order.unwrap_or_default() { + if let Some(schema) = properties.get(&key) { + let is_required = required.as_ref().map_or(false, |r| r.contains(&key)); - let default_value = default_args_json.and_then(|json| json.get(key).cloned()); + let default_value = default_args_json.and_then(|json| json.get(&key).cloned()); let dynamic_enums_value = - dynamic_enums_json.and_then(|json| json.get(key).cloned()); + dynamic_enums_json.and_then(|json| json.get(&key).cloned()); let input_block = create_input_block( - key, + &key, schema, is_required, default_value, @@ -838,26 +717,9 @@ fn process_non_datetime_inputs( } async fn get_slack_token(db: &DB, slack_resource_path: &str, w_id: &str) -> anyhow::Result { - let slack_token = match sqlx::query!( - "SELECT value, is_secret FROM variable WHERE path = $1", - slack_resource_path - ) - .fetch_optional(db) - .await? - { - Some(row) => row, - None => { - return Err(anyhow::anyhow!("No slack token found")); - } - }; - - if slack_token.is_secret { - let mc = build_crypt(&db, w_id).await?; - let bot_token = decrypt(&mc, slack_token.value)?; - Ok(bot_token) - } else { - Ok(slack_token.value) - } + get_secret_value_as_admin(db, w_id, slack_resource_path) + .await + .map_err(|e| anyhow::anyhow!(e.to_string())) } // Sends a Slack message with a button that opens a modal @@ -964,155 +826,59 @@ async fn get_modal_blocks( default_args_json: Option<&serde_json::Value>, dynamic_enums_json: Option<&serde_json::Value>, ) -> Result, Error> { - let res = get_resume_urls_internal( - axum::Extension(db.clone()), - Path((w_id.to_string(), job_id, resume_id)), - Query(QueryApprover { approver: approver.map(|a| a.to_string()) }), + let approval_details = crate::approvals::get_approval_form_details( + db, + w_id, + job_id, + flow_step_id, + resume_id, + approver, + message, + MessageFormat::Slack, ) .await?; - let urls = res.0; + let ApprovalFormDetails { message_str, urls, schema } = approval_details; - tracing::debug!("Job ID: {:?}", job_id); - - let (job_kind, script_hash, raw_flow, parent_job_id, created_at, created_by, script_path, args) = sqlx::query!( - "SELECT - v2_as_queue.job_kind AS \"job_kind!: JobKind\", - v2_as_queue.script_hash AS \"script_hash: ScriptHash\", - v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json>\", - v2_as_completed_job.parent_job AS \"parent_job: Uuid\", - v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\", - v2_as_completed_job.created_by AS \"created_by!\", - v2_as_queue.script_path, - v2_as_queue.args AS \"args: sqlx::types::Json>\" - FROM v2_as_queue - JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id - WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2 - LIMIT 1", - job_id, - &w_id + // Get the card content + let card_content = transform_schemas( + &message_str, + schema + .as_ref() + .and_then(|s| s.resume_form.as_ref()) + .map(|f| { + let inner_schema: ResumeSchema = serde_json::from_value(f.clone()).unwrap(); + inner_schema.schema.properties + }), + &urls, + schema + .as_ref() + .and_then(|s| s.resume_form.as_ref()) + .map(|f| { + let inner_schema: ResumeSchema = serde_json::from_value(f.clone()).unwrap(); + inner_schema.schema.order + }), + schema + .as_ref() + .and_then(|s| s.resume_form.as_ref()) + .map(|f| { + let inner_schema: ResumeSchema = serde_json::from_value(f.clone()).unwrap(); + inner_schema.schema.required + }), + default_args_json, + dynamic_enums_json, ) - .fetch_optional(&db) - .await - .map_err(|e| error::Error::BadRequest(e.to_string()))? - .ok_or_else(|| error::Error::BadRequest("This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string())) - .map(|r| (r.job_kind, r.script_hash, r.raw_flow, r.parent_job, r.created_at, r.created_by, r.script_path, r.args))?; + .await?; - let flow_data = match cache::job::fetch_flow(&db, job_kind, script_hash).await { - Ok(data) => data, - Err(_) => { - if let Some(parent_job_id) = parent_job_id.as_ref() { - cache::job::fetch_preview_flow(&db, parent_job_id, raw_flow).await? - } else { - return Err(error::Error::BadRequest( - "This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string(), - )); - } - } - }; - - let flow_value = &flow_data.flow; - let flow_step_id = flow_step_id.unwrap_or(""); - let module = flow_value.modules.iter().find(|m| m.id == flow_step_id); - - tracing::debug!("Module: {:#?}", module); - - let schema = module.and_then(|module| { - module.suspend.as_ref().map(|suspend| ResumeFormRow { - resume_form: suspend.resume_form.clone(), - hide_cancel: suspend.hide_cancel, - }) - }); - - let args_str = args.map_or("None".to_string(), |a| a.get().to_string()); - let parent_job_id_str = parent_job_id.map_or("None".to_string(), |id| id.to_string()); - let script_path_str = script_path.as_deref().unwrap_or("None"); - - let created_at_formatted = created_at.format("%Y-%m-%d %H:%M:%S").to_string(); - - let mut message_str = format!( - "A workflow has been suspended and is waiting for approval:\n\n\ - *Created by*: {created_by}\n\ - *Created at*: {created_at_formatted}\n\ - *Script path*: {script_path_str}\n\ - *Args*: {args_str}\n\ - *Flow ID*: {parent_job_id_str}\n\n" - ); - - // Append custom message if provided - if let Some(msg) = message { - message_str.push_str(msg); - } - - tracing::debug!("Schema: {:#?}", schema); - - if let Some(resume_schema) = schema { - let hide_cancel = resume_schema.hide_cancel.unwrap_or(false); - - // if hide cancel is false add note to message - if !hide_cancel { - message_str.push_str("\n\n*NOTE*: closing this modal will cancel the workflow.\n\n"); - } - - // Convert message_str back to &str when needed - let message_str_ref: &str = &message_str; - - if let Some(schema_obj) = resume_schema.resume_form { - let inner_schema: ResumeSchema = - serde_json::from_value(schema_obj.clone()).map_err(|e| { - tracing::error!("Failed to deserialize form schema: {:?}", e); - Error::BadRequest( - "Failed to deserialize resume form schema! Unsupported form field used." - .to_string(), - ) - })?; - - let blocks = transform_schemas( - message_str_ref, - Some(&inner_schema.schema.properties), - &urls, - Some(&inner_schema.schema.order), - Some(&inner_schema.schema.required), - default_args_json, - dynamic_enums_json, - ) - .await?; - - tracing::debug!("Slack Blocks: {:#?}", blocks); - return Ok(axum::Json(construct_payload( - blocks, - hide_cancel, - trigger_id, - &urls.resume, - resource_path, - container, - ))); - } else { - tracing::debug!("No suspend form found!"); - let blocks = transform_schemas( - message_str_ref, - None, - &urls, - None, - None, - default_args_json, - dynamic_enums_json, - ) - .await?; - return Ok(axum::Json(construct_payload( - blocks, - hide_cancel, - trigger_id, - &urls.resume, - resource_path, - container, - ))); - } - } else { - Err(Error::BadRequest( - "No approval form schema found.".to_string(), - )) - } + tracing::debug!("Slack Blocks: {:#?}", card_content); + Ok(axum::Json(construct_payload( + card_content, + schema.as_ref().and_then(|s| s.hide_cancel).unwrap_or(false), + trigger_id, + &urls.resume, + resource_path, + container, + ))) } fn construct_payload( diff --git a/backend/windmill-api/src/smtp_server_ee.rs b/backend/windmill-api/src/smtp_server_oss.rs similarity index 69% rename from backend/windmill-api/src/smtp_server_ee.rs rename to backend/windmill-api/src/smtp_server_oss.rs index 48e274f6a1..7d2b72c7f1 100644 --- a/backend/windmill-api/src/smtp_server_ee.rs +++ b/backend/windmill-api/src/smtp_server_oss.rs @@ -1,7 +1,15 @@ +#[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, pub db: DB, @@ -9,6 +17,7 @@ pub struct SmtpServer { pub base_internal_url: String, } +#[cfg(not(feature = "private"))] impl SmtpServer { pub async fn start_listener_thread(self: Arc, _addr: SocketAddr) -> anyhow::Result<()> { let _ = self.auth_cache; diff --git a/backend/windmill-api/src/sqs_triggers_ee.rs b/backend/windmill-api/src/sqs_triggers_oss.rs similarity index 67% rename from backend/windmill-api/src/sqs_triggers_ee.rs rename to backend/windmill-api/src/sqs_triggers_oss.rs index 4f7eb6e254..6e01641a5e 100644 --- a/backend/windmill-api/src/sqs_triggers_ee.rs +++ b/backend/windmill-api/src/sqs_triggers_oss.rs @@ -1,19 +1,31 @@ +#[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, pub aws_resource_path: String, pub message_attributes: Option>, pub path: String, @@ -28,4 +40,4 @@ pub struct SqsTrigger { pub server_id: Option, pub last_server_ping: Option>, pub enabled: bool, -} \ No newline at end of file +} diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index d5656fb6c1..48bd94081c 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -10,6 +10,8 @@ use axum::{body::Body, extract::OriginalUri, http::Response, response::IntoRespo #[cfg(feature = "static_frontend")] use axum::http::header; +#[cfg(feature = "static_frontend")] +use http::HeaderValue; use hyper::Uri; #[cfg(feature = "static_frontend")] @@ -17,6 +19,12 @@ use mime_guess::mime; #[cfg(feature = "static_frontend")] use rust_embed::RustEmbed; +// Content Security Policy configuration +#[cfg(feature = "static_frontend")] +lazy_static::lazy_static! { + static ref CSP_POLICY: String = std::env::var("CSP_POLICY").unwrap_or_default(); +} + // static_handler is a handler that serves static files from the pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFile { StaticFile(original_uri) @@ -51,6 +59,13 @@ fn serve_path(path: &str) -> Response { let mut res = Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + + // Add Content-Security-Policy header for static assets when policy is set + if !CSP_POLICY.is_empty() { + if let Ok(header_value) = HeaderValue::try_from(CSP_POLICY.as_str()) { + res = res.header("Content-Security-Policy", header_value); + } + } if mime.as_ref() == mime::APPLICATION_JAVASCRIPT || mime.as_ref() == mime::TEXT_JAVASCRIPT || path.ends_with(".wasm") diff --git a/backend/windmill-api/src/stripe_ee.rs b/backend/windmill-api/src/stripe_ee.rs deleted file mode 100644 index 1aea2ecd2d..0000000000 --- a/backend/windmill-api/src/stripe_ee.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(feature = "stripe")] -use axum::Router; - -#[cfg(feature = "stripe")] -pub fn add_stripe_routes(router: Router) -> Router { - return router; -} diff --git a/backend/windmill-api/src/stripe_oss.rs b/backend/windmill-api/src/stripe_oss.rs new file mode 100644 index 0000000000..206a78dc53 --- /dev/null +++ b/backend/windmill-api/src/stripe_oss.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::stripe_ee::*; + +#[cfg(not(feature = "private"))] +use axum::Router; + +#[cfg(not(feature = "private"))] +pub fn add_stripe_routes(router: Router) -> Router { + return router; +} diff --git a/backend/windmill-api/src/teams_approvals_oss.rs b/backend/windmill-api/src/teams_approvals_oss.rs new file mode 100644 index 0000000000..df5826aa71 --- /dev/null +++ b/backend/windmill-api/src/teams_approvals_oss.rs @@ -0,0 +1,14 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::teams_approvals_ee::*; + +#[cfg(not(feature = "private"))] +use hyper::StatusCode; + +#[cfg(not(feature = "private"))] +use windmill_common::error::Error; + +#[cfg(not(feature = "private"))] +pub async fn request_teams_approval() -> Result { + Err(Error::InternalErr("enterprise feature only".to_string())) +} diff --git a/backend/windmill-api/src/teams_ee.rs b/backend/windmill-api/src/teams_oss.rs similarity index 70% rename from backend/windmill-api/src/teams_ee.rs rename to backend/windmill-api/src/teams_oss.rs index 46cbe72059..95d4883690 100644 --- a/backend/windmill-api/src/teams_ee.rs +++ b/backend/windmill-api/src/teams_oss.rs @@ -1,39 +1,50 @@ -use http::status::StatusCode; -#[cfg(feature = "enterprise")] +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::teams_ee::*; + +#[cfg(all(feature = "enterprise", not(feature = "private")))] use axum::Router; +#[cfg(not(feature = "private"))] +use http::status::StatusCode; +#[cfg(not(feature = "private"))] use windmill_common::error::Error; +#[cfg(not(feature = "private"))] pub async fn edit_teams_command() -> Result { return Err(Error::BadRequest( "Teams only available on enterprise".to_string(), )); } +#[cfg(not(feature = "private"))] pub async fn workspaces_list_available_teams_ids() -> Result { return Err(Error::BadRequest( "Teams only available on enterprise".to_string(), )); } +#[cfg(not(feature = "private"))] pub async fn connect_teams() -> Result { return Err(Error::BadRequest( "Teams only available on enterprise".to_string(), )); } +#[cfg(not(feature = "private"))] pub async fn run_teams_message_test_job() -> Result { return Err(Error::BadRequest( "Teams only available on enterprise".to_string(), )); } +#[cfg(not(feature = "private"))] pub async fn workspaces_list_available_teams_channels() -> Result { return Err(Error::BadRequest( "Teams only available on enterprise".to_string(), )); } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub fn teams_service() -> Router { Router::new() -} \ No newline at end of file +} diff --git a/backend/windmill-api/src/tracing_init.rs b/backend/windmill-api/src/tracing_init.rs index 9f34e93592..c5c841b2dc 100644 --- a/backend/windmill-api/src/tracing_init.rs +++ b/backend/windmill-api/src/tracing_init.rs @@ -33,6 +33,8 @@ impl OnResponse for MyOnResponse { let status = response.status().as_u16(); if response.status().is_success() || response.status().is_redirection() { tracing::info!(latency = latency, status = status, "response") + } else if response.status().as_u16() == 404 { + tracing::warn!(latency = latency, status = status, "response") } else { tracing::error!(latency = latency, status = status, "response") } diff --git a/backend/windmill-api/src/trigger_helpers.rs b/backend/windmill-api/src/trigger_helpers.rs new file mode 100644 index 0000000000..917fe48bfa --- /dev/null +++ b/backend/windmill-api/src/trigger_helpers.rs @@ -0,0 +1,444 @@ +use serde::Deserialize; +use serde_json::value::RawValue; +use std::collections::HashMap; +use windmill_common::{ + error::Result, + flows::FlowModuleValue, + get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, + jobs::get_has_preprocessor_from_content_and_lang, + scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, + triggers::{ + HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerKind, + RUNNABLE_FORMAT_VERSION_CACHE, + }, + utils::StripPath, + worker::to_raw_value, + FlowVersionInfo, +}; +use windmill_queue::PushArgsOwned; + +use crate::{db::DB, HTTP_CLIENT}; + +struct ScriptInfo { + has_preprocessor: Option, + language: ScriptLang, + content: String, + schema: Option>, +} + +#[derive(Debug, Deserialize)] +struct PropertyDefinition { + r#type: Option, +} + +#[derive(Debug, Deserialize)] +struct PartialSchema { + properties: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum RunnableId { + FlowPath(String), + ScriptId(ScriptId), + HubScript(String), +} + +impl RunnableId { + pub fn from_script_hash(hash: ScriptHash) -> Self { + Self::ScriptId(ScriptId::ScriptHash(hash)) + } + + pub fn from_script_path(path: &str) -> Self { + if path.starts_with("hub/") { + Self::HubScript(path.to_string()) + } else { + Self::ScriptId(ScriptId::ScriptPath(path.to_string())) + } + } + + pub fn from_flow_path(path: &str) -> Self { + Self::FlowPath(path.to_string()) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum ScriptId { + ScriptPath(String), + ScriptHash(ScriptHash), +} + +impl ScriptId { + async fn get_script_hash(self, workspace_id: &str, db: &DB) -> Result { + let hash = match self { + ScriptId::ScriptPath(path) => { + let info = get_latest_deployed_hash_for_path(db, workspace_id, &path).await?; + info.hash + } + ScriptId::ScriptHash(hash) => hash.0, + }; + + Ok(hash) + } +} + +async fn get_script_info( + db: &DB, + workspace_id: &str, + hash: i64, +) -> std::result::Result { + sqlx::query_as!(ScriptInfo, "SELECT has_preprocessor, language as \"language: _\", content, schema as \"schema: _\" FROM script WHERE workspace_id = $1 AND hash = $2", workspace_id, hash) + .fetch_one(db) + .await +} + +fn runnable_format_from_schema_without_preprocessor( + trigger_kind: &TriggerKind, + schema: Option>, +) -> RunnableFormat { + match trigger_kind { + TriggerKind::Mqtt + if schema.as_ref().is_some_and(|schema| { + schema.properties.as_ref().is_some_and(|properties| { + properties.iter().any(|(key, def)| { + key == "payload" && def.r#type.as_ref().is_some_and(|t| t == "array") + }) + }) + }) => + { + RunnableFormat { version: RunnableFormatVersion::V1, has_preprocessor: false } + } + TriggerKind::Kafka | TriggerKind::Nats + if schema.as_ref().is_some_and(|schema| { + schema + .properties + .as_ref() + .is_some_and(|properties| properties.keys().any(|key| key == "msg")) + }) => + { + RunnableFormat { version: RunnableFormatVersion::V1, has_preprocessor: false } + } + _ => RunnableFormat { version: RunnableFormatVersion::V2, has_preprocessor: false }, + } +} + +fn runnable_format_from_preprocessor_args( + args: Option>, +) -> RunnableFormat { + if let Some(args) = args { + if args.iter().any(|arg| arg.name == "wm_trigger") + || (args.len() > 0 && args.iter().all(|arg| arg.name != "event")) + { + RunnableFormat { version: RunnableFormatVersion::V1, has_preprocessor: true } + } else { + RunnableFormat { version: RunnableFormatVersion::V2, has_preprocessor: true } + } + } else { + RunnableFormat { version: RunnableFormatVersion::V2, has_preprocessor: true } + } +} + +enum PreprocessorInfo { + Preprocessor { content: String, language: ScriptLang }, + NoPreprocessor { schema: Option> }, +} + +#[derive(Debug, Deserialize)] +struct FlowInfo { + preprocessor_module: Option>, + schema: Option>, +} + +fn get_preprocessor_args_from_content_and_language( + content: &str, + language: &ScriptLang, +) -> Result>> { + let args = match language { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature( + &content, + true, + false, + Some("preprocessor".to_string()), + )?; + Some(args.args) + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature( + &content, + Some("preprocessor".to_string()), + false, + )?; + Some(args.args) + } + _ => None, + }; + Ok(args) +} + +pub async fn get_runnable_format( + runnable_id: RunnableId, + workspace_id: &str, + db: &DB, + trigger_kind: &TriggerKind, +) -> Result { + let (key, preprocessor_info) = match runnable_id { + RunnableId::HubScript(path) => { + let Some(version) = path.split("/").nth(1) else { + return Err(windmill_common::error::Error::internal_err( + "Invalid hub script path".to_string(), + )); + }; + + let version = match version.parse::() { + Ok(version) => version, + Err(_) => { + return Err(windmill_common::error::Error::internal_err( + "Invalid hub script version".to_string(), + )); + } + }; + + let key = (HubOrWorkspaceId::Hub, version, trigger_kind.clone()); + + let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); + + if let Some(runnable_format) = runnable_format { + tracing::debug!("Using cached runnable format for hub script {path}"); + return Ok(runnable_format); + } + + let hub_script = + get_full_hub_script_by_path(StripPath(path.to_string()), &HTTP_CLIENT, Some(db)) + .await?; + + let has_preprocessor = get_has_preprocessor_from_content_and_lang( + &hub_script.content, + &hub_script.language, + )?; + + let partial_schema = serde_json::from_str(hub_script.schema.get())?; + + ( + key, + if has_preprocessor { + PreprocessorInfo::Preprocessor { + content: hub_script.content, + language: hub_script.language, + } + } else { + PreprocessorInfo::NoPreprocessor { + schema: Some(sqlx::types::Json(partial_schema)), + } + }, + ) + } + RunnableId::FlowPath(path) => { + let FlowVersionInfo { version, .. } = + get_latest_flow_version_info_for_path(db, workspace_id, &path, true).await?; + + let key = ( + HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), + version, + trigger_kind.clone(), + ); + + let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); + + if let Some(runnable_format) = runnable_format { + tracing::debug!("Using cached runnable format for flow {path}"); + return Ok(runnable_format); + } + + let flow_info = sqlx::query_as!( + FlowInfo, + "SELECT + value->'preprocessor_module'->'value' as \"preprocessor_module: _\", + schema as \"schema: _\" + FROM flow_version + WHERE + path = $1 + AND workspace_id = $2 + ORDER BY created_at DESC + LIMIT 1", + path, + workspace_id, + ) + .fetch_one(db) + .await?; + + if let Some(preprocessor_module) = flow_info.preprocessor_module { + match preprocessor_module.0 { + FlowModuleValue::RawScript { content, language, .. } => { + (key, PreprocessorInfo::Preprocessor { content, language }) + } + FlowModuleValue::Script { path, hash, .. } => { + let hash = if let Some(hash) = hash { + hash.0 + } else { + let script_hash = + get_latest_deployed_hash_for_path(db, workspace_id, &path).await?; + script_hash.hash + }; + let script_info = get_script_info(db, workspace_id, hash).await?; + ( + key, + PreprocessorInfo::Preprocessor { + content: script_info.content, + language: script_info.language, + }, + ) + } + _ => { + return Err(windmill_common::error::Error::internal_err( + "Unsupported preprocessor module".to_string(), + )); + } + } + } else { + ( + key, + PreprocessorInfo::NoPreprocessor { schema: flow_info.schema }, + ) + } + } + RunnableId::ScriptId(script_id) => { + let hash = script_id.get_script_hash(workspace_id, db).await?; + let key = ( + HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), + hash, + trigger_kind.clone(), + ); + let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); + + if let Some(runnable_format) = runnable_format { + tracing::debug!("Using cached runnable format for script {hash}"); + return Ok(runnable_format); + } + + let script_info = get_script_info(db, workspace_id, hash).await?; + + if script_info.has_preprocessor.unwrap_or(false) { + ( + key, + PreprocessorInfo::Preprocessor { + content: script_info.content, + language: script_info.language, + }, + ) + } else { + ( + key, + PreprocessorInfo::NoPreprocessor { schema: script_info.schema }, + ) + } + } + }; + + let runnable_format = match preprocessor_info { + PreprocessorInfo::Preprocessor { content, language } => { + let args = get_preprocessor_args_from_content_and_language(&content, &language)?; + runnable_format_from_preprocessor_args(args) + } + PreprocessorInfo::NoPreprocessor { schema } => { + runnable_format_from_schema_without_preprocessor(trigger_kind, schema) + } + }; + + RUNNABLE_FORMAT_VERSION_CACHE.insert(key, runnable_format); + + Ok(runnable_format) +} + +#[allow(dead_code)] +pub trait TriggerJobArgs { + fn v1_payload_fn(payload: T) -> HashMap>; + fn v2_payload_fn(payload: T) -> HashMap> { + Self::v1_payload_fn(payload) + } + fn trigger_kind() -> TriggerKind; + + fn build_job_args_v2( + has_preprocessor: bool, + payload: T, + info: HashMap>, + ) -> PushArgsOwned { + let trigger_kind = Self::trigger_kind(); + let mut args = Self::v2_payload_fn(payload); + if has_preprocessor { + args.insert("kind".to_string(), to_raw_value(&trigger_kind.to_key())); + args.extend(info); + let args = HashMap::from([("event".to_string(), to_raw_value(&args))]); + PushArgsOwned { args, extra: None } + } else { + PushArgsOwned { args, extra: None } + } + } + + fn build_job_args_v1( + has_preprocessor: bool, + payload: T, + info: HashMap>, + ) -> PushArgsOwned { + let trigger_kind = Self::trigger_kind(); + let trigger_key = trigger_kind.to_key(); + let args = Self::v1_payload_fn(payload); + let extra = if has_preprocessor { + Some(HashMap::from([( + "wm_trigger".to_string(), + to_raw_value(&serde_json::json!({ + "kind": trigger_key, + trigger_key: info + })), + )])) + } else { + None + }; + + PushArgsOwned { args, extra } + } + + async fn build_job_args( + runnable_path: &str, + is_flow: bool, + w_id: &str, + db: &DB, + payload: T, + info: HashMap>, + ) -> Result { + let runnable_id = if is_flow { + RunnableId::from_flow_path(runnable_path) + } else { + RunnableId::from_script_path(runnable_path) + }; + Self::build_job_args_from_runnable_id(runnable_id, w_id, db, payload, info).await + } + + async fn build_job_args_from_runnable_id( + runnable_id: RunnableId, + w_id: &str, + db: &DB, + payload: T, + info: HashMap>, + ) -> Result { + let runnable_format = + get_runnable_format(runnable_id, w_id, db, &Self::trigger_kind()).await?; + + match runnable_format { + RunnableFormat { version: RunnableFormatVersion::V1, has_preprocessor } => { + Ok(Self::build_job_args_v1(has_preprocessor, payload, info)) + } + RunnableFormat { version: RunnableFormatVersion::V2, has_preprocessor } => { + Ok(Self::build_job_args_v2(has_preprocessor, payload, info)) + } + } + } + + fn build_capture_payloads( + payload: T, + info: HashMap>, + ) -> (PushArgsOwned, PushArgsOwned) { + let main_args = Self::build_job_args_v2(false, payload.clone(), info.clone()); + let preprocessor_args = Self::build_job_args_v2(true, payload, info); + (main_args, preprocessor_args) + } +} diff --git a/backend/windmill-api/src/triggers.rs b/backend/windmill-api/src/triggers.rs index 7101bdd2a9..c1cccb1c98 100644 --- a/backend/windmill-api/src/triggers.rs +++ b/backend/windmill-api/src/triggers.rs @@ -21,6 +21,9 @@ pub struct TriggersCount { kafka_count: i64, nats_count: i64, postgres_count: i64, + mqtt_count: i64, + sqs_count: i64, + gcp_count: i64, } pub(crate) async fn get_triggers_count_internal( db: &DB, @@ -97,6 +100,36 @@ pub(crate) async fn get_triggers_count_internal( .await? .unwrap_or(0); + let mqtt_count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM mqtt_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", + path, + is_flow, + w_id + ) + .fetch_one(db) + .await? + .unwrap_or(0); + + let sqs_count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM sqs_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", + path, + is_flow, + w_id + ) + .fetch_one(db) + .await? + .unwrap_or(0); + + let gcp_count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM gcp_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", + path, + is_flow, + w_id + ) + .fetch_one(db) + .await? + .unwrap_or(0); + let webhook_count = (if is_flow { sqlx::query_scalar!( "SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]", @@ -141,6 +174,9 @@ pub(crate) async fn get_triggers_count_internal( kafka_count, nats_count, postgres_count, + mqtt_count, + gcp_count, + sqs_count, })) } diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index b8cac903b6..3a4c2be568 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -8,6 +8,8 @@ #![allow(non_snake_case)] +use quick_cache::sync::Cache; + use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::time::Duration; @@ -20,7 +22,8 @@ use crate::utils::{ generate_instance_wide_unique_username, get_instance_username_or_create_pending, }; use crate::{ - db::DB, utils::require_super_admin, webhook_util::WebhookShared, COOKIE_DOMAIN, IS_SECURE, + auth::ExpiringAuthCache, db::DB, utils::require_super_admin, webhook_util::WebhookShared, + COOKIE_DOMAIN, IS_SECURE, }; use argon2::{Argon2, PasswordHash, PasswordVerifier}; use axum::{ @@ -39,7 +42,7 @@ use sqlx::FromRow; use time::OffsetDateTime; use tower_cookies::{Cookie, Cookies}; use tracing::Instrument; -use windmill_audit::audit_ee::{audit_log, AuditAuthor}; +use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; use windmill_common::auth::fetch_authed_from_permissioned_as; use windmill_common::global_settings::AUTOMATE_USERNAME_CREATION_SETTING; @@ -214,6 +217,10 @@ pub async fn fetch_api_authed( fetch_api_authed_from_permissioned_as(permissioned_as, email, w_id, db, username_override).await } +lazy_static::lazy_static! { + static ref API_AUTHED_CACHE: Cache<(String,String,String), ExpiringAuthCache> = Cache::new(300); +} + #[allow(unused)] pub async fn fetch_api_authed_from_permissioned_as( permissioned_as: String, @@ -222,18 +229,43 @@ pub async fn fetch_api_authed_from_permissioned_as( db: &DB, username_override: Option, ) -> error::Result { - let authed = - fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?; - Ok(ApiAuthed { - username: authed.username, - email: email, - is_admin: authed.is_admin, - is_operator: authed.is_operator, - groups: authed.groups, - folders: authed.folders, - scopes: authed.scopes, - username_override: username_override, - }) + let key = (w_id.to_string(), permissioned_as.clone(), email.clone()); + + let mut api_authed = match API_AUTHED_CACHE.get(&key) { + Some(expiring_authed) if expiring_authed.expiry > chrono::Utc::now() => { + tracing::debug!("API authed cache hit for user {}", email); + expiring_authed.authed + } + _ => { + tracing::debug!("API authed cache miss for user {}", email); + let authed = + fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?; + + let api_authed = ApiAuthed { + username: authed.username, + email: email, + is_admin: authed.is_admin, + is_operator: authed.is_operator, + groups: authed.groups, + folders: authed.folders, + scopes: authed.scopes, + username_override: None, + }; + + API_AUTHED_CACHE.insert( + key, + ExpiringAuthCache { + authed: api_authed.clone(), + expiry: chrono::Utc::now() + chrono::Duration::try_seconds(120).unwrap(), + }, + ); + + api_authed + } + }; + + api_authed.username_override = username_override; + Ok(api_authed) } #[derive(FromRow, Serialize)] @@ -302,6 +334,7 @@ pub struct NewUser { pub super_admin: bool, pub name: Option, pub company: Option, + pub skip_email: Option, } #[derive(Deserialize)] @@ -509,7 +542,7 @@ async fn list_users_as_super_admin( let rows = if active_only.is_some_and(|x| x) { sqlx::query_as!( GlobalUserInfo, - "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')), + "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')), authors as (SELECT distinct email FROM usr WHERE usr.operator IS false) SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username FROM password @@ -1475,7 +1508,7 @@ async fn create_user( Extension(argon2): Extension>>, Json(nu): Json, ) -> Result<(StatusCode, String)> { - crate::users_ee::create_user(authed, db, webhook, argon2, nu).await + crate::users_oss::create_user(authed, db, webhook, argon2, nu).await } async fn delete_workspace_user( @@ -1549,7 +1582,7 @@ async fn set_password( Json(ep): Json, ) -> Result { let email = authed.email.clone(); - crate::users_ee::set_password(db, argon2, authed, &email, ep).await + crate::users_oss::set_password(db, argon2, authed, &email, ep).await } async fn set_password_of_user( @@ -1560,7 +1593,7 @@ async fn set_password_of_user( Json(ep): Json, ) -> Result { require_super_admin(&db, &authed.email).await?; - crate::users_ee::set_password(db, argon2, authed, &email, ep).await + crate::users_oss::set_password(db, argon2, authed, &email, ep).await } async fn set_login_type( @@ -1717,7 +1750,22 @@ async fn refresh_token( .await? .unwrap_or(false); - let _ = create_session_token(&authed.email, super_admin, &mut tx, cookies).await?; + let new_token = create_session_token(&authed.email, super_admin, &mut tx, cookies).await?; + + audit_log( + &mut *tx, + &AuditAuthor { + email: authed.email.to_string(), + username: authed.email.to_string(), + username_override: None, + }, + "users.token.refresh", + ActionKind::Create, + &"global", + Some(&truncate_token(&new_token)), + None, + ) + .await?; tx.commit().await?; Ok("token refreshed".to_string()) @@ -1805,6 +1853,18 @@ async fn create_token( .fetch_optional(&mut *tx) .await? .unwrap_or(false); + if *CLOUD_HOSTED { + let nb_tokens = + sqlx::query_scalar!("SELECT COUNT(*) FROM token WHERE email = $1", &authed.email) + .fetch_one(&db) + .await?; + if nb_tokens.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of tokens (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } sqlx::query!( "INSERT INTO token (token, email, label, expiration, super_admin, scopes, workspace_id) @@ -2538,7 +2598,7 @@ async fn update_username_in_workpsace<'c>( .await?; sqlx::query!( - r#"UPDATE flow_workspace_runnables SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, + r#"UPDATE workspace_runnable_dependencies SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, new_username, old_username, w_id @@ -2546,7 +2606,15 @@ async fn update_username_in_workpsace<'c>( .await?; sqlx::query!( - r#"UPDATE flow_workspace_runnables SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, + r#"UPDATE workspace_runnable_dependencies SET app_path = REGEXP_REPLACE(app_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE app_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, + new_username, + old_username, + w_id + ).execute(&mut **tx) + .await?; + + sqlx::query!( + r#"UPDATE workspace_runnable_dependencies SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, new_username, old_username, w_id diff --git a/backend/windmill-api/src/users_ee.rs b/backend/windmill-api/src/users_oss.rs similarity index 71% rename from backend/windmill-api/src/users_ee.rs rename to backend/windmill-api/src/users_oss.rs index 7a11239a2f..88a12eb710 100644 --- a/backend/windmill-api/src/users_ee.rs +++ b/backend/windmill-api/src/users_oss.rs @@ -1,15 +1,27 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::users_ee::*; + +#[cfg(not(feature = "private"))] use std::sync::Arc; +#[cfg(not(feature = "private"))] use crate::db::ApiAuthed; +#[cfg(not(feature = "private"))] use crate::users::{EditPassword, NewUser}; +#[cfg(not(feature = "private"))] use crate::{db::DB, webhook_util::WebhookShared}; +#[cfg(not(feature = "private"))] use argon2::Argon2; +#[cfg(not(feature = "private"))] use http::StatusCode; +#[cfg(not(feature = "private"))] use windmill_common::error::{Error, Result}; +#[cfg(not(feature = "private"))] pub async fn create_user( _authed: ApiAuthed, _db: DB, @@ -22,6 +34,7 @@ pub async fn create_user( )) } +#[cfg(not(feature = "private"))] pub async fn set_password( _db: DB, _argon2: Arc>, @@ -34,6 +47,7 @@ pub async fn set_password( )) } +#[cfg(not(feature = "private"))] pub fn send_email_if_possible(_subject: &str, _content: &str, _to: &str) { tracing::warn!( "send_email_if_possible is not implemented in Windmill's Open Source repository" diff --git a/backend/windmill-api/src/utils.rs b/backend/windmill-api/src/utils.rs index 1da91ae448..b81d4e4bc4 100644 --- a/backend/windmill-api/src/utils.rs +++ b/backend/windmill-api/src/utils.rs @@ -8,7 +8,7 @@ use axum::{body::Body, response::Response}; use regex::Regex; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use sqlx::{Postgres, Transaction}; #[cfg(feature = "enterprise")] use windmill_common::worker::CLOUD_HOSTED; @@ -29,13 +29,6 @@ pub struct WithStarredInfoQuery { pub with_starred_info: Option, } -#[derive(Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum RunnableKind { - Script, - Flow, -} - pub async fn require_super_admin(db: &DB, email: &str) -> error::Result<()> { let is_admin = is_super_admin_email(db, email).await?; @@ -190,6 +183,15 @@ pub fn content_plain(body: Body) -> Response { .unwrap() } +#[allow(unused)] +pub fn non_empty_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let o: Option = Option::deserialize(deserializer)?; + Ok(o.filter(|s| !s.trim().is_empty())) +} + use serde::Serialize; #[derive(Serialize)] @@ -406,3 +408,16 @@ pub async fn acknowledge_all_critical_alerts( ); Ok("All unacknowledged critical alerts acknowledged".to_string()) } + +#[cfg(feature = "http_trigger")] +#[derive(Clone)] +pub struct ExpiringCacheEntry { + pub value: T, + pub expiry: std::time::Instant, +} + +#[cfg(all(feature = "kafka", feature = "enterprise", feature = "private"))] +pub async fn update_rw_lock(lock: std::sync::Arc>, value: T) -> () { + let mut w = lock.write().await; + *w = value; +} diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index 425c4d86fc..4a6a970db5 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -20,7 +20,7 @@ use axum::{ use hyper::StatusCode; use serde_json::Value; -use windmill_audit::audit_ee::{audit_log, AuditAuthorable}; +use windmill_audit::audit_oss::{audit_log, AuditAuthorable}; use windmill_audit::ActionKind; use windmill_common::{ db::UserDB, @@ -29,6 +29,7 @@ use windmill_common::{ variables::{ build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable, }, + worker::CLOUD_HOSTED, }; use lazy_static::lazy_static; @@ -61,7 +62,7 @@ async fn list_contextual_variables( ) -> JsonResult> { Ok(Json( get_reserved_variables( - &db, + &db.into(), &w_id, "q1A0qcPuO00yxioll7iph76N9CJDqn", &email, @@ -74,8 +75,7 @@ async fn list_contextual_variables( Some("u/user/triggering_flow_path".to_string()), Some("c".to_string()), Some("017e0ad5-f499-73b6-5488-92a61c5196dd".to_string()), - Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c".to_string()), - Some(chrono::offset::Utc::now()) + Some(chrono::offset::Utc::now()), ) .await .to_vec(), @@ -186,7 +186,7 @@ async fn get_variable( #[cfg(feature = "oauth2")] { Some( - crate::oauth2_ee::_refresh_token( + crate::oauth2_oss::_refresh_token( tx, &variable.path, &w_id, @@ -314,6 +314,20 @@ async fn create_variable( Query(AlreadyEncrypted { already_encrypted }): Query, Json(variable): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_variables = sqlx::query_scalar!( + "SELECT COUNT(*) FROM variable WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_variables.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of variables (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } let authed = maybe_refresh_folders(&variable.path, &w_id, authed, &db).await; check_path_conflict(&db, &w_id, &variable.path).await?; @@ -450,6 +464,7 @@ struct EditVariable { value: Option, is_secret: Option, description: Option, + account: Option, } #[derive(Deserialize)] @@ -506,6 +521,10 @@ async fn update_variable( sqlb.set_str("description", &desc); } + if let Some(account_id) = ns.account { + sqlb.set_str("account", account_id); + } + if let Some(nbool) = ns.is_secret { let old_secret = sqlx::query_scalar!( "SELECT is_secret from variable WHERE path = $1 AND workspace_id = $2", @@ -523,6 +542,21 @@ async fn update_variable( sqlb.set_str("is_secret", nbool); } sqlb.returning("path"); + + // Get old account_id if we're updating the account field + let old_account_id = if ns.account.is_some() { + sqlx::query_scalar!( + "SELECT account FROM variable WHERE path = $1 AND workspace_id = $2", + &path, + &w_id + ) + .fetch_optional(&db) + .await? + .flatten() + } else { + None + }; + let mut tx: Transaction<'_, Postgres> = user_db.begin(&authed).await?; if let Some(npath) = ns.path { @@ -575,6 +609,33 @@ async fn update_variable( None, ) .await?; + + // Clean up old account if it's no longer referenced and different from new account + if let Some(old_acc_id) = old_account_id { + if ns.account.is_some() && ns.account != Some(old_acc_id) { + // Check if old account is still referenced by other variables or resources + let account_still_used = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM variable WHERE account = $1 AND workspace_id = $2)", + old_acc_id, + &w_id + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(true); + + if !account_still_used { + // Delete the orphaned account + sqlx::query!( + "DELETE FROM account WHERE id = $1 AND workspace_id = $2", + old_acc_id, + &w_id + ) + .execute(&mut *tx) + .await?; + } + } + } + tx.commit().await?; handle_deployment_metadata( @@ -653,7 +714,7 @@ pub async fn get_value_internal<'c>( if variable.is_expired.unwrap_or(false) && variable.account.is_some() { #[cfg(feature = "oauth2")] { - crate::oauth2_ee::_refresh_token( + crate::oauth2_oss::_refresh_token( tx, &variable.path, &w_id, @@ -691,14 +752,21 @@ pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result, Extension(user_db): Extension, Path(w_id): Path, Json(ct): Json, @@ -241,11 +245,23 @@ async fn create_websocket_trigger( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::WebsocketTrigger { path: ct.path.clone() }, + Some(format!("WebSocket trigger '{}' created", ct.path)), + true, + ) + .await?; + Ok((StatusCode::CREATED, format!("{}", ct.path))) } async fn update_websocket_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(ct): Json, @@ -284,16 +300,27 @@ async fn update_websocket_trigger( &mut *tx, &authed, "websocket_triggers.update", - ActionKind::Create, + ActionKind::Update, &w_id, - Some(path), + Some(&ct.path), None, ) .await?; tx.commit().await?; - Ok(path.to_string()) + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::WebsocketTrigger { path: ct.path.clone() }, + Some(format!("WebSocket trigger '{}' updated", ct.path)), + true, + ) + .await?; + + Ok(ct.path.to_string()) } #[derive(Deserialize)] @@ -303,6 +330,7 @@ pub struct SetEnabled { pub async fn set_enabled( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, Json(payload): Json, @@ -336,6 +364,17 @@ pub async fn set_enabled( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::WebsocketTrigger { path: path.to_string() }, + Some(format!("WebSocket trigger '{}' updated", path)), + true, + ) + .await?; + Ok(format!( "succesfully updated WebSocket trigger at path {} to status {}", path, payload.enabled @@ -344,6 +383,7 @@ pub async fn set_enabled( async fn delete_websocket_trigger( authed: ApiAuthed, + Extension(db): Extension, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> error::Result { @@ -370,6 +410,17 @@ async fn delete_websocket_trigger( tx.commit().await?; + handle_deployment_metadata( + &authed.email, + &authed.username, + &db, + &w_id, + windmill_git_sync::DeployedObject::WebsocketTrigger { path: path.to_string() }, + Some(format!("WebSocket trigger '{}' deleted", path)), + true, + ) + .await?; + Ok(format!("WebSocket trigger {path} deleted")) } @@ -606,7 +657,6 @@ async fn wait_runnable_result( StripPath(path.clone()), RunJobQuery::default(), args, - None, ) .await?; @@ -633,7 +683,6 @@ async fn wait_runnable_result( StripPath(path.clone()), RunJobQuery::default(), args, - None, ) .await?; @@ -888,10 +937,11 @@ impl WebsocketTrigger { async fn handle( &self, db: &DB, - args: PushArgsOwned, + msg: &str, + trigger_info: HashMap>, return_message_channels: Option, ) -> () { - if let Err(err) = run_job(db, self, args, return_message_channels).await { + if let Err(err) = run_job(db, self, &msg, trigger_info, return_message_channels).await { report_critical_error( format!( "Failed to trigger job from WebSocket {}: {:?}", @@ -917,6 +967,16 @@ impl WebsocketTrigger { } } +impl TriggerJobArgs<&str> for WebsocketTrigger { + fn v1_payload_fn(payload: &str) -> HashMap> { + HashMap::from([("msg".to_string(), to_raw_value(&payload))]) + } + + fn trigger_kind() -> TriggerKind { + TriggerKind::Websocket + } +} + #[derive(Deserialize)] struct CaptureConfigForWebsocket { trigger_config: SqlxJson, @@ -983,15 +1043,18 @@ impl CaptureConfigForWebsocket { Some(()) } - async fn handle(&self, db: &DB, args: PushArgsOwned) -> () { + async fn handle(&self, db: &DB, msg: &str, trigger_info: HashMap>) -> () { + let (main_args, preprocessor_args) = + WebsocketTrigger::build_capture_payloads(&msg, trigger_info); + if let Err(err) = insert_capture_payload( db, &self.workspace_id, &self.path, self.is_flow, &TriggerKind::Websocket, - PushArgsOwned { args: args.args, extra: None }, - args.extra.as_ref().map(to_raw_value), + main_args, + preprocessor_args, &self.owner, ) .await @@ -1257,20 +1320,15 @@ async fn listen_to_websocket( } } if should_handle { - - let args = HashMap::from([("msg".to_string(), to_raw_value(&text))]); - let extra = Some(HashMap::from([( - "wm_trigger".to_string(), - to_raw_value(&serde_json::json!({"kind": "websocket", "websocket": { "url": url }})), - )])); - - let args = PushArgsOwned { args, extra }; + let trigger_info = HashMap::from([ + ("url".to_string(), to_raw_value(&url)), + ]); match &ws { WebsocketEnum::Trigger(ws_trigger) => { - ws_trigger.handle(&db, args, return_message_channels.clone()).await; + ws_trigger.handle(&db, &text, trigger_info, return_message_channels.clone()).await; }, WebsocketEnum::Capture(capture) => { - capture.handle(&db, args).await; + capture.handle(&db, &text, trigger_info).await; }, } } @@ -1309,9 +1367,20 @@ async fn listen_to_websocket( async fn run_job( db: &DB, trigger: &WebsocketTrigger, - args: PushArgsOwned, + msg: &str, + trigger_info: HashMap>, return_message_channels: Option, ) -> anyhow::Result<()> { + let args = WebsocketTrigger::build_job_args( + &trigger.script_path, + trigger.is_flow, + &trigger.workspace_id, + db, + msg, + trigger_info, + ) + .await?; + let authed = fetch_api_authed( trigger.edited_by.clone(), trigger.email.clone(), @@ -1372,7 +1441,6 @@ async fn run_job( runnable_path, run_query, args, - None, ) .await?; } else { @@ -1384,7 +1452,6 @@ async fn run_job( runnable_path, run_query, args, - None, ) .await?; } diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index c83370bfb5..9953d3b41c 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -8,9 +8,9 @@ use std::collections::HashMap; -use crate::ai::{AIProvider, AIResource, AI_KEY_CACHE}; +use crate::ai::{AIConfig, AI_REQUEST_CACHE}; use crate::db::ApiAuthed; -use crate::users_ee::send_email_if_possible; +use crate::users_oss::send_email_if_possible; use crate::utils::get_instance_username_or_create_pending; use crate::BASE_URL; use crate::{ @@ -30,13 +30,13 @@ use chrono::Utc; use regex::Regex; use uuid::Uuid; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::db::UserDB; use windmill_common::s3_helpers::LargeFileStorage; use windmill_common::users::username_to_permissioned_as; use windmill_common::variables::{build_crypt, decrypt, encrypt}; -use windmill_common::worker::to_raw_value; +use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; #[cfg(feature = "enterprise")] use windmill_common::workspaces::WorkspaceDeploymentUISettings; #[cfg(feature = "enterprise")] @@ -58,7 +58,7 @@ use sqlx::{FromRow, Postgres, Transaction}; use windmill_common::oauth2::InstanceEvent; use windmill_common::utils::not_found_if_none; -use crate::teams_ee::{ +use crate::teams_oss::{ connect_teams, edit_teams_command, run_teams_message_test_job, workspaces_list_available_teams_channels, workspaces_list_available_teams_ids, }; @@ -143,9 +143,9 @@ pub fn workspaced_service() -> Router { .route("/critical_alerts/mute", post(mute_critical_alerts)) .route("/operator_settings", post(update_operator_settings)); - #[cfg(feature = "stripe")] + #[cfg(all(feature = "stripe", feature = "enterprise"))] { - crate::stripe_ee::add_stripe_routes(router) + crate::stripe_oss::add_stripe_routes(router) } #[cfg(not(feature = "stripe"))] @@ -211,10 +211,7 @@ pub struct WorkspaceSettings { #[serde(skip_serializing_if = "Option::is_none")] pub deploy_to: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub ai_resource: Option, - pub ai_models: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub code_completion_model: Option, + pub ai_config: Option, #[serde(skip_serializing_if = "Option::is_none")] pub error_handler: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -229,7 +226,6 @@ pub struct WorkspaceSettings { pub deploy_ui: Option, // effectively: WorkspaceDeploymentUISettings #[serde(skip_serializing_if = "Option::is_none")] pub default_app: Option, - pub automatic_billing: bool, #[serde(skip_serializing_if = "Option::is_none")] pub default_scripts: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -238,6 +234,8 @@ pub struct WorkspaceSettings { pub color: Option, #[serde(skip_serializing_if = "Option::is_none")] pub operator_settings: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub git_app_installations: Option, } #[derive(FromRow, Serialize, Debug)] @@ -291,13 +289,6 @@ struct EditWebhook { webhook: Option, } -#[derive(Deserialize)] -struct EditCopilotConfig { - ai_resource: Option, - code_completion_model: Option, - ai_models: Vec, -} - #[derive(Deserialize, Serialize, Debug)] struct LargeFileStorageWithSecondary { #[serde(flatten)] @@ -393,19 +384,15 @@ async fn list_pending_invites( async fn is_premium( authed: ApiAuthed, - Extension(db): Extension, - Path(w_id): Path, + Extension(_db): Extension, + Path(_w_id): Path, ) -> JsonResult { require_admin(authed.is_admin, &authed.username)?; - let mut tx = db.begin().await?; - let row = sqlx::query_scalar!( - "SELECT premium FROM workspace WHERE workspace.id = $1", - &w_id - ) - .fetch_one(&mut *tx) - .await?; - tx.commit().await?; - Ok(Json(row)) + #[cfg(feature = "cloud")] + let premium = windmill_common::workspaces::is_premium_workspace(&_db, &_w_id).await; + #[cfg(not(feature = "cloud"))] + let premium = false; + Ok(Json(premium)) } async fn exists_workspace( @@ -453,13 +440,15 @@ async fn get_settings( let mut tx = user_db.begin(&authed).await?; let settings = sqlx::query_as!( WorkspaceSettings, - "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_resource, ai_models, code_completion_model, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, automatic_billing, default_scripts, mute_critical_alerts, color, operator_settings FROM workspace_settings WHERE workspace_id = $1", + "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_config, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, default_scripts, mute_critical_alerts, color, operator_settings, git_app_installations FROM workspace_settings WHERE workspace_id = $1", &w_id ) - .fetch_one(&mut *tx) + .fetch_optional(&mut *tx) .await .map_err(|e| Error::internal_err(format!("getting settings: {e:#}")))?; tx.commit().await?; + let settings = not_found_if_none(settings, "workspace settings", &w_id)?; + Ok(Json(settings)) } @@ -499,11 +488,11 @@ async fn edit_slack_command( if es.slack_command_script.is_some() { let exists_slack_command_with_team_id = sqlx::query_scalar!( r#" - SELECT EXISTS (SELECT 1 - FROM workspace_settings - WHERE workspace_id <> $1 + SELECT EXISTS (SELECT 1 + FROM workspace_settings + WHERE workspace_id <> $1 AND slack_command_script IS NOT NULL - AND slack_team_id IS NOT NULL + AND slack_team_id IS NOT NULL AND slack_team_id = (SELECT slack_team_id FROM workspace_settings WHERE workspace_id = $1)) "#, &w_id @@ -651,7 +640,7 @@ async fn edit_auto_invite( Path(w_id): Path, Json(ea): Json, ) -> Result { - crate::workspaces_ee::edit_auto_invite(authed, db, w_id, ea).await + crate::workspaces_oss::edit_auto_invite(authed, db, w_id, ea).await } async fn edit_webhook( @@ -701,51 +690,26 @@ async fn edit_copilot_config( Extension(db): Extension, Path(w_id): Path, ApiAuthed { is_admin, username, .. }: ApiAuthed, - Json(eo): Json, + Json(ai_config): Json, ) -> Result { require_admin(is_admin, &username)?; let mut tx = db.begin().await?; - if let Some(ai_resource) = &eo.ai_resource { - let parsed_ai_resource = serde_json::from_value::(ai_resource.clone()) - .map_err(|e| Error::BadRequest(e.to_string()))?; + sqlx::query!( + "UPDATE workspace_settings SET ai_config = $1 WHERE workspace_id = $2", + sqlx::types::Json(&ai_config) as sqlx::types::Json<&AIConfig>, + &w_id + ) + .execute(&mut *tx) + .await?; - #[cfg(not(feature = "enterprise"))] - { - if matches!(parsed_ai_resource.provider, AIProvider::CustomAI) { - return Err(Error::BadRequest( - "Custom AI is only available on EE".to_string(), - )); - } + if let Some(ref providers) = ai_config.providers { + for provider in providers.keys() { + AI_REQUEST_CACHE.remove(&(w_id.clone(), provider.clone())); } - - sqlx::query!( - "UPDATE workspace_settings SET ai_resource = $1, code_completion_model = $2, ai_models = $3 WHERE workspace_id = $4", - ai_resource, - eo.code_completion_model, - eo.ai_models.as_slice(), - &w_id - ) - .execute(&mut *tx) - .await?; - - if let Some(cached) = AI_KEY_CACHE.get(&w_id) { - if parsed_ai_resource.path.is_none() || parsed_ai_resource.path.unwrap() != cached.path - { - AI_KEY_CACHE.remove(&w_id); - } - } - } else { - sqlx::query!( - "UPDATE workspace_settings SET ai_resource = NULL, code_completion_model = $1, ai_models = '{}' WHERE workspace_id = $2", - eo.code_completion_model, - &w_id, - ) - .execute(&mut *tx) - .await?; - AI_KEY_CACHE.remove(&w_id); } + audit_log( &mut *tx, &authed, @@ -753,16 +717,7 @@ async fn edit_copilot_config( ActionKind::Update, &w_id, Some(&authed.email), - Some( - [ - ("ai_resource", &format!("{:?}", eo.ai_resource)[..]), - ( - "code_completion_model", - &format!("{:?}", eo.code_completion_model)[..], - ), - ] - .into(), - ), + Some([("ai_config", &format!("{:?}", ai_config)[..])].into()), ) .await?; tx.commit().await?; @@ -770,42 +725,33 @@ async fn edit_copilot_config( Ok(format!("Edit copilot config for workspace {}", &w_id)) } -#[derive(Serialize)] -struct CopilotInfo { - #[serde(skip_serializing_if = "Option::is_none")] - pub ai_provider: Option, - pub exists_ai_resource: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub code_completion_model: Option, - pub ai_models: Vec, -} async fn get_copilot_info( Extension(db): Extension, Path(w_id): Path, -) -> JsonResult { +) -> JsonResult { let mut tx = db.begin().await?; - let record = sqlx::query!( - "SELECT ai_resource, code_completion_model, ai_models FROM workspace_settings WHERE workspace_id = $1", + let copilot_info = sqlx::query_scalar!( + "SELECT ai_config as \"ai_config: sqlx::types::Json\" FROM workspace_settings WHERE workspace_id = $1", &w_id ) .fetch_one(&mut *tx) .await - .map_err(|e| Error::internal_err(format!("getting ai_resource and code_completion_model: {e:#}")))?; + .map_err(|e| { + Error::internal_err(format!( + "getting ai config: {e:#}" + )) + })?; tx.commit().await?; - let (ai_provider, exists_ai_resource) = if let Some(ai_resource) = record.ai_resource { - let ai_resource = serde_json::from_value::(ai_resource)?; - (Some(ai_resource.provider), ai_resource.path.is_some()) + if let Some(sqlx::types::Json(copilot_info)) = copilot_info { + Ok(Json(copilot_info)) } else { - (None, false) - }; - - Ok(Json(CopilotInfo { - ai_provider, - exists_ai_resource, - code_completion_model: record.code_completion_model, - ai_models: record.ai_models, - })) + Ok(Json(AIConfig { + providers: None, + default_model: None, + code_completion_model: None, + })) + } } async fn edit_large_file_storage_config( @@ -1362,7 +1308,8 @@ struct UsedTriggers { pub nats_used: bool, pub postgres_used: bool, pub mqtt_used: bool, - pub sqs_used: bool + pub sqs_used: bool, + pub gcp_used: bool, } async fn get_used_triggers( @@ -1374,15 +1321,15 @@ async fn get_used_triggers( let websocket_used = sqlx::query_as!( UsedTriggers, r#" - SELECT - - EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS "websocket_used!", + SELECT + EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS "websocket_used!", EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS "http_routes_used!", EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as "kafka_used!", EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as "nats_used!", EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS "postgres_used!", EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS "mqtt_used!", - EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS "sqs_used!" + EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS "sqs_used!", + EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS "gcp_used!" "#, w_id ) @@ -1507,6 +1454,21 @@ async fn create_workspace( #[cfg(not(feature = "enterprise"))] _check_nb_of_workspaces(&db).await?; + if *CLOUD_HOSTED { + let nb_workspaces = sqlx::query_scalar!( + "SELECT COUNT(*) FROM workspace WHERE owner = $1", + authed.email + ) + .fetch_one(&db) + .await?; + if nb_workspaces.unwrap_or(0) >= 10 { + return Err(Error::BadRequest( + "You have reached the maximum number of workspaces (10) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } + let mut tx: Transaction<'_, Postgres> = db.begin().await?; check_w_id_conflict(&mut tx, &nw.id).await?; @@ -2104,8 +2066,8 @@ async fn change_workspace_color( async fn get_usage(Extension(db): Extension, Path(w_id): Path) -> Result { let usage = sqlx::query_scalar!( " - SELECT usage.usage FROM usage - WHERE is_workspace = true + SELECT usage.usage FROM usage + WHERE is_workspace = true AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND id = $1", w_id diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 8c6293bddb..5673958ab8 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -147,6 +147,7 @@ pub(crate) struct ArchiveQueryParams { skip_secrets: Option, skip_variables: Option, skip_resources: Option, + skip_resource_types: Option, include_schedules: Option, include_triggers: Option, include_users: Option, @@ -249,10 +250,7 @@ struct SimplifiedSettings { error_handler_extra_args: Option, error_handler_muted_on_cancel: bool, #[serde(skip_serializing_if = "Option::is_none")] - ai_resource: Option, - ai_models: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - code_completion_model: Option, + ai_config: Option, #[serde(skip_serializing_if = "Option::is_none")] large_file_storage: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -280,6 +278,7 @@ pub(crate) async fn tarball_workspace( plain_secret, plain_secrets, skip_resources, + skip_resource_types, skip_secrets, skip_variables, include_schedules, @@ -361,6 +360,7 @@ pub(crate) async fn tarball_workspace( ScriptLang::Bigquery => "bq.sql", ScriptLang::Snowflake => "sf.sql", ScriptLang::Mssql => "ms.sql", + ScriptLang::DuckDb => "duckdb.sql", ScriptLang::Graphql => "gql", ScriptLang::Nativets => "fetch.ts", ScriptLang::Bun | ScriptLang::Bunnative => { @@ -374,7 +374,10 @@ pub(crate) async fn tarball_workspace( ScriptLang::Rust => "rs", ScriptLang::Ansible => "playbook.yml", ScriptLang::CSharp => "cs", + ScriptLang::Nu => "nu", ScriptLang::OracleDB => "odb.sql", + ScriptLang::Java => "java", + // for related places search: ADD_NEW_LANG }; archive .write_to_archive(&script.content, &format!("{}.{}", script.path, ext)) @@ -428,7 +431,7 @@ pub(crate) async fn tarball_workspace( } } - if !skip_resources.unwrap_or(false) { + if !skip_resource_types.unwrap_or(false) { let resource_types = sqlx::query_as!( ResourceType, "SELECT * FROM resource_type WHERE workspace_id = $1", @@ -497,9 +500,9 @@ pub(crate) async fn tarball_workspace( { let apps = sqlx::query_as::<_, AppWithLastVersion>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, - app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by from app, app_version - WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)]", + app.extra_perms, app_version.value, + app_version.created_at, app_version.created_by from app, app_version + WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false", ) .bind(&w_id) .fetch_all(&mut *tx) @@ -534,13 +537,37 @@ pub(crate) async fn tarball_workspace( #[cfg(feature = "http_trigger")] { let http_triggers = sqlx::query_as!( - crate::http_triggers::HttpTrigger, - "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, edited_by, edited_at, email, extra_perms, is_async, requires_auth, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger - WHERE workspace_id = $1", - &w_id - ) - .fetch_all(&mut *tx) - .await?; + crate::http_triggers::HttpTrigger, + r#" + SELECT + workspace_id, + workspaced_route, + path, + route_path, + route_path_key, + authentication_resource_path, + script_path, + is_flow, + summary, + description, + edited_by, + edited_at, + email, + extra_perms, + is_async, + authentication_method AS "authentication_method: _", + http_method AS "http_method: _", + static_asset_config AS "static_asset_config: _", + is_static_website, + wrap_body, + raw_string + FROM http_trigger + WHERE workspace_id = $1 + "#, + &w_id + ) + .fetch_all(&mut *tx) + .await?; for trigger in http_triggers { let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); @@ -553,13 +580,35 @@ pub(crate) async fn tarball_workspace( #[cfg(feature = "websocket")] { let websocket_triggers = sqlx::query_as!( - crate::websocket_triggers::WebsocketTrigger, - "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger - WHERE workspace_id = $1", - &w_id - ) - .fetch_all(&mut *tx) - .await?; + crate::websocket_triggers::WebsocketTrigger, + r#" + SELECT + workspace_id, + path, + url, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled, + filters AS "filters: _", + initial_messages AS "initial_messages: _", + url_runnable_args AS "url_runnable_args: _", + can_return_message + FROM + websocket_trigger + WHERE + workspace_id = $1 + "#, + &w_id + ) + .fetch_all(&mut *tx) + .await?; for trigger in websocket_triggers { let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); @@ -575,7 +624,7 @@ pub(crate) async fn tarball_workspace( #[cfg(all(feature = "enterprise", feature = "kafka"))] { let kafka_triggers = sqlx::query_as!( - crate::kafka_triggers_ee::KafkaTrigger, + crate::kafka_triggers_oss::KafkaTrigger, "SELECT * FROM kafka_trigger WHERE workspace_id = $1", &w_id @@ -597,9 +646,30 @@ pub(crate) async fn tarball_workspace( #[cfg(all(feature = "enterprise", feature = "sqs_trigger"))] { let sqs_triggers = sqlx::query_as!( - crate::sqs_triggers_ee::SqsTrigger, - "SELECT * FROM sqs_trigger - WHERE workspace_id = $1", + crate::sqs_triggers_oss::SqsTrigger, + r#" + SELECT + aws_auth_resource_type AS "aws_auth_resource_type: _", + aws_resource_path, + message_attributes, + queue_url, + workspace_id, + path, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled + FROM + sqs_trigger + WHERE + workspace_id = $1 + "#, &w_id ) .fetch_all(&mut *tx) @@ -608,10 +678,49 @@ pub(crate) async fn tarball_workspace( for trigger in sqs_triggers { let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); archive - .write_to_archive( - &trigger_str, - &format!("{}.sqs_trigger.json", trigger.path), - ) + .write_to_archive(&trigger_str, &format!("{}.sqs_trigger.json", trigger.path)) + .await?; + } + } + + #[cfg(all(feature = "enterprise", feature = "gcp_trigger"))] + { + let gcp_triggers = sqlx::query_as!( + crate::gcp_triggers_oss::GcpTrigger, + r#" + SELECT + gcp_resource_path, + subscription_id, + topic_id, + workspace_id, + delivery_type AS "delivery_type: _", + delivery_config AS "delivery_config: _", + subscription_mode AS "subscription_mode: _", + path, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled + FROM + gcp_trigger + WHERE + workspace_id = $1 + "#, + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in gcp_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive(&trigger_str, &format!("{}.gcp_trigger.json", trigger.path)) .await?; } } @@ -619,7 +728,7 @@ pub(crate) async fn tarball_workspace( #[cfg(all(feature = "enterprise", feature = "nats"))] { let nats_triggers = sqlx::query_as!( - crate::nats_triggers_ee::NatsTrigger, + crate::nats_triggers_oss::NatsTrigger, "SELECT * FROM nats_trigger WHERE workspace_id = $1", &w_id @@ -657,6 +766,45 @@ pub(crate) async fn tarball_workspace( .await?; } } + + #[cfg(all(feature = "enterprise", feature = "mqtt_trigger"))] + { + let mqtt_triggers = sqlx::query_as!( + crate::mqtt_triggers::MqttTrigger, + r#" + SELECT + mqtt_resource_path, + subscribe_topics as "subscribe_topics: _", + v3_config as "v3_config: _", + v5_config as "v5_config: _", + client_version AS "client_version: _", + client_id, + workspace_id, + path, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled + FROM + mqtt_trigger + "#, + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in mqtt_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive(&trigger_str, &format!("{}.mqtt_trigger.json", trigger.path)) + .await?; + } + } } if include_users.unwrap_or(false) { @@ -690,7 +838,7 @@ pub(crate) async fn tarball_workspace( if include_groups.unwrap_or(false) { let groups = sqlx::query!( - r#"SELECT g_.workspace_id, name, summary, extra_perms, array_agg(u2g.usr) filter (where u2g.usr is not null) as members + r#"SELECT g_.workspace_id, name, summary, extra_perms, array_agg(u2g.usr) filter (where u2g.usr is not null) as members FROM usr u JOIN usr_to_group u2g ON u2g.usr = u.username AND u2g.workspace_id = u.workspace_id RIGHT JOIN group_ g_ ON g_.workspace_id = u.workspace_id AND g_.name = u2g.group_ @@ -752,22 +900,20 @@ pub(crate) async fn tarball_workspace( let settings = sqlx::query_as!( SimplifiedSettings, r#"SELECT - -- slack_team_id, - -- slack_name, - -- slack_command_script, + -- slack_team_id, + -- slack_name, + -- slack_command_script, -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email, auto_invite_domain IS NOT NULL AS "auto_invite_enabled!", - CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS "auto_invite_as!", - CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS "auto_invite_mode!", - webhook, - deploy_to, - error_handler, - ai_resource, - ai_models, - code_completion_model, - error_handler_extra_args, - error_handler_muted_on_cancel, - large_file_storage, + CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS "auto_invite_as!", + CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS "auto_invite_mode!", + webhook, + deploy_to, + error_handler, + ai_config, + error_handler_extra_args, + error_handler_muted_on_cancel, + large_file_storage, git_sync, default_app, default_scripts, diff --git a/backend/windmill-api/src/workspaces_extra.rs b/backend/windmill-api/src/workspaces_extra.rs index 2f77112331..6238ead74f 100644 --- a/backend/windmill-api/src/workspaces_extra.rs +++ b/backend/windmill-api/src/workspaces_extra.rs @@ -8,12 +8,13 @@ use axum::{ Json, }; -use windmill_audit::audit_ee::audit_log; +use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::worker::CLOUD_HOSTED; use windmill_common::{ + auth::is_super_admin_email, error::{Error, Result}, utils::require_admin, }; @@ -32,7 +33,7 @@ pub(crate) async fn change_workspace_id( Extension(db): Extension, Json(rw): Json, ) -> Result { - if *CLOUD_HOSTED { + if *CLOUD_HOSTED && !is_super_admin_email(&db, &authed.email).await? { return Err(Error::BadRequest( "This feature is not available on the cloud".to_string(), )); @@ -190,7 +191,7 @@ pub(crate) async fn change_workspace_id( .await?; sqlx::query!( - "UPDATE flow_workspace_runnables SET workspace_id = $1 WHERE workspace_id = $2", + "UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2", &rw.new_id, &old_id ) diff --git a/backend/windmill-api/src/workspaces_ee.rs b/backend/windmill-api/src/workspaces_oss.rs similarity index 70% rename from backend/windmill-api/src/workspaces_ee.rs rename to backend/windmill-api/src/workspaces_oss.rs index aa8799e233..4f55539da1 100644 --- a/backend/windmill-api/src/workspaces_ee.rs +++ b/backend/windmill-api/src/workspaces_oss.rs @@ -1,8 +1,14 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::workspaces_ee::*; + +#[cfg(not(feature = "private"))] use crate::{ db::{ApiAuthed, DB}, workspaces::EditAutoInvite, }; +#[cfg(not(feature = "private"))] pub async fn edit_auto_invite( _authed: ApiAuthed, _db: DB, diff --git a/backend/windmill-audit/Cargo.toml b/backend/windmill-audit/Cargo.toml index 8b202b4abf..5a9ea376b6 100644 --- a/backend/windmill-audit/Cargo.toml +++ b/backend/windmill-audit/Cargo.toml @@ -10,6 +10,7 @@ path = "./src/lib.rs" [features] enterprise = ["windmill-common/enterprise"] +private = [] [dependencies] serde.workspace = true diff --git a/backend/windmill-audit/src/audit_ee.rs b/backend/windmill-audit/src/audit_oss.rs similarity index 78% rename from backend/windmill-audit/src/audit_ee.rs rename to backend/windmill-audit/src/audit_oss.rs index 97a8e6bbcf..d29daaa7ae 100644 --- a/backend/windmill-audit/src/audit_ee.rs +++ b/backend/windmill-audit/src/audit_oss.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::audit_ee::*; + /* * Author: Ruben Fiszel * Copyright: Windmill Labs, Inc 2022 @@ -5,23 +9,26 @@ * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. */ -use std::collections::HashMap; - -use windmill_common::{ - error::{Error, Result}, - utils::Pagination, +#[cfg(not(feature = "private"))] +use { + crate::{ActionKind, AuditLog, ListAuditLogQuery}, + sqlx::{Postgres, Transaction}, + std::collections::HashMap, + windmill_common::{ + error::{Error, Result}, + utils::Pagination, + }, }; -use crate::{ActionKind, AuditLog, ListAuditLogQuery}; -use sqlx::{Postgres, Transaction}; - #[derive(Clone)] +#[cfg(not(feature = "private"))] pub struct AuditAuthor { pub username: String, pub email: String, pub username_override: Option, } +#[cfg(not(feature = "private"))] impl AuditAuthorable for AuditAuthor { fn email(&self) -> &str { &self.email @@ -36,12 +43,14 @@ impl AuditAuthorable for AuditAuthor { } } +#[cfg(not(feature = "private"))] pub trait AuditAuthorable { fn username(&self) -> &str; fn email(&self) -> &str; fn username_override(&self) -> Option<&str>; } +#[cfg(not(feature = "private"))] #[tracing::instrument(level = "trace", skip_all)] pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>( _db: E, @@ -56,6 +65,7 @@ pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>( Ok(()) } +#[cfg(not(feature = "private"))] pub async fn list_audit( _tx: Transaction<'_, Postgres>, _w_id: String, @@ -66,6 +76,7 @@ pub async fn list_audit( return Ok(vec![]); } +#[cfg(not(feature = "private"))] pub async fn get_audit(tx: Transaction<'_, Postgres>, _id: i32, _w_id: &str) -> Result { // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature tx.commit().await?; diff --git a/backend/windmill-audit/src/lib.rs b/backend/windmill-audit/src/lib.rs index 10894798fb..15bda522c1 100644 --- a/backend/windmill-audit/src/lib.rs +++ b/backend/windmill-audit/src/lib.rs @@ -1,7 +1,9 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; +#[cfg(feature = "private")] pub mod audit_ee; +pub mod audit_oss; #[derive(sqlx::Type, Serialize, Deserialize, Debug)] #[sqlx(type_name = "ACTION_KIND", rename_all = "lowercase")] diff --git a/backend/windmill-autoscaling/Cargo.toml b/backend/windmill-autoscaling/Cargo.toml index fbebaf0fd7..7e4bc623ad 100644 --- a/backend/windmill-autoscaling/Cargo.toml +++ b/backend/windmill-autoscaling/Cargo.toml @@ -10,6 +10,7 @@ path = "./src/lib.rs" [features] enterprise = ["windmill-queue/enterprise", "windmill-common/enterprise"] +private = [] default = [] [dependencies] diff --git a/backend/windmill-autoscaling/src/autoscaling_ee.rs b/backend/windmill-autoscaling/src/autoscaling_ee.rs deleted file mode 100644 index 1c9defbede..0000000000 --- a/backend/windmill-autoscaling/src/autoscaling_ee.rs +++ /dev/null @@ -1,6 +0,0 @@ -use windmill_common::DB; - -pub async fn apply_all_autoscaling(_db: &DB) -> anyhow::Result<()> { - // Autoscaling is an ee feature - Ok(()) -} diff --git a/backend/windmill-autoscaling/src/autoscaling_oss.rs b/backend/windmill-autoscaling/src/autoscaling_oss.rs new file mode 100644 index 0000000000..c9cbdc3ee0 --- /dev/null +++ b/backend/windmill-autoscaling/src/autoscaling_oss.rs @@ -0,0 +1,12 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::autoscaling_ee::*; + +#[cfg(not(feature = "private"))] +use windmill_common::DB; + +#[cfg(not(feature = "private"))] +pub async fn apply_all_autoscaling(_db: &DB) -> anyhow::Result<()> { + // Autoscaling is an ee feature + Ok(()) +} diff --git a/backend/windmill-autoscaling/src/lib.rs b/backend/windmill-autoscaling/src/lib.rs index 28b9319244..1b9d6b94bf 100644 --- a/backend/windmill-autoscaling/src/lib.rs +++ b/backend/windmill-autoscaling/src/lib.rs @@ -1,2 +1,4 @@ -mod autoscaling_ee; -pub use autoscaling_ee::*; +#[cfg(feature = "private")] +pub mod autoscaling_ee; +mod autoscaling_oss; +pub use autoscaling_oss::*; diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index f19ef1b06c..250f48e336 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -7,22 +7,26 @@ edition.workspace = true [features] default = [] enterprise = [] +private = [] jemalloc = ["dep:tikv-jemalloc-ctl"] tantivy = [] prometheus = ["dep:prometheus"] loki = ["dep:tracing-loki"] benchmark = [] -parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"] +parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts", "dep:aws-smithy-types-convert", "dep:datafusion"] +aws_auth = ["dep:aws-sdk-sts", "dep:aws-config"] otel = ["dep:opentelemetry-semantic-conventions", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk", "dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-appender-tracing", "dep:tonic"] smtp = ["dep:mail-send"] scoped_cache = [] - +cloud = [] +openidconnect = ["dep:openidconnect"] [lib] name = "windmill_common" path = "src/lib.rs" [dependencies] +tar.workspace = true hmac.workspace = true sha2.workspace = true thiserror.workspace = true @@ -32,6 +36,8 @@ serde_json.workspace = true chrono.workspace = true chrono-tz.workspace = true hex.workspace = true +reqwest-middleware = { workspace = true } +reqwest-retry = { workspace = true } rand.workspace = true sqlx = { workspace = true, features = ["postgres"] } uuid.workspace = true @@ -39,6 +45,9 @@ tracing = { workspace = true } axum = { workspace = true } hyper = { workspace = true } tokio = { workspace = true } +tokio-stream.workspace = true +tokio-util.workspace = true +datafusion = { workspace = true, optional = true} reqwest = { workspace = true } tracing-subscriber = { workspace = true } lazy_static.workspace = true @@ -54,6 +63,7 @@ object_store = { workspace = true, optional = true } prometheus = { workspace = true, optional = true } aws-config = { workspace = true, optional = true } aws-sdk-sts = { workspace = true, optional = true } +aws-smithy-types-convert = { workspace = true, optional = true } indexmap.workspace = true bytes.workspace = true mail-send = { workspace = true, optional = true } @@ -62,8 +72,15 @@ async-stream.workspace = true const_format.workspace = true crc.workspace = true windmill-macros.workspace = true +windmill-parser-sql.workspace = true +windmill-parser-ts.workspace = true +windmill-parser-py.workspace = true jsonwebtoken.workspace = true backon.workspace = true +openidconnect = { workspace = true, optional = true } +strum.workspace = true +strum_macros.workspace = true +url.workspace = true semver.workspace = true croner = "2.0.6" @@ -71,6 +88,8 @@ quick_cache.workspace = true pin-project-lite.workspace = true futures.workspace = true tempfile.workspace = true +systemstat.workspace = true +size.workspace = true opentelemetry-semantic-conventions = { workspace = true, optional = true } opentelemetry-otlp = { workspace = true, optional = true } diff --git a/backend/windmill-common/src/agent_workers.rs b/backend/windmill-common/src/agent_workers.rs new file mode 100644 index 0000000000..52c7e2d70c --- /dev/null +++ b/backend/windmill-common/src/agent_workers.rs @@ -0,0 +1,91 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct QueueInitJob { + pub content: String, +} + +use lazy_static::lazy_static; +use std::time::Duration; + +use reqwest_middleware::ClientBuilder; +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; + +use crate::{jwt::decode_without_verify, worker::HttpClient}; + +lazy_static! { + pub static ref BASE_INTERNAL_URL: String = + std::env::var("BASE_INTERNAL_URL").unwrap_or("http://localhost:8080".to_string()); + pub static ref AGENT_TOKEN: String = std::env::var("AGENT_TOKEN").unwrap_or_default(); + pub static ref DECODED_AGENT_TOKEN: Option = { + if AGENT_TOKEN.is_empty() { + None + } else { + decode_without_verify::(AGENT_TOKEN.trim_start_matches(AGENT_JWT_PREFIX)) + .ok() + } + }; +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AgentAuth { + pub worker_group: String, + pub suffix: Option, + pub tags: Vec, + pub exp: Option, +} + +pub const AGENT_JWT_PREFIX: &str = "jwt_agent_"; + +pub fn build_agent_http_client(worker_suffix: &str) -> HttpClient { + let client = ClientBuilder::new( + reqwest::Client::builder() + .pool_max_idle_per_host(10) + .pool_idle_timeout(Duration::from_secs(60)) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(30)) + .default_headers({ + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + "User-Agent", // Replace with your desired header name + "Windmill-Agent/1.0".parse().unwrap(), // Replace with your desired header value + ); + let token = format!( + "{}{}_{}", + AGENT_JWT_PREFIX, + worker_suffix, + AGENT_TOKEN.trim_start_matches(AGENT_JWT_PREFIX), + ); + headers.insert( + "Authorization", + format!("Bearer {}", token).parse().unwrap(), + ); + headers + }) + .build() + .expect("Failed to create HTTP client"), + ) + .with(RetryTransientMiddleware::new_with_policy( + ExponentialBackoff::builder().build_with_max_retries(5), + )) + .build(); + HttpClient(client) +} + +#[derive(Deserialize, Serialize)] +pub struct PingJobStatus { + pub mem_peak: Option, + pub current_mem: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct PingJobStatusResponse { + pub canceled_by: Option, + pub canceled_reason: Option, + pub already_completed: bool, +} + +// #[derive(Serialize, Deserialize)] +// pub struct PullJobRequest { +// pub worker_name: String, +// } diff --git a/backend/windmill-common/src/apps.rs b/backend/windmill-common/src/apps.rs index bc8429c279..c5b22d434c 100644 --- a/backend/windmill-common/src/apps.rs +++ b/backend/windmill-common/src/apps.rs @@ -6,6 +6,8 @@ * LICENSE-AGPL for a copy of the license. */ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; /// Id in the `app_script` table. @@ -21,3 +23,8 @@ pub struct ListAppQuery { pub include_draft_only: Option, pub with_deployment_msg: Option, } + +#[derive(Deserialize)] +pub struct RawAppValue { + pub files: HashMap, +} diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index 451157b115..5823fccf7c 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -1,12 +1,58 @@ +use anyhow::Context; +use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::{ db::Authed, error::{Error, Result}, + jwt, users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SYNC_EMAIL}, DB, }; +#[derive(Debug)] +pub struct IdToken { + token: String, + expiration: DateTime, +} + +pub fn has_expired(expiration_time: DateTime, take: Option) -> bool { + let now = Utc::now(); + + let expiration = match take { + Some(duration) => expiration_time - duration, + None => expiration_time, + }; + + now > expiration +} + +impl From for String { + fn from(value: IdToken) -> Self { + value.token + } +} + +impl ToString for IdToken { + fn to_string(&self) -> String { + self.token.clone() + } +} + +impl IdToken { + pub fn new(token: String, expiration: DateTime) -> Self { + Self { token, expiration } + } + + pub fn token(&self) -> &str { + &self.token + } + pub fn expiration(&self) -> &DateTime { + &self.expiration + } +} + #[derive(Deserialize, Serialize)] pub struct JWTAuthClaims { pub email: String, @@ -22,17 +68,14 @@ pub struct JWTAuthClaims { pub scopes: Option>, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct JobPerms { - pub workspace_id: String, - pub job_id: String, pub email: String, pub username: String, pub is_admin: bool, pub is_operator: bool, pub groups: Vec, pub folders: Vec, - pub created_at: chrono::NaiveDateTime, } impl From for Authed { @@ -208,3 +251,169 @@ pub async fn get_groups_for_user( .collect(); Ok(groups) } + +#[tracing::instrument(level = "trace", skip_all)] +pub async fn create_token_for_owner( + db: &DB, + w_id: &str, + owner: &str, + label: &str, + expires_in: u64, + email: &str, + job_id: &Uuid, + perms: Option, +) -> crate::error::Result { + let job_perms = if perms.is_some() { + Ok(perms) + } else { + sqlx::query_as!( + JobPerms, + "SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2", + job_id, + w_id + ) + .fetch_optional(db) + .await + }; + let job_authed = match job_perms { + Ok(Some(jp)) => jp.into(), + _ => { + tracing::warn!("Could not get permissions for job {job_id} from job_perms table, getting permissions directly..."); + fetch_authed_from_permissioned_as(owner.to_string(), email.to_string(), w_id, db) + .await + .map_err(|e| { + Error::internal_err(format!( + "Could not get permissions directly for job {job_id}: {e:#}" + )) + })? + } + }; + + let payload = JWTAuthClaims { + email: job_authed.email, + username: job_authed.username, + is_admin: job_authed.is_admin, + is_operator: job_authed.is_operator, + groups: job_authed.groups, + folders: job_authed.folders, + label: Some(label.to_string()), + workspace_id: w_id.to_string(), + exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in as i64)).timestamp() + as usize, + job_id: Some(job_id.to_string()), + scopes: None, + }; + + let token = jwt::encode_with_internal_secret(&payload) + .await + .with_context(|| format!("Could not encode JWT token for job {job_id}"))?; + + Ok(format!("jwt_{}", token)) +} + +#[cfg(feature = "aws_auth")] +pub mod aws { + + use crate::error::to_anyhow; + + use super::*; + use crate::utils::empty_as_none; + use aws_config::{BehaviorVersion, Region}; + use aws_sdk_sts::{ + config::Credentials as AwsCredentials, + operation::{ + assume_role_with_saml::AssumeRoleWithSamlOutput, + assume_role_with_web_identity::AssumeRoleWithWebIdentityOutput, + }, + types::Credentials, + Client, + }; + + pub const AWS_OIDC_AUDIENCE: &'static str = "sts.amazonaws.com"; + + pub trait GetAuthenticationOutput { + fn get_credentials(&self) -> Result<&Credentials>; + } + + impl GetAuthenticationOutput for AssumeRoleWithSamlOutput { + fn get_credentials(&self) -> Result<&Credentials> { + let credentials = self.credentials.as_ref().ok_or(Error::BadGateway( + "Error fetching credentials from AWS STS".to_string(), + ))?; + Ok(credentials) + } + } + + impl GetAuthenticationOutput for AssumeRoleWithWebIdentityOutput { + fn get_credentials(&self) -> Result<&Credentials> { + let credentials = self.credentials.as_ref().ok_or(Error::BadGateway( + "Error fetching credentials from AWS STS".to_string(), + ))?; + Ok(credentials) + } + } + + #[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] + #[sqlx(type_name = "AWS_AUTH_RESOURCE_TYPE", rename_all = "lowercase")] + #[serde(rename_all = "lowercase")] + pub enum AwsAuthResourceType { + Credentials, + Oidc, + } + + #[derive(Debug, Deserialize)] + pub struct CredentialsAuth { + #[serde(deserialize_with = "empty_as_none")] + pub region: Option, + #[serde(rename = "awsAccessKeyId")] + pub aws_access_key_id: String, + #[serde(rename = "awsSecretAccessKey")] + pub aws_secret_access_key: String, + } + + #[derive(Clone, Debug, Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct OidcAuth { + #[serde(deserialize_with = "empty_as_none")] + pub region: Option, + #[serde(rename = "roleArn")] + pub role_arn: String, + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + pub enum AWSAuthConfig { + Credentials(CredentialsAuth), + Oidc(OidcAuth), + } + + pub async fn get_oidc_authentication_data( + oidc_auth: OidcAuth, + role_session_name: Option, + token: String, + ) -> Result { + let region = oidc_auth.region.unwrap_or_else(|| "us-east-1".to_string()); + + let credentials = AwsCredentials::new("", "", None, None, "UserInput"); + + let config = aws_config::defaults(BehaviorVersion::latest()) + .credentials_provider(credentials) + .region(Region::new(region.clone())) + .load() + .await; + + let assume_role_with_web_identity_fluent_builder = Client::new(&config) + .assume_role_with_web_identity() + .set_role_arn(Some(oidc_auth.role_arn)) + .set_role_session_name(role_session_name.map(|str| str.to_string())) + .set_web_identity_token(Some(token)); + + let resp = assume_role_with_web_identity_fluent_builder + .clone() + .send() + .await + .map_err(to_anyhow)?; + + Ok(resp) + } +} diff --git a/backend/windmill-worker/src/bench.rs b/backend/windmill-common/src/bench.rs similarity index 98% rename from backend/windmill-worker/src/bench.rs rename to backend/windmill-common/src/bench.rs index 5006d5a34c..c851198b2a 100644 --- a/backend/windmill-worker/src/bench.rs +++ b/backend/windmill-common/src/bench.rs @@ -1,9 +1,9 @@ -use serde::Serialize; -use tokio::time::Instant; -use windmill_common::{ +use crate::{ worker::{write_file, TMP_DIR}, DB, }; +use serde::Serialize; +use tokio::time::Instant; #[derive(Serialize)] pub struct BenchmarkInfo { @@ -41,8 +41,8 @@ impl BenchmarkInfo { self.total_duration = Some(total_duration as u64); println!( - "Writing benchmark {path}, duration of benchmark: {total_duration}s and RPS: {}", - self.iters as f64 / total_duration as f64 + "Writing benchmark {path}, duration of benchmark: {total_duration}ms and RPS: {}", + self.iters as f64 / total_duration as f64 * 1000.0 ); write_file(TMP_DIR, path, &serde_json::to_string(&self).unwrap()).expect("write profiling"); Ok(()) @@ -79,7 +79,7 @@ impl BenchmarkIter { } pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) { - use windmill_common::{jobs::JobKind, scripts::ScriptLang}; + use crate::{jobs::JobKind, scripts::ScriptLang}; let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string()); diff --git a/backend/windmill-common/src/cache.rs b/backend/windmill-common/src/cache.rs index 7447213605..03d8ec7f03 100644 --- a/backend/windmill-common/src/cache.rs +++ b/backend/windmill-common/src/cache.rs @@ -8,9 +8,13 @@ //! and there is only one test per thread, so using thread-local cache avoid unexpected results. use crate::{ - apps::AppScriptId, error, flows::FlowNodeId, flows::FlowValue, scripts::ScriptHash, - scripts::ScriptLang, + apps::AppScriptId, + error, + flows::{FlowNodeId, FlowValue}, + schema::SchemaValidator, + scripts::{ScriptHash, ScriptLang}, }; +use anyhow::anyhow; #[cfg(feature = "scoped_cache")] use std::thread::ThreadId; @@ -307,6 +311,8 @@ pub struct ScriptMetadata { pub language: Option, pub envs: Option>, pub codebase: Option, + pub schema: Option, + pub schema_validator: Option, } #[derive(Debug)] @@ -316,6 +322,25 @@ pub struct RawScript { pub meta: Option, } +#[derive(Debug, Deserialize, Serialize)] +pub struct RawScriptApi { + pub content: String, + pub lock: Option, + pub meta: Option, +} + +impl From for RawScriptApi { + fn from(value: RawScript) -> Self { + RawScriptApi { content: value.content, lock: value.lock, meta: value.meta } + } +} + +impl From for RawScript { + fn from(value: RawScriptApi) -> Self { + RawScript { content: value.content, lock: value.lock, meta: value.meta } + } +} + #[derive(Debug)] pub struct RawFlow { pub raw_flow: Box, @@ -328,6 +353,25 @@ pub struct RawNode { pub raw_flow: Option>, } +#[derive(Debug, Deserialize, Serialize)] +pub struct RawNodeApi { + pub raw_code: Option, + pub raw_lock: Option, + pub raw_flow: Option>, +} + +impl From for RawNodeApi { + fn from(value: RawNode) -> Self { + RawNodeApi { raw_code: value.raw_code, raw_lock: value.raw_lock, raw_flow: value.raw_flow } + } +} + +impl From for RawNode { + fn from(value: RawNodeApi) -> Self { + RawNode { raw_code: value.raw_code, raw_lock: value.raw_lock, raw_flow: value.raw_flow } + } +} + #[derive(Debug, Clone)] struct Entry(Arc); @@ -337,7 +381,7 @@ struct ScriptFull { pub meta: Arc, } -fn unwrap_or_error( +pub fn unwrap_or_error( at: &'static Location, entity: &'static str, key: Key, @@ -357,6 +401,11 @@ pub fn clear() { } pub mod flow { + use crate::{ + worker::{fetch_flow_node_query, Connection}, + DB, + }; + use super::*; make_static! { @@ -382,10 +431,10 @@ pub mod flow { /// This should be preferred over fetching the database directly. #[track_caller] pub fn fetch_script<'c>( - e: impl PgExecutor<'c>, + conn: &'c Connection, node: FlowNodeId, - ) -> impl Future>> { - let fetch_node = fetch_node(e, node); + ) -> impl Future>> + 'c { + let fetch_node = fetch_node(conn, node); async move { fetch_node.await.and_then(|data| match data { RawData::Script(data) => Ok(data), @@ -403,11 +452,12 @@ pub mod flow { /// This should be preferred over fetching the database directly. #[track_caller] pub fn fetch_flow<'c>( - e: impl PgExecutor<'c>, + db: &'c DB, node: FlowNodeId, - ) -> impl Future>> { - let fetch_node = fetch_node(e, node); + ) -> impl Future>> + 'c { async move { + let conn = Connection::Sql(db.clone()); + let fetch_node = fetch_node(&conn, node); fetch_node.await.and_then(|data| match data { RawData::Flow(data) => Ok(data), RawData::Script(_) => Err(error::Error::internal_err(format!( @@ -424,31 +474,23 @@ pub mod flow { /// This should be preferred over fetching the database directly. #[track_caller] pub(super) fn fetch_node<'c>( - e: impl PgExecutor<'c>, + conn: &'c Connection, node: FlowNodeId, - ) -> impl Future> { + ) -> impl Future> + 'c { let loc = Location::caller(); // If not present, `get_or_insert_async` will lock the key until the future completes, // so only one thread will be able to fetch the data from the database and write it to // the file system and cache, hence no race on the file system. NODES.get_or_insert_async(node, async move { - sqlx::query!( - "SELECT \ - code AS \"raw_code: String\", \ - lock AS \"raw_lock: String\", \ - flow AS \"raw_flow: Json>\" \ - FROM flow_node WHERE id = $1 LIMIT 1", - node.0, - ) - .fetch_optional(e) - .await - .map_err(Into::into) - .and_then(unwrap_or_error(&loc, "Flow node", node)) - .map(|r| RawNode { - raw_code: r.raw_code, - raw_lock: r.raw_lock, - raw_flow: r.raw_flow.map(|Json(raw_flow)| raw_flow), - }) + match conn { + Connection::Sql(db) => fetch_flow_node_query(db, node.0, loc).await, + Connection::Http(client) => { + let r = client + .get::(&format!("/api/agent_workers/flow_script/{}", node.0)) + .await?; + Ok(r.into()) + } + } }) } @@ -496,6 +538,8 @@ pub mod flow { } pub mod script { + use crate::{worker::Connection, DB}; + use super::*; make_static! { @@ -514,30 +558,52 @@ pub mod script { /// it to the file system and cache. /// This should be preferred over fetching the database directly. #[track_caller] - pub fn fetch<'c>( - e: impl PgExecutor<'c>, + pub fn fetch( + conn: &Connection, hash: ScriptHash, ) -> impl Future, Arc)>> { // If not present, `get_or_insert_async` will lock the key until the future completes, // so only one thread will be able to fetch the data from the database and write it to // the file system and cache, hence no race on the file system. let loc = Location::caller(); + let conn = conn.clone(); let fut = CACHE.get_or_insert_async(hash, async move { - sqlx::query!( + match conn { + Connection::Sql(db) => fetch_script_from_db(&db, hash, loc).await, + Connection::Http(client) => { + let r = client + .get::(&format!("/api/agent_workers/script/{}", hash.0)) + .await?; + Ok(r.into()) + } + } + }); + fut.map_ok(|ScriptFull { data, meta }| (data, meta)) + } + + pub async fn fetch_script_from_db( + db: &DB, + hash: ScriptHash, + loc: &'static Location<'_>, + ) -> error::Result { + sqlx::query!( "SELECT \ - content AS \"content!: String\", - lock AS \"lock: String\", \ - language AS \"language: Option\", \ - envs AS \"envs: Vec\", \ - codebase LIKE '%.tar' as use_tar \ - FROM script WHERE hash = $1 LIMIT 1", - hash.0 - ) - .fetch_optional(e) - .await - .map_err(Into::into) - .and_then(unwrap_or_error(&loc, "Script", hash)) - .map(|r| RawScript { + content AS \"content!: String\", + lock AS \"lock: String\", \ + language AS \"language: Option\", \ + envs AS \"envs: Vec\", \ + schema AS \"schema: String\", \ + schema_validation AS \"schema_validation: bool\", \ + codebase LIKE '%.tar' as use_tar \ + FROM script WHERE hash = $1 LIMIT 1", + hash.0 + ) + .fetch_optional(db) + .await + .map_err(Into::into) + .and_then(unwrap_or_error(&loc, "Script", hash)) + .and_then(|r| { + Ok(RawScript { content: r.content, lock: r.lock, meta: Some(ScriptMetadata { @@ -553,10 +619,20 @@ pub mod script { } else { None }, + schema_validator: if r.schema_validation { + r.schema + .as_ref() + .map(|schema_str| { + SchemaValidator::from_schema(schema_str).map_err(|e| anyhow!("Couldn't create schema validator for script requiring schema validation: {e}")) + }) + .transpose()? + } else { + None + }, + schema: r.schema, }), }) - }); - fut.map_ok(|ScriptFull { data, meta }| (data, meta)) + }) } /// Invalidate the script cache for the given `hash`. @@ -566,6 +642,8 @@ pub mod script { } pub mod app { + use crate::worker::{fetch_raw_script_from_app_query, Connection}; + use super::*; make_static! { @@ -584,23 +662,23 @@ pub mod app { /// This should be preferred over fetching the database directly. #[track_caller] pub fn fetch_script<'c>( - e: impl PgExecutor<'c>, + conn: &'c Connection, id: AppScriptId, - ) -> impl Future>> { + ) -> impl Future>> + 'c { // If not present, `get_or_insert_async` will lock the key until the future completes, // so only one thread will be able to fetch the data from the database and write it to // the file system and cache, hence no race on the file system. let loc = Location::caller(); let fut = CACHE.get_or_insert_async(id, async move { - sqlx::query!( - "SELECT lock, code FROM app_script WHERE id = $1 LIMIT 1", - id.0, - ) - .fetch_optional(e) - .await - .map_err(Into::into) - .and_then(unwrap_or_error(&loc, "Application script", id)) - .map(|r| RawScript { content: r.code, lock: r.lock, meta: None }) + match conn { + Connection::Sql(db) => fetch_raw_script_from_app_query(db, id.0, loc).await, + Connection::Http(client) => { + let r = client + .get::(&format!("/api/agent_workers/app_script/{}", id.0)) + .await?; + Ok(r.into()) + } + } }); fut.map_ok(|Entry(data)| data) } @@ -608,7 +686,7 @@ pub mod app { pub mod job { use super::*; - use crate::jobs::JobKind; + use crate::{jobs::JobKind, worker::Connection, DB}; #[cfg(not(feature = "scoped_cache"))] lazy_static! { @@ -628,15 +706,18 @@ pub mod job { } #[track_caller] - pub fn fetch_preview_flow<'a, 'c>( - e: impl PgExecutor<'c> + 'a, + pub fn fetch_preview_flow<'a>( + db: &'a DB, job: &'a Uuid, - // original raw values from `queue` or `completed_job` tables: - // kept for backward compatibility. raw_flow: Option>>, ) -> impl Future>> + 'a { - let fetch_preview = fetch_preview(e, job, None, None, raw_flow); + // Create the Connection first so it lives for the entire scope + async move { + let conn = Connection::from(db); + + let fetch_preview = fetch_preview(&conn, job, None, None, raw_flow); + fetch_preview.await.and_then(|data| match data { RawData::Flow(data) => Ok(data), RawData::Script(_) => Err(error::Error::internal_err(format!( @@ -648,7 +729,7 @@ pub mod job { #[track_caller] pub fn fetch_preview_script<'a, 'c>( - e: impl PgExecutor<'c> + 'a, + e: &'a Connection, job: &'a Uuid, // original raw values from `queue` or `completed_job` tables: // kept for backward compatibility. @@ -668,7 +749,7 @@ pub mod job { #[track_caller] pub fn fetch_preview<'a, 'c>( - e: impl PgExecutor<'c> + 'a, + e: &'a Connection, job: &'a Uuid, // original raw values from `queue` or `completed_job` tables: // kept for backward compatibility. @@ -679,16 +760,21 @@ pub mod job { let loc = Location::caller(); let fetch = async move { match (raw_lock, raw_code, raw_flow) { - (None, None, None) => sqlx::query!( - "SELECT raw_code, raw_lock, raw_flow AS \"raw_flow: Json>\" \ - FROM v2_job WHERE id = $1 LIMIT 1", - job - ) - .fetch_optional(e) - .await - .map_err(Into::into) - .and_then(unwrap_or_error(&loc, "Preview", job)) - .map(|r| (r.raw_lock, r.raw_code, r.raw_flow)), + (None, None, None) => match e { + Connection::Sql(pool) => sqlx::query!( + "SELECT raw_code, raw_lock, raw_flow AS \"raw_flow: Json>\" \ + FROM v2_job WHERE id = $1 LIMIT 1", + job + ) + .fetch_optional(pool) + .await + .map_err(Into::into) + .and_then(unwrap_or_error(&loc, "Preview", job)) + .map(|r| (r.raw_lock, r.raw_code, r.raw_flow)), + Connection::Http(_) => Err(error::Error::InternalErr(format!( + "Cannot fetch preview in HTTP mode" + ))), + }, (lock, code, flow) => Ok((lock, code, flow)), } .and_then(|(lock, code, flow)| match flow { @@ -709,8 +795,8 @@ pub mod job { } #[track_caller] - pub fn fetch_script<'c>( - e: impl PgExecutor<'c>, + pub fn fetch_script( + db: DB, kind: JobKind, hash: Option, ) -> impl Future>> { @@ -718,11 +804,15 @@ pub mod job { let loc = Location::caller(); async move { match (kind, hash.map(|ScriptHash(id)| id)) { - (FlowScript, Some(id)) => flow::fetch_script(e, FlowNodeId(id)).await, - (Script | Dependencies, Some(hash)) => script::fetch(e, ScriptHash(hash)) + (FlowScript, Some(id)) => { + flow::fetch_script(&Connection::Sql(db.clone()), FlowNodeId(id)).await + } + (Script | Dependencies, Some(hash)) => script::fetch(&db.into(), ScriptHash(hash)) .await .map(|(data, _meta)| data), - (AppScript, Some(id)) => app::fetch_script(e, AppScriptId(id)).await, + (AppScript, Some(id)) => { + app::fetch_script(&Connection::Sql(db.clone()), AppScriptId(id)).await + } _ => Err(error::Error::internal_err(format!( "Isn't a script job: {:?}", kind @@ -734,19 +824,19 @@ pub mod job { #[track_caller] pub fn fetch_flow<'c>( - e: impl PgExecutor<'c> + Copy, + db: &'c DB, kind: JobKind, hash: Option, - ) -> impl Future>> { + ) -> impl Future>> + 'c { use JobKind::*; let loc = Location::caller(); async move { match (kind, hash.map(|ScriptHash(id)| id)) { - (FlowDependencies, Some(id)) => flow::fetch_version(e, id).await, - (FlowNode, Some(id)) => flow::fetch_flow(e, FlowNodeId(id)).await, - (Flow, Some(id)) => match flow::fetch_version_lite(e, id).await { + (FlowDependencies, Some(id)) => flow::fetch_version(db, id).await, + (FlowNode, Some(id)) => flow::fetch_flow(db, FlowNodeId(id)).await, + (Flow, Some(id)) => match flow::fetch_version_lite(db, id).await { Ok(raw_flow) => Ok(raw_flow), - Err(_) => flow::fetch_version(e, id).await, + Err(_) => flow::fetch_version(db, id).await, }, _ => Err(error::Error::internal_err(format!( "Isn't a flow job {:?}", @@ -802,6 +892,12 @@ const _: () = { } } + impl ScriptMetadata { + fn export_metadata(&self, dst: &impl Storage) -> error::Result<()> { + Ok(dst.put("info.json", serde_json::to_vec(self)?)?) + } + } + impl Export for ScriptFull { type Untrusted = RawScript; @@ -817,7 +913,7 @@ const _: () = { fn export(&self, dst: &impl Storage) -> error::Result<()> { self.data.export(dst)?; - self.meta.export(dst)?; + self.meta.export_metadata(dst)?; Ok(()) } } @@ -933,6 +1029,7 @@ const _: () = { (u64, |x| format!("{:016x}", x)), (Uuid, |x| format!("{:032x}", x.as_u128())), (ScriptHash, |x| format!("{:016x}", x.0)), + ((u8, ScriptHash), |x| format!("{:02x}-{:016x}", x.0, x.1.0)), (FlowNodeId, |x| format!("{:016x}", x.0)), (AppScriptId, |x| format!("{:016x}", x.0)) } diff --git a/backend/windmill-common/src/client.rs b/backend/windmill-common/src/client.rs new file mode 100644 index 0000000000..6bc1d12ef9 --- /dev/null +++ b/backend/windmill-common/src/client.rs @@ -0,0 +1,253 @@ +use anyhow::Context; +use reqwest::{Body, Response}; +use serde::de::DeserializeOwned; + +use crate::{ + error::{self, to_anyhow}, + s3_helpers::{DuckdbConnectionSettingsQueryV2, DuckdbConnectionSettingsResponse}, + utils::HTTP_CLIENT, +}; + +#[derive(Clone)] +pub struct AuthedClient { + pub base_internal_url: String, + pub workspace: String, + pub token: String, + pub force_client: Option, +} + +impl AuthedClient { + pub fn new( + base_internal_url: String, + workspace: String, + token: String, + force_client: Option, + ) -> AuthedClient { + AuthedClient { base_internal_url, workspace, token, force_client } + } + + pub async fn get(&self, url: &str, query: Vec<(&str, String)>) -> anyhow::Result { + self.force_client + .as_ref() + .unwrap_or(&HTTP_CLIENT) + .get(url) + .query(&query) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::AUTHORIZATION, + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token))?, + ) + .send() + .await + .map_err(|e| { + tracing::error!("Error executing get request from authed http client to {url} with query {query:?}: {e}"); + anyhow::anyhow!("Error executing get request from authed http client to {url} with query {query:?}: {e}") + }) + } + + pub async fn get_id_token(&self, audience: &str) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/oidc/token/{}", + self.base_internal_url, self.workspace, audience + ); + let response = self.get(&url, vec![]).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding oidc token as json string")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn get_resource_value(&self, path: &str) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/resources/get_value/{}", + self.base_internal_url, self.workspace, path + ); + let response = self.get(&url, vec![]).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding resource value as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn get_variable_value(&self, path: &str) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/variables/get_value/{}", + self.base_internal_url, self.workspace, path + ); + let response = self.get(&url, vec![]).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding variable value as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn get_resource_value_interpolated( + &self, + path: &str, + job_id: Option, + ) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/resources/get_value_interpolated/{}", + self.base_internal_url, self.workspace, path + ); + let mut query = Vec::with_capacity(1usize); + if let Some(v) = &job_id { + query.push(("job_id", v.to_string())); + } + let response = self.get(&url, query).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding interpolated resource value as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn get_completed_job_result( + &self, + path: &str, + json_path: Option, + ) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/jobs_u/completed/get_result/{}", + self.base_internal_url, self.workspace, path + ); + let query = if let Some(json_path) = json_path { + vec![("json_path", json_path)] + } else { + vec![] + }; + let response = self.get(&url, query).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding completed job result as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn get_result_by_id( + &self, + flow_job_id: &str, + node_id: &str, + json_path: Option, + ) -> anyhow::Result { + let url = format!( + "{}/api/w/{}/jobs/result_by_id/{}/{}", + self.base_internal_url, self.workspace, flow_job_id, node_id + ); + let query = if let Some(json_path) = json_path { + vec![("json_path", json_path)] + } else { + vec![] + }; + let response = self.get(&url, query).await?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding result by id as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), + } + } + + pub async fn upload_s3_file( + &self, + workspace_id: &str, + object_key: String, + storage: Option, + body: S, + ) -> anyhow::Result<()> + where + S: futures::stream::TryStream + Send + 'static, + S::Error: Into>, + bytes::Bytes: From, + { + let mut query = vec![("file_key", object_key)]; + if let Some(storage) = storage { + query.push(("storage", storage)); + } + let response = self + .force_client + .as_ref() + .unwrap_or(&HTTP_CLIENT) + .post(format!( + "{}/api/w/{}/job_helpers/upload_s3_file", + self.base_internal_url, workspace_id + )) + .query(&query) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::AUTHORIZATION, + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token)) + .map_err(|e| anyhow::anyhow!(e.to_string()))?, + ) + .body(Body::wrap_stream(body)) + .send() + .await + .context(format!("Sent upload_s3_file request",)) + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + + match response.status().as_u16() { + 200u16 => Ok(()), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default()))?, + } + } + + pub async fn get_duckdb_connection_settings( + &self, + s3: &DuckdbConnectionSettingsQueryV2, + ) -> error::Result { + let url = format!( + "{}/api/w/{}/job_helpers/v2/duckdb_connection_settings", + self.base_internal_url, &self.workspace + ); + let response = self + .force_client + .as_ref() + .unwrap_or(&HTTP_CLIENT) + .post(url) + .header( + reqwest::header::CONTENT_TYPE, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .header( + reqwest::header::AUTHORIZATION, + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token)) + .map_err(|e| error::Error::BadConfig(e.to_string()))?, + ) + .body(serde_json::to_string(&s3).map_err(to_anyhow)?) + .send() + .await + .context(format!("Sent get_duckdb_connection_settings request",)) + .map_err(error::Error::from)?; + match response.status().as_u16() { + 200u16 => Ok(response + .json::() + .await + .context("decoding duckdb_connection_settings response as json")?), + _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default()))?, + } + } +} diff --git a/backend/windmill-common/src/db.rs b/backend/windmill-common/src/db.rs index 96be430e73..580e14f9d9 100644 --- a/backend/windmill-common/src/db.rs +++ b/backend/windmill-common/src/db.rs @@ -72,12 +72,6 @@ impl UserDB { where T: Authable, { - let user = if authed.is_admin() { - "windmill_admin" - } else { - "windmill_user" - }; - let (folders_write, folders_read): &(Vec<_>, Vec<_>) = &authed.folders().into_iter().partition(|x| x.1); @@ -95,10 +89,6 @@ impl UserDB { let mut tx = self.db.begin().await?; - sqlx::query(&format!("SET LOCAL ROLE {}", user)) - .execute(&mut *tx) - .await?; - if let Some(schema) = PG_SCHEMA.as_ref() { sqlx::query(&format!("SET LOCAL search_path TO {}", schema)) .execute(&mut *tx) @@ -106,51 +96,28 @@ impl UserDB { } sqlx::query!( - "SELECT set_config('session.user', $1, true)", - authed.username() - ) - .fetch_optional(&mut *tx) - .await?; - - sqlx::query!( - "SELECT set_config('session.groups', $1, true)", - &authed.groups().join(",") - ) - .fetch_optional(&mut *tx) - .await?; - - sqlx::query!( - "SELECT set_config('session.pgroups', $1, true)", - &authed + "SELECT set_session_context($1, $2, $3, $4, $5, $6)", + authed.is_admin(), + authed.username(), + authed.groups().join(","), + authed .groups() .iter() .map(|x| format!("g/{}", x)) .collect::>() - .join(",") - ) - .fetch_optional(&mut *tx) - .await?; - - sqlx::query!( - "SELECT set_config('session.folders_read', $1, true)", + .join(","), folders_read .iter() .map(|x| x.0.clone()) .collect::>() - .join(",") - ) - .fetch_optional(&mut *tx) - .await?; - - sqlx::query!( - "SELECT set_config('session.folders_write', $1, true)", + .join(","), folders_write .iter() .map(|x| x.0.clone()) .collect::>() .join(",") ) - .fetch_optional(&mut *tx) + .execute(&mut *tx) .await?; Ok(tx) diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee_oss.rs similarity index 62% rename from backend/windmill-common/src/ee.rs rename to backend/windmill-common/src/ee_oss.rs index 2a820475f7..51b1efd2e2 100644 --- a/backend/windmill-common/src/ee.rs +++ b/backend/windmill-common/src/ee_oss.rs @@ -1,24 +1,35 @@ -#[cfg(feature = "enterprise")] +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::ee::*; + +#[cfg(all(feature = "enterprise", not(feature = "private")))] use crate::db::DB; -use crate::ee::LicensePlan::Community; -#[cfg(feature = "enterprise")] +#[cfg(not(feature = "private"))] +use crate::ee_oss::LicensePlan::Community; +#[cfg(all(feature = "enterprise", not(feature = "private")))] use crate::error; +#[cfg(not(feature = "private"))] use serde::Deserialize; +#[cfg(not(feature = "private"))] use std::sync::Arc; +#[cfg(not(feature = "private"))] use tokio::sync::RwLock; +#[cfg(not(feature = "private"))] lazy_static::lazy_static! { pub static ref LICENSE_KEY_VALID: Arc> = Arc::new(RwLock::new(true)); pub static ref LICENSE_KEY_ID: Arc> = Arc::new(RwLock::new("".to_string())); pub static ref LICENSE_KEY: Arc> = Arc::new(RwLock::new("".to_string())); } +#[cfg(not(feature = "private"))] pub enum LicensePlan { Community, Pro, Enterprise, } +#[cfg(not(feature = "private"))] pub async fn get_license_plan() -> LicensePlan { // Implementation is not open source return Community; @@ -26,6 +37,7 @@ pub async fn get_license_plan() -> LicensePlan { #[derive(Deserialize)] #[serde(untagged)] +#[cfg(not(feature = "private"))] pub enum CriticalErrorChannel { Email { email: String }, Slack { slack_channel: String }, @@ -33,6 +45,7 @@ pub enum CriticalErrorChannel { } #[derive(Deserialize)] +#[cfg(not(feature = "private"))] pub struct TeamsChannel { pub team_id: String, pub team_name: String, @@ -40,6 +53,7 @@ pub struct TeamsChannel { pub channel_name: String, } +#[cfg(not(feature = "private"))] pub enum CriticalAlertKind { #[cfg(feature = "enterprise")] CriticalError, @@ -47,7 +61,7 @@ pub enum CriticalAlertKind { RecoveredCriticalError, } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn send_critical_alert( _error_message: String, _db: &DB, @@ -56,7 +70,7 @@ pub async fn send_critical_alert( ) { } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn maybe_renew_license_key_on_start( _http_client: &reqwest::Client, _db: &crate::db::DB, @@ -66,14 +80,14 @@ pub async fn maybe_renew_license_key_on_start( force_renew_now } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub enum RenewReason { Manual, Schedule, OnStart, } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn renew_license_key( _http_client: &reqwest::Client, _db: &crate::db::DB, @@ -84,7 +98,7 @@ pub async fn renew_license_key( "".to_string() } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn create_customer_portal_session( _http_client: &reqwest::Client, _key: Option, @@ -93,8 +107,18 @@ pub async fn create_customer_portal_session( Ok("".to_string()) } -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn worker_groups_alerts(_db: &DB) {} -#[cfg(feature = "enterprise")] +#[cfg(all(feature = "enterprise", not(feature = "private")))] pub async fn jobs_waiting_alerts(_db: &DB) {} + +#[cfg(all(feature = "enterprise", not(feature = "private")))] +pub async fn low_disk_alerts( + _db: &DB, + _server_mode: bool, + _worker_mode: bool, + _workers: Vec, +) { + // Implementation is not open source +} diff --git a/backend/windmill-common/src/email_ee.rs b/backend/windmill-common/src/email_oss.rs similarity index 61% rename from backend/windmill-common/src/email_ee.rs rename to backend/windmill-common/src/email_oss.rs index 42aebbeec3..e6a340523e 100644 --- a/backend/windmill-common/src/email_ee.rs +++ b/backend/windmill-common/src/email_oss.rs @@ -1,5 +1,11 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::email_ee::*; + +#[cfg(not(feature = "private"))] use crate::server::Smtp; +#[cfg(not(feature = "private"))] pub async fn send_email( _subject: &str, _content: &str, diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index faf1a72d1b..a6d43a5fc4 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -22,6 +22,8 @@ pub type JsonResult = std::result::Result, Error>; #[derive(Debug, Error)] pub enum Error { + #[error("Bad gateway: {0}")] + BadGateway(String), #[error("Bad config: {0}")] BadConfig(String), #[error("Connecting to database: {0}")] @@ -64,16 +66,22 @@ pub enum Error { DatabaseMigration(#[from] MigrateError), #[error("Non-zero exit status for {0}: {1}")] ExitStatus(String, i32), + #[error("ExecutionRawError: {0}")] + ExecutionRawError(Box), #[error("Error: {error:#} @{location:#}")] Anyhow { error: anyhow::Error, location: String }, #[error("Error: {0:#?}")] JsonErr(serde_json::Value), #[error("{0}")] - AiError(String), + AIError(String), #[error("{0}")] AlreadyCompleted(String), #[error("Find python error: {0}")] FindPythonError(String), + #[error("Problem with arguments: {0}")] + ArgumentErr(String), + #[error("{1}")] + Generic(StatusCode, String), } fn prettify_location(location: &'static Location<'static>) -> String { @@ -171,26 +179,29 @@ pub fn to_anyhow(e: T) -> anyhow:: impl IntoResponse for Error { fn into_response(self) -> axum::response::Response { - let e = &self; - let body = Body::from(e.to_string()); - let status = match self { Self::NotFound(_) => axum::http::StatusCode::NOT_FOUND, Self::NotAuthorized(_) => axum::http::StatusCode::UNAUTHORIZED, Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN, Self::SqlErr { .. } | Self::BadRequest(_) - | Self::AiError(_) + | Self::AIError(_) | Self::QuotaExceeded(_) => axum::http::StatusCode::BAD_REQUEST, + Self::BadGateway(_) => axum::http::StatusCode::BAD_GATEWAY, + Self::Generic(status_code, _) => status_code, _ => axum::http::StatusCode::INTERNAL_SERVER_ERROR, }; + let e = &self; + if matches!(status, axum::http::StatusCode::NOT_FOUND) { tracing::warn!(message = e.to_string()); } else { tracing::error!(message = e.to_string(), error = ?e); }; + let body = Body::from(e.to_string()); + axum::response::Response::builder() .header("Content-Type", "text/plain") .status(status) diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index ab0e714968..443461af64 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -22,7 +22,8 @@ use crate::{ error::Error, more_serde::{default_empty_string, default_id, default_null, default_true, is_default}, scripts::{Schema, ScriptHash, ScriptLang}, - worker::to_raw_value, + worker::{to_raw_value, Connection}, + DB, }; #[derive(Serialize, Deserialize, sqlx::FromRow)] @@ -135,10 +136,11 @@ pub struct FlowValue { pub concurrency_key: Option, } -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Default, Deserialize, Serialize, Debug, Clone)] pub struct StopAfterIf { pub expr: String, pub skip_if_stopped: bool, + pub error_message: Option, } #[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq)] @@ -731,7 +733,7 @@ pub async fn resolve_maybe_value( } /// Resolve modules recursively. -pub async fn resolve_value( +async fn resolve_value( e: &sqlx::PgPool, workspace_id: &str, value: &mut Box, @@ -749,7 +751,7 @@ pub async fn resolve_value( /// Resolve module value recursively. pub async fn resolve_module( - e: &sqlx::PgPool, + db: &DB, workspace_id: &str, value: &mut Box, with_code: bool, @@ -783,7 +785,7 @@ pub async fn resolve_module( let (lock, content) = if !with_code { (Some("...".to_string()), "...".to_string()) } else { - cache::flow::fetch_script(e, id) + cache::flow::fetch_script(&Connection::Sql(db.clone()), id) .await .map(|data| (data.lock.clone(), data.code.clone()))? }; @@ -801,13 +803,13 @@ pub async fn resolve_module( }; } ForloopFlow { modules, modules_node, .. } | WhileloopFlow { modules, modules_node, .. } => { - resolve_modules(e, workspace_id, modules, modules_node.take(), with_code).await?; + resolve_modules(db, workspace_id, modules, modules_node.take(), with_code).await?; } BranchOne { branches, default, default_node } => { - resolve_modules(e, workspace_id, default, default_node.take(), with_code).await?; + resolve_modules(db, workspace_id, default, default_node.take(), with_code).await?; for branch in branches { resolve_modules( - e, + db, workspace_id, &mut branch.modules, branch.modules_node.take(), @@ -819,7 +821,7 @@ pub async fn resolve_module( BranchAll { branches, .. } => { for branch in branches { resolve_modules( - e, + db, workspace_id, &mut branch.modules, branch.modules_node.take(), diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 816a99486e..895384e855 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -11,6 +11,8 @@ pub const LICENSE_KEY_SETTING: &str = "license_key"; pub const NPM_CONFIG_REGISTRY_SETTING: &str = "npm_config_registry"; pub const BUNFIG_INSTALL_SCOPES_SETTING: &str = "bunfig_install_scopes"; pub const NUGET_CONFIG_SETTING: &str = "nuget_config"; +pub const MAVEN_REPOS_SETTING: &str = "maven_repos"; +pub const NO_DEFAULT_MAVEN_SETTING: &str = "no_default_maven"; pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url"; pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url"; @@ -28,19 +30,20 @@ pub const EXPOSE_METRICS_SETTING: &str = "expose_metrics"; pub const EXPOSE_DEBUG_METRICS_SETTING: &str = "expose_debug_metrics"; pub const KEEP_JOB_DIR_SETTING: &str = "keep_job_dir"; pub const REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING: &str = "require_preexisting_user_for_oauth"; -pub const OBJECT_STORE_CACHE_CONFIG_SETTING: &str = "object_store_cache_config"; +pub const OBJECT_STORE_CONFIG_SETTING: &str = "object_store_cache_config"; pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation"; pub const HUB_BASE_URL_SETTING: &str = "hub_base_url"; pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url"; pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels"; pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui"; +pub const CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING: &str = "critical_alerts_on_db_oversize"; pub const DEV_INSTANCE_SETTING: &str = "dev_instance"; pub const JWT_SECRET_SETTING: &str = "jwt_secret"; pub const EMAIL_DOMAIN_SETTING: &str = "email_domain"; pub const OTEL_SETTING: &str = "otel"; -pub const ENV_SETTINGS: [&str; 57] = [ +pub const ENV_SETTINGS: &[&str] = &[ "DISABLE_NSJAIL", "MODE", "NUM_WORKERS", @@ -58,8 +61,11 @@ pub const ENV_SETTINGS: [&str; 57] = [ "S3_CACHE_BUCKET", "COOKIE_DOMAIN", "PYTHON_PATH", + "NU_PATH", "DENO_PATH", "GO_PATH", + "JAVA_PATH", + // for related places search: ADD_NEW_LANG "GOPRIVATE", "GOPROXY", "NETRC", @@ -98,6 +104,7 @@ pub const ENV_SETTINGS: [&str; 57] = [ "OTEL_LOGS", "DISABLE_S3_STORE", "PG_SCHEMA", + "PG_LISTENER_REFRESH_PERIOD_SECS", ]; use crate::error; diff --git a/backend/windmill-common/src/job_metrics.rs b/backend/windmill-common/src/job_metrics.rs index 6577e34d6e..d416e4d44b 100644 --- a/backend/windmill-common/src/job_metrics.rs +++ b/backend/windmill-common/src/job_metrics.rs @@ -49,10 +49,7 @@ pub async fn register_metric_for_job( .await? .flatten(); if exists.unwrap_or(false) { - return Err(error::Error::BadRequest(format!( - "Metric {} is already registered for job {}", - metric_id, job_id - ))); + return Ok(metric_id); } let (scalar_int, scalar_float, timestamps, timeseries_int, timeseries_float) = match metric_kind diff --git a/backend/windmill-common/src/job_s3_helpers_ee.rs b/backend/windmill-common/src/job_s3_helpers_ee.rs deleted file mode 100644 index b00c00a583..0000000000 --- a/backend/windmill-common/src/job_s3_helpers_ee.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::future::Future; - -use crate::{ - error::Error, - s3_helpers::{ObjectStoreResource, StorageResourceType}, -}; - -pub async fn get_s3_resource_internal<'c, F, Fut>( - _resource_type: StorageResourceType, - _s3_resource_value_raw: serde_json::Value, - _gen_token: F, -) -> crate::error::Result -where - F: FnOnce(String) -> Fut, - Fut: Future> + Send + 'static, -{ - todo!() -} diff --git a/backend/windmill-common/src/job_s3_helpers_oss.rs b/backend/windmill-common/src/job_s3_helpers_oss.rs new file mode 100644 index 0000000000..b7ebd57f3e --- /dev/null +++ b/backend/windmill-common/src/job_s3_helpers_oss.rs @@ -0,0 +1,42 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::job_s3_helpers_ee::*; + +#[cfg(not(feature = "private"))] +use crate::s3_helpers::{ObjectStoreResource, StorageResourceType}; + +#[cfg(not(feature = "private"))] +pub async fn get_s3_resource_internal<'c>( + _resource_type: StorageResourceType, + _s3_resource_value_raw: serde_json::Value, + _gen_token: TokenGenerator<'c>, + _db: &crate::DB, +) -> crate::error::Result { + todo!() +} + +#[cfg(not(feature = "private"))] +pub enum TokenGenerator<'c> { + AsClient(&'c crate::client::AuthedClient), + AsServerInstance(), +} + +#[cfg(not(feature = "private"))] +impl<'c> TokenGenerator<'c> { + pub async fn gen_token( + &self, + _audience: &str, + _db: Option<&crate::DB>, + ) -> anyhow::Result { + todo!() + } +} + +#[cfg(all(feature = "parquet", not(feature = "private")))] +pub(crate) async fn generate_s3_aws_oidc_resource<'c>( + _clone: crate::s3_helpers::S3AwsOidcResource, + _token_generator: TokenGenerator<'c>, + _init_private_key: Option<&sqlx::Pool>, +) -> crate::error::Result { + todo!() +} diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index e6eb5a6728..eb6fc5ce8f 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -5,26 +5,29 @@ use futures_core::Stream; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; -use sqlx::{types::Json, Pool, Postgres, Transaction}; +use sqlx::{types::Json, Pool, Postgres}; use tokio::io::AsyncReadExt; use uuid::Uuid; pub const ENTRYPOINT_OVERRIDE: &str = "_ENTRYPOINT_OVERRIDE"; +pub const LARGE_LOG_THRESHOLD_SIZE: usize = 9000; use crate::{ apps::AppScriptId, error::{self, to_anyhow, Error}, flow_status::{FlowStatus, RestartedFrom}, flows::{FlowNodeId, FlowValue, Retry}, - get_latest_deployed_hash_for_path, - scripts::{ScriptHash, ScriptLang}, + get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, + scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, users::username_to_permissioned_as, + utils::{StripPath, HTTP_CLIENT}, worker::{to_raw_value, TMP_DIR}, + FlowVersionInfo, ScriptHashInfo, }; #[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] #[sqlx(type_name = "JOB_KIND", rename_all = "lowercase")] -#[serde(rename_all(serialize = "lowercase"))] +#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))] pub enum JobKind { Script, #[allow(non_camel_case_types)] @@ -51,6 +54,13 @@ impl JobKind { JobKind::Flow | JobKind::FlowPreview | JobKind::SingleScriptFlow | JobKind::FlowNode ) } + + pub fn is_dependency(&self) -> bool { + matches!( + self, + JobKind::FlowDependencies | JobKind::AppDependencies | JobKind::Dependencies + ) + } } #[derive(sqlx::FromRow, Debug, Serialize, Clone)] @@ -261,6 +271,7 @@ impl CompletedJob { pub enum JobPayload { ScriptHub { path: String, + apply_preprocessor: bool, }, ScriptHash { hash: ScriptHash, @@ -323,6 +334,7 @@ pub enum JobPayload { path: String, dedicated_worker: Option, apply_preprocessor: bool, + version: i64, }, RestartedFlow { completed_job_id: Uuid, @@ -377,9 +389,28 @@ pub struct OnBehalfOf { pub permissioned_as: String, } -pub async fn script_path_to_payload<'e, E: sqlx::Executor<'e, Database = Postgres>>( +pub fn get_has_preprocessor_from_content_and_lang( + content: &str, + language: &ScriptLang, +) -> error::Result { + let has_preprocessor = match language { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature(&content, true, true, None)?; + args.has_preprocessor.unwrap_or(false) + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature(&content, None, true)?; + args.has_preprocessor.unwrap_or(false) + } + _ => false, + }; + + Ok(has_preprocessor) +} + +pub async fn script_path_to_payload<'e, A: sqlx::Acquire<'e, Database = Postgres> + Send>( script_path: &str, - db: E, + db: A, w_id: &str, skip_preprocessor: Option, ) -> error::Result<( @@ -389,62 +420,74 @@ pub async fn script_path_to_payload<'e, E: sqlx::Executor<'e, Database = Postgre Option, Option, )> { - let (job_payload, tag, delete_after_use, script_timeout, on_behalf_of) = - if script_path.starts_with("hub/") { - ( - JobPayload::ScriptHub { path: script_path.to_owned() }, - None, - None, - None, - None, - ) + let (job_payload, tag, delete_after_use, script_timeout, on_behalf_of) = if script_path + .starts_with("hub/") + { + let hub_script = + get_full_hub_script_by_path(StripPath(script_path.to_string()), &HTTP_CLIENT, None) + .await?; + + let has_preprocessor = + get_has_preprocessor_from_content_and_lang(&hub_script.content, &hub_script.language)?; + + ( + JobPayload::ScriptHub { + path: script_path.to_owned(), + apply_preprocessor: has_preprocessor && !skip_preprocessor.unwrap_or(false), + }, + None, + None, + None, + None, + ) + } else { + let ScriptHashInfo { + hash, + tag, + concurrency_key, + concurrent_limit, + concurrency_time_window_s, + cache_ttl, + language, + dedicated_worker, + priority, + delete_after_use, + timeout, + has_preprocessor, + on_behalf_of_email, + created_by, + .. + } = get_latest_deployed_hash_for_path(db, w_id, script_path).await?; + + let on_behalf_of = if let Some(email) = on_behalf_of_email { + Some(OnBehalfOf { + email, + permissioned_as: username_to_permissioned_as(created_by.as_str()), + }) } else { - let ( - script_hash, - tag, - custom_concurrency_key, + None + }; + + ( + JobPayload::ScriptHash { + hash: ScriptHash(hash), + path: script_path.to_owned(), + custom_concurrency_key: concurrency_key, concurrent_limit, concurrency_time_window_s, - cache_ttl, + cache_ttl: cache_ttl, language, dedicated_worker, priority, - delete_after_use, - script_timeout, - has_preprocessor, - on_behalf_of_email, - created_by, - ) = get_latest_deployed_hash_for_path(db, w_id, script_path).await?; - - let on_behalf_of = if let Some(email) = on_behalf_of_email { - Some(OnBehalfOf { - email, - permissioned_as: username_to_permissioned_as(created_by.as_str()), - }) - } else { - None - }; - - ( - JobPayload::ScriptHash { - hash: script_hash, - path: script_path.to_owned(), - custom_concurrency_key, - concurrent_limit, - concurrency_time_window_s, - cache_ttl: cache_ttl, - language, - dedicated_worker, - priority, - apply_preprocessor: !skip_preprocessor.unwrap_or(false) - && has_preprocessor.unwrap_or(false), - }, - tag, - delete_after_use, - script_timeout, - on_behalf_of, - ) - }; + apply_preprocessor: !skip_preprocessor.unwrap_or(false) + && has_preprocessor.unwrap_or(false), + }, + tag, + delete_after_use, + timeout, + on_behalf_of, + ) + }; Ok(( job_payload, tag, @@ -454,52 +497,6 @@ pub async fn script_path_to_payload<'e, E: sqlx::Executor<'e, Database = Postgre )) } -pub async fn script_hash_to_tag_and_limits<'c>( - script_hash: &ScriptHash, - db: &mut Transaction<'c, Postgres>, - w_id: &String, -) -> error::Result<( - Option, - Option, - Option, - Option, - Option, - ScriptLang, - Option, - Option, - Option, - Option, - Option, - String, -)> { - let script = sqlx::query!( - "select tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, on_behalf_of_email, created_by from script where hash = $1 AND workspace_id = $2", - script_hash.0, - w_id - ) - .fetch_one(&mut **db) - .await - .map_err(|e| { - Error::internal_err(format!( - "querying getting tag for hash {script_hash}: {e:#}" - )) - })?; - Ok(( - script.tag, - script.concurrency_key, - script.concurrent_limit, - script.concurrency_time_window_s, - script.cache_ttl, - script.language, - script.dedicated_worker, - script.priority, - script.delete_after_use, - script.timeout, - script.on_behalf_of_email, - script.created_by, - )) -} - pub async fn get_payload_tag_from_prefixed_path( path: &str, db: &DB, @@ -509,18 +506,10 @@ pub async fn get_payload_tag_from_prefixed_path( script_path_to_payload(path.strip_prefix("script/").unwrap(), db, w_id, Some(true)).await? } else if path.starts_with("flow/") { let path = path.strip_prefix("flow/").unwrap().to_string(); - let r = sqlx::query!( - "SELECT tag, dedicated_worker from flow WHERE path = $1 and workspace_id = $2", - &path, - &w_id, - ) - .fetch_optional(db) - .await?; - let (tag, dedicated_worker) = r - .map(|x| (x.tag, x.dedicated_worker)) - .unwrap_or_else(|| (None, None)); + let FlowVersionInfo { dedicated_worker, tag, version, .. } = + get_latest_flow_version_info_for_path(db, w_id, &path, true).await?; ( - JobPayload::Flow { path, dedicated_worker, apply_preprocessor: false }, + JobPayload::Flow { path, dedicated_worker, apply_preprocessor: false, version }, tag, None, None, @@ -619,11 +608,11 @@ pub async fn get_logs_from_store( logs: &str, log_file_index: &Option>, ) -> Option>> { - use crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS; + use crate::s3_helpers::get_object_store; if log_offset > 0 { if let Some(file_index) = log_file_index.clone() { - if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { + if let Some(os) = get_object_store().await { let logs = logs.to_string(); let stream = async_stream::stream! { for file_p in file_index.clone() { diff --git a/backend/windmill-common/src/jwt.rs b/backend/windmill-common/src/jwt.rs index be93ed6614..8ebdb8dacb 100644 --- a/backend/windmill-common/src/jwt.rs +++ b/backend/windmill-common/src/jwt.rs @@ -1,6 +1,6 @@ use crate::error::{self, to_anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use tokio::sync::RwLock; lazy_static::lazy_static! { @@ -40,3 +40,20 @@ pub async fn decode_with_internal_secret(token: &str) -> er Ok(result.claims) } + +pub fn decode_without_verify(token: &str) -> anyhow::Result { + // Create a validation that skips all checks + let mut validation = jsonwebtoken::Validation::default(); + validation.insecure_disable_signature_validation(); + validation.validate_exp = false; + validation.validate_nbf = false; + validation.required_spec_claims = HashSet::new(); + + // Use an empty key since we're not verifying + let key = jsonwebtoken::DecodingKey::from_secret(&[]); + + // Decode the token + let token_data = jsonwebtoken::decode::(token, &key, &validation)?; + + Ok(token_data.claims) +} diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 9eeeec3cdf..498beecd6c 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -6,23 +6,38 @@ * LICENSE-AGPL for a copy of the license. */ +use quick_cache::sync::Cache; use std::{ + future::Future, net::SocketAddr, str::FromStr, - sync::{atomic::AtomicBool, Arc}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; -use ee::CriticalErrorChannel; +use tokio::sync::broadcast; + +use ee_oss::CriticalErrorChannel; use error::Error; use scripts::ScriptLang; use sqlx::{Pool, Postgres}; +pub mod agent_workers; pub mod apps; pub mod auth; +#[cfg(feature = "benchmark")] +pub mod bench; pub mod cache; +pub mod client; pub mod db; +#[cfg(feature = "private")] pub mod ee; +pub mod ee_oss; +#[cfg(feature = "private")] pub mod email_ee; +pub mod email_oss; pub mod error; pub mod external_ip; pub mod flow_status; @@ -30,26 +45,42 @@ pub mod flows; pub mod global_settings; pub mod indexer; pub mod job_metrics; -#[cfg(feature = "parquet")] +#[cfg(all(feature = "parquet", feature = "private"))] pub mod job_s3_helpers_ee; +#[cfg(feature = "parquet")] +pub mod job_s3_helpers_oss; + +#[cfg(all(feature = "enterprise", feature = "openidconnect", feature = "private"))] +pub mod oidc_ee; +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +pub mod oidc_oss; + pub mod jobs; pub mod jwt; pub mod more_serde; pub mod oauth2; +#[cfg(feature = "private")] pub mod otel_ee; +pub mod otel_oss; pub mod queue; pub mod s3_helpers; pub mod schedule; +pub mod schema; pub mod scripts; pub mod server; +#[cfg(feature = "private")] pub mod stats_ee; +pub mod stats_oss; +#[cfg(feature = "private")] pub mod teams_ee; +pub mod teams_oss; pub mod tracing_init; pub mod users; pub mod utils; pub mod variables; pub mod worker; pub mod workspaces; +pub mod triggers; pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50; pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5; @@ -105,6 +136,7 @@ lazy_static::lazy_static! { pub static ref CRITICAL_ERROR_CHANNELS: Arc>> = Arc::new(RwLock::new(vec![])); + pub static ref CRITICAL_ALERTS_ON_DB_OVERSIZE: Arc>> = Arc::new(RwLock::new(None)); pub static ref JOB_RETENTION_SECS: Arc> = Arc::new(RwLock::new(0)); @@ -112,10 +144,19 @@ lazy_static::lazy_static! { pub static ref INSTANCE_NAME: String = rd_string(5); + pub static ref DEPLOYED_SCRIPT_HASH_CACHE: Cache<(String, String), ExpiringLatestVersionId> = Cache::new(1000); + pub static ref FLOW_VERSION_CACHE: Cache<(String, String), ExpiringLatestVersionId> = Cache::new(1000); + pub static ref DEPLOYED_SCRIPT_INFO_CACHE: Cache<(String, i64), ScriptHashInfo> = Cache::new(1000); + pub static ref FLOW_INFO_CACHE: Cache<(String, i64), FlowVersionInfo> = Cache::new(1000); + + pub static ref QUIET_LOGS: bool = std::env::var("QUIET_LOGS").map(|s| s.parse::().unwrap_or(false)).unwrap_or(false); + } +const LATEST_VERSION_ID_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60); + pub async fn shutdown_signal( - tx: tokio::sync::broadcast::Sender<()>, + tx: KillpillSender, mut rx: tokio::sync::broadcast::Receiver<()>, ) -> anyhow::Result<()> { use std::io; @@ -151,7 +192,7 @@ pub async fn shutdown_signal( } tracing::info!("signal received, starting graceful shutdown"); - let _ = tx.send(()); + let _ = tx.send(); Ok(()) } @@ -325,54 +366,207 @@ type Tag = String; pub type DB = Pool; -pub async fn get_latest_deployed_hash_for_path<'e, E: sqlx::Executor<'e, Database = Postgres>>( +#[derive(Clone)] +pub struct ExpiringLatestVersionId { + id: i64, + expires_at: std::time::Instant, +} + +#[derive(Clone)] +pub struct ScriptHashInfo { + pub path: String, + pub hash: i64, + pub tag: Option, + pub concurrency_key: Option, + pub concurrent_limit: Option, + pub concurrency_time_window_s: Option, + pub cache_ttl: Option, + pub language: ScriptLang, + pub dedicated_worker: Option, + pub priority: Option, + pub delete_after_use: Option, + pub timeout: Option, + pub has_preprocessor: Option, + pub on_behalf_of_email: Option, + pub created_by: String, +} + +pub fn get_latest_deployed_hash_for_path< + 'a, + 'e, + E: sqlx::Acquire<'e, Database = Postgres> + Send + 'a, +>( + db: E, + w_id: &'a str, + script_path: &'a str, +) -> impl Future> + Send + 'a { + async move { + let mut conn = db.acquire().await?; + let cache_key = (w_id.to_string(), script_path.to_string()); + + let hash = match DEPLOYED_SCRIPT_HASH_CACHE.get(&cache_key) { + Some(cached_hash) if cached_hash.expires_at > std::time::Instant::now() => { + tracing::debug!( + "Using cached script hash {} for {script_path}", + cached_hash.id + ); + cached_hash.id + } + _ => { + tracing::debug!("Fetching script hash for {script_path}"); + let hash = sqlx::query_scalar!( + "select hash from script where path = $1 AND workspace_id = $2 AND deleted = false AND lock IS not NULL AND lock_error_logs IS NULL ORDER BY created_at DESC LIMIT 1", + script_path, + w_id + ) + .fetch_optional(&mut *conn) + .await?; + + let hash = utils::not_found_if_none(hash, "script", script_path)?; + + DEPLOYED_SCRIPT_HASH_CACHE.insert( + cache_key, + ExpiringLatestVersionId { + id: hash, + expires_at: std::time::Instant::now() + LATEST_VERSION_ID_CACHE_TTL, + }, + ); + + hash + } + }; + + get_script_info_for_hash(&mut *conn, w_id, hash).await + } +} + +pub async fn get_script_info_for_hash<'e, E: sqlx::PgExecutor<'e>>( db: E, w_id: &str, - script_path: &str, -) -> error::Result<( - scripts::ScriptHash, - Option, - Option, - Option, - Option, - Option, - ScriptLang, - Option, - Option, - Option, - Option, - Option, - Option, - String, -)> { - let r_o = sqlx::query!( - "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by from script where path = $1 AND workspace_id = $2 AND - created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND - deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)", - script_path, - w_id - ) - .fetch_optional(db) - .await?; + hash: i64, +) -> error::Result { + let key = (w_id.to_string(), hash); - let script = utils::not_found_if_none(r_o, "deployed script", script_path)?; + match DEPLOYED_SCRIPT_INFO_CACHE.get(&key) { + Some(info) => { + tracing::debug!("Using cached deployed script info for {hash}"); + Ok(info) + } + _ => { + tracing::debug!("Fetching deployed script info for {hash}"); + let info = sqlx::query_as!( + ScriptHashInfo, + "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by, path from script where hash = $1 AND workspace_id = $2", + hash, + w_id + ) + .fetch_optional(db) + .await?; - Ok(( - scripts::ScriptHash(script.hash), - script.tag, - script.concurrency_key, - script.concurrent_limit, - script.concurrency_time_window_s, - script.cache_ttl, - script.language, - script.dedicated_worker, - script.priority, - script.delete_after_use, - script.timeout, - script.has_preprocessor, - script.on_behalf_of_email, - script.created_by, - )) + let info = utils::not_found_if_none(info, "script", &hash.to_string())?; + + DEPLOYED_SCRIPT_INFO_CACHE.insert(key, info.clone()); + + Ok(info) + } + } +} + +#[derive(Clone)] +pub struct FlowVersionInfo { + pub version: i64, + pub tag: Option, + pub early_return: Option, + pub has_preprocessor: Option, + pub on_behalf_of_email: Option, + pub edited_by: String, + pub dedicated_worker: Option, +} + +pub fn get_latest_flow_version_info_for_path< + 'a, + 'e, + A: sqlx::Acquire<'e, Database = Postgres> + Send + 'a, +>( + db: A, + w_id: &'a str, + path: &'a str, + use_cache: bool, +) -> impl Future> + Send + 'a { + // as instructed in the docstring of sqlx::Acquire + async move { + let mut conn = db.acquire().await?; + + let cache_key = (w_id.to_string(), path.to_string()); + let cached_version = if use_cache { + FLOW_VERSION_CACHE.get(&cache_key) + } else { + None + }; + + let version = match cached_version { + Some(cached_version) if cached_version.expires_at > std::time::Instant::now() => { + tracing::debug!("Using cached flow version {} for {path}", cached_version.id); + cached_version.id + } + _ => { + tracing::debug!("Fetching flow version for {path}"); + let version = sqlx::query_scalar!( + "SELECT flow_version.id from flow + INNER JOIN flow_version + ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] + WHERE flow.path = $1 and flow.workspace_id = $2", + path, + w_id + ) + .fetch_optional(&mut *conn) + .await?; + + let version = utils::not_found_if_none(version, "flow", path)?; + + FLOW_VERSION_CACHE.insert( + cache_key, + ExpiringLatestVersionId { + id: version, + expires_at: std::time::Instant::now() + LATEST_VERSION_ID_CACHE_TTL, + }, + ); + + version + } + }; + + let key = (w_id.to_string(), version); + + match FLOW_INFO_CACHE.get(&key) { + Some(info) => { + tracing::debug!("Using cached flow version info for {version} ({path})"); + Ok(info) + } + _ => { + tracing::debug!("Fetching flow version info for {version} ({path})"); + let info = sqlx::query_as!( + FlowVersionInfo, + "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by, flow_version.id AS version + FROM flow + INNER JOIN flow_version + ON flow_version.id = $3 + WHERE flow.path = $1 and flow.workspace_id = $2", + path, + w_id, + version + ) + .fetch_optional(&mut *conn) + .await?; + + let info = utils::not_found_if_none(info, "flow", path)?; + + FLOW_INFO_CACHE.insert(key, info.clone()); + + Ok(info) + } + } + } } pub async fn get_latest_hash_for_path<'c>( @@ -420,3 +614,56 @@ pub async fn get_latest_hash_for_path<'c>( script.created_by, )) } + +pub struct KillpillSender { + tx: broadcast::Sender<()>, + already_sent: Arc, +} + +impl Clone for KillpillSender { + fn clone(&self) -> Self { + KillpillSender { tx: self.tx.clone(), already_sent: self.already_sent.clone() } + } +} + +impl KillpillSender { + pub fn new(capacity: usize) -> (Self, broadcast::Receiver<()>) { + let (tx, rx) = broadcast::channel(capacity); + let sender = KillpillSender { tx, already_sent: Arc::new(AtomicBool::new(false)) }; + (sender, rx) + } + + pub fn clone(&self) -> Self { + KillpillSender { tx: self.tx.clone(), already_sent: self.already_sent.clone() } + } + + pub fn subscribe(&self) -> broadcast::Receiver<()> { + self.tx.subscribe() + } + + // Try to send the killpill if it hasn't been sent already + pub fn send(&self) -> bool { + // Check if it's already been sent, and if not, set the flag to true + if !self.already_sent.swap(true, Ordering::SeqCst) { + // We're the first to set it to true, so send the signal + if let Err(e) = self.tx.send(()) { + tracing::error!("failed to send killpill: {:?}", e); + } + true + } else { + // Signal was already sent + false + } + } + + // // Force send a signal regardless of previous sends + // fn force_send(&self) -> Result> { + // self.already_sent.store(true, Ordering::SeqCst); + // self.tx.send(()) + // } + + // // Check if the killpill has been sent + // fn is_sent(&self) -> bool { + // self.already_sent.load(Ordering::SeqCst) + // } +} diff --git a/backend/windmill-common/src/oidc_oss.rs b/backend/windmill-common/src/oidc_oss.rs new file mode 100644 index 0000000000..0a42cee2ed --- /dev/null +++ b/backend/windmill-common/src/oidc_oss.rs @@ -0,0 +1,92 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::oidc_ee::*; + +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2023 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +#[cfg(not(feature = "private"))] +use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "private"))] +use tokio::sync::RwLock; +#[cfg(all( + feature = "enterprise", + feature = "openidconnect", + not(feature = "private") +))] +use { + crate::db::DB, + crate::{ + auth::IdToken as WindmillIdToken, + error::{Error, Result}, + }, + anyhow, +}; + +#[cfg(all(feature = "openidconnect", not(feature = "private")))] +use openidconnect::AdditionalClaims; + +#[cfg(all(feature = "openidconnect", not(feature = "private")))] +impl AdditionalClaims for JobClaim {} + +#[cfg(all(feature = "openidconnect", not(feature = "private")))] +impl AdditionalClaims for WorkspaceClaim {} + +#[cfg(all(feature = "openidconnect", not(feature = "private")))] +impl AdditionalClaims for InstanceClaim {} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[cfg(not(feature = "private"))] +pub struct WorkspaceClaim { + pub workspace: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[cfg(not(feature = "private"))] +pub struct InstanceClaim {} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[cfg(not(feature = "private"))] +pub struct JobClaim { + pub job_id: String, + pub path: Option, + pub flow_path: Option, + pub groups: Vec, + pub username: String, + pub email: String, + pub workspace: String, +} + +#[cfg(not(feature = "private"))] +lazy_static::lazy_static! { + static ref PRIVATE_KEY: RwLock> = RwLock::new(None); +} + +#[cfg(not(feature = "private"))] +pub async fn generate_id_token( + _db: Option<&DB>, + _claim: T, + _audience: &str, + _identifier: String, + _email: Option, +) -> Result { + Err(Error::internal_err( + "Not implemented in Windmill's Open Source repository".to_string(), + )) +} + +#[cfg(all( + feature = "enterprise", + feature = "openidconnect", + not(feature = "private") +))] +pub async fn get_private_key(_db: Option<&DB>) -> anyhow::Result { + Err(anyhow::anyhow!( + "Not implemented in Windmill's Open Source repository" + )) +} diff --git a/backend/windmill-common/src/otel_ee.rs b/backend/windmill-common/src/otel_oss.rs similarity index 60% rename from backend/windmill-common/src/otel_ee.rs rename to backend/windmill-common/src/otel_oss.rs index f3ada162f6..9225915636 100644 --- a/backend/windmill-common/src/otel_ee.rs +++ b/backend/windmill-common/src/otel_oss.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::otel_ee::*; + /* * Author: Ruben Fiszel * Copyright: Windmill Labs, Inc 2022 @@ -6,43 +10,51 @@ * LICENSE-AGPL for a copy of the license. */ +#[cfg(not(feature = "private"))] use crate::{jobs::QueuedJob, utils::Mode}; +#[cfg(not(feature = "private"))] use uuid::Uuid; +#[cfg(not(feature = "private"))] pub fn set_span_parent(_span: &tracing::Span, _rj: &Uuid) {} -#[cfg(not(all(feature = "otel", feature = "enterprise")))] +#[cfg(all( + not(all(feature = "otel", feature = "enterprise")), + not(feature = "private") +))] pub(crate) type OtelProvider = Option<()>; -#[cfg(all(feature = "otel", feature = "enterprise"))] +#[cfg(all(feature = "otel", feature = "enterprise", not(feature = "private")))] pub(crate) type OtelProvider = Option; -#[cfg(not(feature = "otel"))] +#[cfg(all(not(feature = "otel"), not(feature = "private")))] pub fn otel_ctx() -> () {} -#[cfg(feature = "otel")] +#[cfg(all(feature = "otel", not(feature = "private")))] #[inline(always)] pub fn otel_ctx() -> opentelemetry::Context { opentelemetry::Context::current() } -#[cfg(not(feature = "otel"))] +#[cfg(all(not(feature = "otel"), not(feature = "private")))] impl FutureExt for T {} -#[cfg(not(feature = "otel"))] +#[cfg(all(not(feature = "otel"), not(feature = "private")))] pub trait FutureExt: Sized { fn with_context(self, _otel_cx: ()) -> Self { self } } +#[cfg(not(feature = "private"))] use tracing_subscriber::EnvFilter; +#[cfg(not(feature = "private"))] pub(crate) fn init_logs_bridge(_mode: &Mode, _hostname: &str, _env: &str) -> Option { None } -#[cfg(all(feature = "otel", feature = "enterprise"))] +#[cfg(all(feature = "otel", feature = "enterprise", not(feature = "private")))] pub(crate) fn init_otlp_tracer( _mode: &Mode, _hostname: &str, @@ -51,8 +63,10 @@ pub(crate) fn init_otlp_tracer( None } +#[cfg(not(feature = "private"))] pub(crate) fn init_meter_provider(_mode: &Mode, _hostname: &str, _env: &str) -> OtelProvider { None } +#[cfg(not(feature = "private"))] pub fn add_root_flow_job_to_otlp(_queued_job: &QueuedJob, _success: bool) {} diff --git a/backend/windmill-common/src/s3_helpers.rs b/backend/windmill-common/src/s3_helpers.rs index 01507f5b6a..171958f5cd 100644 --- a/backend/windmill-common/src/s3_helpers.rs +++ b/backend/windmill-common/src/s3_helpers.rs @@ -4,6 +4,7 @@ use crate::error; use aws_sdk_sts::config::ProvideCredentials; #[cfg(feature = "parquet")] use axum::async_trait; +use chrono::{DateTime, Utc}; #[cfg(feature = "parquet")] use object_store::aws::AwsCredential; #[cfg(feature = "parquet")] @@ -16,14 +17,201 @@ use object_store::{aws::AmazonS3Builder, ClientOptions}; use reqwest::header::HeaderMap; use serde::{Deserialize, Serialize}; #[cfg(feature = "parquet")] -use std::sync::Arc; +use std::sync::{Arc, Mutex}; + #[cfg(feature = "parquet")] use tokio::sync::RwLock; #[cfg(feature = "parquet")] -lazy_static::lazy_static! { +use crate::error::to_anyhow; +#[cfg(feature = "parquet")] +use crate::utils::rd_string; +#[cfg(feature = "parquet")] +use bytes::Bytes; +#[cfg(feature = "parquet")] +use datafusion::arrow::array::{RecordBatch, RecordBatchWriter}; +#[cfg(feature = "parquet")] +use datafusion::arrow::error::ArrowError; +#[cfg(feature = "parquet")] +use datafusion::arrow::json::writer::JsonArray; +#[cfg(feature = "parquet")] +use datafusion::arrow::{csv, json}; +#[cfg(feature = "parquet")] +use datafusion::parquet::arrow::ArrowWriter; +#[cfg(feature = "parquet")] +use futures::TryStreamExt; +#[cfg(feature = "parquet")] +use std::io::Write; +#[cfg(feature = "parquet")] +use tokio::task; +#[cfg(feature = "parquet")] +use windmill_parser_sql::S3ModeFormat; - pub static ref OBJECT_STORE_CACHE_SETTINGS: Arc>>> = Arc::new(RwLock::new(None)); +#[cfg(feature = "parquet")] +#[derive(Clone)] +pub struct ExpirableObjectStore { + pub store: Arc, + pub refresh: Option, +} + +#[cfg(feature = "parquet")] +#[derive(Clone)] +pub struct ObjectStoreRefresh { + refresh: Option>, + settings: ObjectSettings, +} + +#[cfg(feature = "parquet")] +impl ObjectStoreRefresh { + pub fn new(settings: ObjectSettings, refresh: Option>) -> Self { + Self { settings, refresh } + } + fn refresh_needed(&self) -> bool { + if let Some(refresh) = self.refresh { + if refresh < Utc::now() - chrono::Duration::minutes(1) { + return true; + } + } + return false; + } + + async fn refresh(&self) -> Option { + return build_object_store_from_settings(self.settings.clone(), None) + .await + .map_err(|e| { + tracing::error!("Error building s3 client from settings: {:?}", e); + e + }) + .ok(); + } +} + +#[cfg(feature = "parquet")] +impl From> for ExpirableObjectStore { + fn from(store: Arc) -> Self { + Self { store, refresh: None } + } +} + +// #[cfg(feature = "parquet")] + +// impl ExpirableObjectStore { +// pub fn new(store: Arc, expiration: Option>) -> Self { +// Self { store, expiration } +// } +// } + +#[cfg(feature = "parquet")] +lazy_static::lazy_static! { + pub static ref OBJECT_STORE_SETTINGS: Arc>> = Arc::new(RwLock::new(None)); +} + +#[cfg(feature = "parquet")] +pub async fn get_object_store() -> Option> { + let settings = OBJECT_STORE_SETTINGS.read().await; + if let Some(s) = settings.as_ref() { + match &s.refresh { + Some(refresh) => { + if refresh.refresh_needed() { + let refresh = refresh.clone(); + drop(settings); + let new_store = refresh.refresh().await; + if let Some(new_store) = new_store { + let mut s3_cache_settings = OBJECT_STORE_SETTINGS.write().await; + let arc = new_store.store.clone(); + *s3_cache_settings = Some(new_store); + return Some(arc); + } else { + return None; + } + } else { + return Some(s.store.clone()); + } + } + None => { + return Some(s.store.clone()); + } + } + } else { + return None; + } +} + +#[cfg(feature = "parquet")] +pub enum ObjectStoreReload { + //if the jwks endpoints are not up yet, we should retry later soon + Later, + Never, +} + +#[cfg(feature = "parquet")] +pub async fn reload_object_store_setting(db: &crate::DB) -> ObjectStoreReload { + use crate::{ + ee_oss::{get_license_plan, LicensePlan}, + global_settings::{load_value_from_global_settings, OBJECT_STORE_CONFIG_SETTING}, + s3_helpers::ObjectSettings, + }; + + let s3_config = load_value_from_global_settings(db, OBJECT_STORE_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 ObjectStoreReload::Never; + } + let setting = serde_json::from_value::(v); + match setting { + Ok(setting) => { + let is_oidc = matches!(setting, ObjectSettings::AwsOidc(_)); + let s3_client = build_object_store_from_settings(setting, Some(db)).await; + match s3_client { + Ok(s3_client) => { + let mut s3_cache_settings = OBJECT_STORE_SETTINGS.write().await; + *s3_cache_settings = Some(s3_client); + } + Err(e) => { + if is_oidc { + tracing::error!("Error building s3 client from oidc settings. It may be due to the jwks endpoints not being up yet, it will be attempted again in 10s to leave time for the server to be ready: {:?}", e); + return ObjectStoreReload::Later; + } else { + tracing::error!("Error building s3 client from settings: {:?}", e); + } + } + } + } + Err(e) => { + tracing::error!("Error parsing s3 cache config: {:?}", e) + } + } + } else { + let mut s3_cache_settings = OBJECT_STORE_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 ObjectStoreReload::Never; + } + *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() + .map(|x| ExpirableObjectStore::from(x)) + } else { + *s3_cache_settings = None; + } + } + } + return ObjectStoreReload::Never; } #[derive(Serialize, Deserialize, Debug)] @@ -56,6 +244,15 @@ pub enum ObjectStoreResource { Azure(AzureBlobResource), } +impl ObjectStoreResource { + pub fn expiration(&self) -> Option> { + match self { + ObjectStoreResource::S3(s3_resource) => s3_resource.expiration, + _ => None, + } + } +} + #[derive(Deserialize, Debug)] pub enum StorageResourceType { S3, @@ -79,6 +276,8 @@ pub struct S3Resource { #[serde(rename = "pathStyle")] pub path_style: Option, pub token: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expiration: Option>, pub port: Option, } @@ -101,7 +300,7 @@ pub struct AzureBlobResource { pub federated_token_file: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Hash)] pub struct S3AwsOidcResource { #[serde(rename = "bucket")] pub bucket: String, @@ -111,13 +310,15 @@ pub struct S3AwsOidcResource { pub audience: Option, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct S3Object { pub s3: String, #[serde(skip_serializing_if = "Option::is_none")] pub storage: Option, #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub presigned: Option, } #[cfg(feature = "parquet")] @@ -385,17 +586,47 @@ pub enum ObjectStoreSettings { pub enum ObjectSettings { S3(S3Settings), Azure(AzureBlobResource), + AwsOidc(S3AwsOidcResource), +} + +impl ObjectSettings { + pub fn get_bucket(&self) -> Option<&String> { + match self { + ObjectSettings::S3(s3_settings) => s3_settings.bucket.as_ref(), + ObjectSettings::Azure(azure_settings) => Some(&azure_settings.container_name), + ObjectSettings::AwsOidc(s3_aws_oidc_settings) => Some(&s3_aws_oidc_settings.bucket), + } + } } #[cfg(feature = "parquet")] pub async fn build_object_store_from_settings( settings: ObjectSettings, -) -> error::Result> { + init_private_key: Option<&crate::DB>, +) -> error::Result { match settings { - ObjectSettings::S3(s3_settings) => build_s3_client_from_settings(s3_settings).await, + ObjectSettings::S3(s3_settings) => build_s3_client_from_settings(s3_settings) + .await + .map(|x| ExpirableObjectStore::from(x)), ObjectSettings::Azure(azure_settings) => { let azure_blob_resource = azure_settings; - build_azure_blob_client(&azure_blob_resource) + build_azure_blob_client(&azure_blob_resource).map(|x| ExpirableObjectStore::from(x)) + } + ObjectSettings::AwsOidc(ref s3_aws_oidc_settings) => { + let token_generator = crate::job_s3_helpers_oss::TokenGenerator::AsServerInstance(); + let res = crate::job_s3_helpers_oss::generate_s3_aws_oidc_resource( + s3_aws_oidc_settings.clone(), + token_generator, + init_private_key, + ) + .await?; + + build_object_store_client(&res) + .await + .map(|x| ExpirableObjectStore { + store: x, + refresh: Some(ObjectStoreRefresh::new(settings.clone(), res.expiration())), + }) } } } @@ -443,6 +674,7 @@ pub async fn build_s3_client_from_settings( path_style: settings.path_style, port: settings.port, token: None, + expiration: None, }; build_s3_client(&s3_resource).await @@ -474,3 +706,205 @@ impl CredentialProvider for AwsCredentialAdapter { pub fn bundle(w_id: &str, hash: &str) -> String { format!("script_bundle/{}/{}", w_id, hash) } + +pub fn raw_app(w_id: &str, version: &i64) -> String { + format!("/home/rfiszel/raw_app/{}/{}", w_id, version) +} + +// Originally used a Arc> +// But cannot call .close() on it because it moves the value and the object is not Sized +#[cfg(feature = "parquet")] +enum RecordBatchWriterEnum { + Parquet(ArrowWriter), + Csv(csv::Writer), + Json(json::Writer), +} + +#[cfg(feature = "parquet")] +impl RecordBatchWriter for RecordBatchWriterEnum { + fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> { + match self { + RecordBatchWriterEnum::Parquet(w) => w.write(batch).map_err(|e| e.into()), + RecordBatchWriterEnum::Csv(w) => w.write(batch), + RecordBatchWriterEnum::Json(w) => w.write(batch), + } + } + + fn close(self) -> Result<(), ArrowError> { + match self { + RecordBatchWriterEnum::Parquet(w) => w.close().map_err(|e| e.into()).map(drop), + RecordBatchWriterEnum::Csv(w) => w.close(), + RecordBatchWriterEnum::Json(w) => w.close(), + } + } +} + +#[cfg(feature = "parquet")] +struct ChannelWriter { + sender: tokio::sync::mpsc::Sender>, +} + +#[cfg(feature = "parquet")] +impl Write for ChannelWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let data: Bytes = buf.to_vec().into(); + self.sender.blocking_send(Ok(data)).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + format!("Channel send error: {}", e), + ) + })?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +#[cfg(not(feature = "parquet"))] +pub async fn convert_json_line_stream>( + mut _stream: impl futures::TryStreamExt> + Unpin, + _output_format: windmill_parser_sql::S3ModeFormat, +) -> anyhow::Result>> { + Ok(async_stream::stream! { + yield Err(anyhow::anyhow!("Parquet feature is not enabled. Cannot convert JSON line stream.")); + }) +} + +#[cfg(feature = "parquet")] +pub async fn convert_json_line_stream>( + mut stream: impl TryStreamExt> + Unpin, + output_format: S3ModeFormat, +) -> anyhow::Result>> { + const MAX_MPSC_SIZE: usize = 1000; + + use datafusion::{execution::context::SessionContext, prelude::NdJsonReadOptions}; + use futures::StreamExt; + use std::path::PathBuf; + use tokio::io::AsyncWriteExt; + + let mut path = PathBuf::from(std::env::temp_dir()); + path.push(format!("{}.json", rd_string(8))); + let path_str = path + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid path"))?; + + // Write the stream to a temporary file + let mut file: tokio::fs::File = tokio::fs::File::create(&path).await.map_err(to_anyhow)?; + + while let Some(chunk) = stream.next().await { + match chunk { + Ok(chunk) => { + // Convert the chunk to bytes and write it to the file + let b: bytes::Bytes = serde_json::to_string(&chunk)?.into(); + file.write_all(&b).await?; + file.write_all(b"\n").await?; + } + Err(e) => { + tokio::fs::remove_file(&path).await?; + return Err(e.into()); + } + } + } + + file.flush().await?; + file.sync_all().await?; + drop(file); + + let ctx = SessionContext::new(); + ctx.register_json( + "my_table", + path_str, + NdJsonReadOptions { ..Default::default() }, + ) + .await + .map_err(to_anyhow)?; + + let df = ctx.sql("SELECT * FROM my_table").await.map_err(to_anyhow)?; + let schema = df.schema().clone().into(); + let mut datafusion_stream = df.execute_stream().await.map_err(to_anyhow)?; + + let (tx, rx) = tokio::sync::mpsc::channel(MAX_MPSC_SIZE); + let writer: Arc>> = + Arc::new(Mutex::new(Some(match output_format { + S3ModeFormat::Parquet => RecordBatchWriterEnum::Parquet( + ArrowWriter::try_new(ChannelWriter { sender: tx.clone() }, Arc::new(schema), None) + .map_err(to_anyhow)?, + ), + + S3ModeFormat::Csv => { + RecordBatchWriterEnum::Csv(csv::Writer::new(ChannelWriter { sender: tx.clone() })) + } + S3ModeFormat::Json => { + RecordBatchWriterEnum::Json(json::Writer::<_, JsonArray>::new(ChannelWriter { + sender: tx.clone(), + })) + } + }))); + + // This spawn is so that the data is sent in the background. Else the function would deadlock + // when hitting the mpsc channel limit + task::spawn(async move { + while let Some(batch_result) = datafusion_stream.next().await { + let batch: RecordBatch = match batch_result { + Ok(batch) => batch, + Err(e) => { + tracing::error!("Error in datafusion stream: {:?}", &e); + match tx.send(Err(e.into())).await { + Ok(_) => {} + Err(e) => tracing::error!("Failed to write error to channel: {:?}", &e), + } + break; + } + }; + let writer = writer.clone(); + // Writer calls blocking_send which would crash if called from the async context + let write_result = task::spawn_blocking(move || { + // SAFETY: We await so the code is actually sequential, lock unwrap cannot panic + // Second unwrap is ok because we initialized the option with Some + writer.lock().unwrap().as_mut().unwrap().write(&batch) + }) + .await; + match write_result { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + tracing::error!("Error writing batch: {:?}", &e); + match tx.send(Err(e.into())).await { + Ok(_) => {} + Err(e) => tracing::error!("Failed to write error to channel: {:?}", &e), + } + } + Err(e) => tracing::error!("Error in blocking task: {:?}", &e), + }; + } + task::spawn_blocking(move || { + writer.lock().unwrap().take().unwrap().close()?; + drop(writer); + Ok::<_, anyhow::Error>(()) + }) + .await??; + drop(ctx); + tokio::fs::remove_file(&path).await?; + Ok::<_, anyhow::Error>(()) + }); + + Ok(tokio_stream::wrappers::ReceiverStream::new(rx)) +} + +#[derive(Deserialize, Serialize)] +pub struct DuckdbConnectionSettingsResponse { + pub connection_settings_str: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub azure_container_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub s3_bucket: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct DuckdbConnectionSettingsQueryV2 { + #[serde(skip_serializing_if = "Option::is_none")] + pub s3_resource_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub storage: Option, +} diff --git a/backend/windmill-common/src/schedule.rs b/backend/windmill-common/src/schedule.rs index b655f35c76..e78d3a211c 100644 --- a/backend/windmill-common/src/schedule.rs +++ b/backend/windmill-common/src/schedule.rs @@ -53,6 +53,8 @@ pub struct Schedule { #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tag: Option, #[serde(skip_serializing_if = "Option::is_none")] pub paused_until: Option>, diff --git a/backend/windmill-common/src/schema.rs b/backend/windmill-common/src/schema.rs new file mode 100644 index 0000000000..71ecd220d1 --- /dev/null +++ b/backend/windmill-common/src/schema.rs @@ -0,0 +1,641 @@ +use anyhow::anyhow; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, str::FromStr}; + +use serde_json::{value::RawValue, Value}; + +use crate::{error::Error, scripts::ScriptLang}; + +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub enum JsonPrimitiveType { + String, + Number, + Integer, + Object, + Array, + Boolean, + Null, +} + +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub enum SchemaValidationRule { + StrictEnum(Vec), + IsNull, + IsInteger, + IsString, + IsBool, + IsDatetime, + IsNumber, + IsEmail, + IsObject(Vec<(String, Vec)>), + IsArray(Vec), + IsUnionType(Vec>), + IsOneOf(HashMap>), + IsBytes, +} + +impl SchemaValidationRule { + fn from_primitive(p: &JsonPrimitiveType, val: &Value) -> Result, anyhow::Error> { + let mut schema_rules = vec![]; + + match p { + JsonPrimitiveType::String => { + schema_rules.push(SchemaValidationRule::IsString); + + if let Some(format) = val.get("format").and_then(|f| f.as_str()) { + if format == "date" || format == "date-time" { + schema_rules.push(SchemaValidationRule::IsDatetime); + } + + if format == "email" { + schema_rules.push(SchemaValidationRule::IsEmail); + } + } + + if let Some(encoding) = val.get("contentEncoding").and_then(|e| e.as_str()) { + if encoding == "base64" { + schema_rules.push(SchemaValidationRule::IsBytes); + } + } + } + + JsonPrimitiveType::Number => { + schema_rules.push(SchemaValidationRule::IsNumber); + } + JsonPrimitiveType::Integer => { + schema_rules.push(SchemaValidationRule::IsInteger); + } + JsonPrimitiveType::Object => { + let mut obj_rules = vec![]; + + if let Some(properties) = val.get("properties") { + let properties = properties + .as_object() + .ok_or(anyhow!("Field properties should be an object"))?; + + for (key, v) in properties { + obj_rules.push((key.clone(), SchemaValidationRule::from_value(v)?)) + } + + schema_rules.push(SchemaValidationRule::IsObject(obj_rules)); + } else if let Some(one_of) = val.get("oneOf") { + let one_of = one_of + .as_array() + .ok_or(anyhow!("`oneOf` needs to be an array"))?; + let mut rules_map: HashMap> = HashMap::new(); + + for variant in one_of { + let variant_label = variant + .get("title") + .ok_or(anyhow!( + "oneOf variant definition should have a `title` field" + ))? + .as_str() + .ok_or(anyhow!( + "oneOf variant definition `title` field should be a string" + ))?; + if !rules_map.contains_key(variant_label) { + rules_map.insert( + variant_label.to_string(), + SchemaValidationRule::from_value(variant)?, + ); + } else { + return Err(anyhow!( + "oneOf definition has a duplicate variant `{variant_label}`" + )); + } + } + + schema_rules.push(SchemaValidationRule::IsOneOf(rules_map)) + } else { + let is_resource = val + .get("format") + .and_then(|f| f.as_str()) + .map(|f| f.starts_with("resource")) + .unwrap_or(false); + if !is_resource { + return Err(anyhow!( + "Object type should have a `properties` or `anyOf` field, or be a resource" + )); + } + } + } + JsonPrimitiveType::Array => { + let items = val + .get("items") + .ok_or(anyhow!("Array type should have field `items`"))?; + + let arr_rules = SchemaValidationRule::from_value(items)?; + + schema_rules.push(SchemaValidationRule::IsArray(arr_rules)); + } + JsonPrimitiveType::Boolean => { + schema_rules.push(SchemaValidationRule::IsBool); + } + JsonPrimitiveType::Null => { + schema_rules.push(SchemaValidationRule::IsNull); + } + } + + Ok(schema_rules) + } + + fn from_value(val: &Value) -> Result, Error> { + if let Some(any_of) = val.get("anyOf").and_then(|any_of| any_of.as_array()) { + let mut r = vec![]; + + for variant in any_of { + r.push(SchemaValidationRule::from_value(variant)?); + } + return Ok(vec![SchemaValidationRule::IsUnionType(r)]); + } + + let mut schema_rules = vec![]; + + let typ = val.get("type").ok_or(anyhow!("Missing `type` field"))?; + + if let Some(typ) = typ.as_str() { + schema_rules.append(&mut SchemaValidationRule::from_primitive( + &JsonPrimitiveType::from_str(typ)?, + val, + )?); + } else if let Some(typ_arr) = typ.as_array() { + let typ_arr = typ_arr + .into_iter() + .map(|v| { + SchemaValidationRule::from_primitive( + &JsonPrimitiveType::from_str( + v.as_str() + .ok_or(anyhow!("Expected array of strings for `type` field"))?, + )?, + v, + ) + }) + .collect::>, anyhow::Error>>()?; + + schema_rules.push(SchemaValidationRule::IsUnionType(typ_arr)); + } else { + return Err(anyhow!( + "Unsupported value for type field, expected string or string array" + ) + .into()); + } + + if let Some(enum_variants) = val.get("enum") { + let variants = enum_variants + .as_array() + .ok_or(anyhow!("enum variants are not in an array"))? + .clone(); + schema_rules.push(SchemaValidationRule::StrictEnum(variants)); + } + + Ok(schema_rules) + } + + fn apply_rule(&self, key: &str, val: &Value, required: bool) -> Result<(), Error> { + if val.is_null() { + if !required { + return Ok(()); + } + return Err(Error::ArgumentErr(format!("Argument {key} cannot be null"))); + } + match self { + SchemaValidationRule::IsNull => { + if !val.is_null() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be null" + ))); + } + } + SchemaValidationRule::StrictEnum(vec) => { + if !vec.contains(val) { + let options = vec.iter().map(|s| s.to_string()).join(", "); + return Err(Error::ArgumentErr(format!( + "Enum type argument `{key}` expected one of `[{options}]` but received {}", + val.to_string() + ))); + } + } + SchemaValidationRule::IsNumber => { + if !val.is_number() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be a numeric value" + ))); + } + } + SchemaValidationRule::IsInteger => { + if !val.is_i64() && !val.is_u64() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be an integer" + ))); + } + } + SchemaValidationRule::IsString => { + if !val.is_string() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be a string" + ))); + } + } + SchemaValidationRule::IsBool => { + if !val.is_boolean() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be a boolean" + ))); + } + } + SchemaValidationRule::IsObject(o) => { + if !val.is_object() { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be an object" + ))); + } + + for (s, rules) in o { + let v = val + .get(&s) + .ok_or(Error::ArgumentErr(format!("Missing field {s} in {key}")))?; + for r in rules { + r.apply_rule(&format!("{key}.{s}"), v, true)?; + } + } + } + SchemaValidationRule::IsArray(vec) => { + if let Some(arr) = val.as_array() { + for (i, el) in arr.iter().enumerate() { + for r in vec { + r.apply_rule(&format!("{key}[{i}]"), el, true)?; + } + } + } else { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` should be an array" + ))); + } + } + // TODO: For better error messages on OneOf, make a dedicated OneOf type that matches the label instead of trying the whole type. + SchemaValidationRule::IsUnionType(vec) => { + let mut match_count = 0; + + let mut errors = String::new(); + for typ in vec { + if let Some(e) = typ + .iter() + .map(|r| r.apply_rule(key, val, true)) + .find_map(Result::err) + { + errors.push_str(&format!("- {e}\n")); + } else { + match_count += 1; + } + } + + if match_count == 0 { + return Err(Error::ArgumentErr(format!( + "Argument `{key}` is not valid, failed matching to one of the expected types. Here is a list of possible errors:\n{errors}" + ))); + } + } + SchemaValidationRule::IsOneOf(vec) => { + let variant_label = val + .get("label") + .ok_or(Error::ArgumentErr(format!( + "oneOf Variant for argument `{key}` should have a label field" + )))? + .as_str() + .ok_or(Error::ArgumentErr(format!( + "Argument `{key}` of type oneOf expected the label to be a string" + )))?; + + let variant_rules = vec + .get(variant_label) + .ok_or_else(|| Error::ArgumentErr(format!( + "Argument `{key}` of type oneOf expected one of the following variants {}, but received `{variant_label}`", vec.keys().join(", ") + )))?; + + for r in variant_rules { + r.apply_rule(key, val, true).map_err(|e| Error::ArgumentErr(format!("Argument `{key}`: The schema for the selected oneOf variant `{variant_label}` was not respected: {e}")))?; + } + } + // TODO: Implement validation on these + SchemaValidationRule::IsDatetime => (), + SchemaValidationRule::IsEmail => (), + SchemaValidationRule::IsBytes => (), + } + + Ok(()) + } +} + +fn find_annotation(comm_lit: &str, annotation: &str, code: &str) -> bool { + let a = format!("{comm_lit} {annotation}"); + for l in code.lines() { + if !l.starts_with(comm_lit) { + break; + } + + if l.trim_end() == a { + return true; + } + } + + false +} + +pub fn should_validate_schema(code: &str, lang: &ScriptLang) -> bool { + let annotation = "schema_validation"; + use ScriptLang::*; + let comment = match lang { + Nativets | Bun | Bunnative | Deno | Php | CSharp | Java => "//", + Python3 | Go | Bash | Powershell | Graphql | Ansible | Nu => "#", + Postgresql | Mysql | Bigquery | Snowflake | Mssql | OracleDB | DuckDb => "--", + Rust => "//!", + // for related places search: ADD_NEW_LANG + }; + find_annotation(comment, annotation, code) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SchemaValidator { + pub required: Vec, + pub rules: Vec<(String, Vec)>, +} + +impl SchemaValidator { + pub fn validate(&self, args: &HashMap>) -> Result<(), Error> { + for key in &self.required { + if !args.contains_key(key) { + return Err(Error::ArgumentErr(format!("Argument {key} is required"))); + } + } + + for (key, rules) in &self.rules { + if let Some(raw_val) = args.get(key) { + let parsed_val = Value::from_str(raw_val.get()).map_err(|e| { + Error::ArgumentErr(format!("Failed to parse `{key}` argument: {e}")) + })?; + for rule in rules { + rule.apply_rule(key, &parsed_val, self.required.contains(key))?; + } + } + } + + Ok(()) + } + + pub fn from_schema(schema: &str) -> Result { + let schema: Value = serde_json::from_str(schema)?; + + if let Some(draft_version) = schema.get("$schema") { + match draft_version.as_str() { + Some("https://json-schema.org/draft/2020-12/schema") => (), + _ => return Err(anyhow!("Supplied schema draft version is unsuported").into()), + } + } else { + return Err(anyhow!("No draft version supplied").into()); + } + + let required: Vec = schema + .get("required") + .ok_or(anyhow!("Missing `required` field on schema"))? + .as_array() + .ok_or(anyhow!("`required` field should be an array of strings"))? + .into_iter() + .map(|v| { + v.as_str() + .map(|s| s.to_string()) + .ok_or(anyhow!("required field key is not a string")) + }) + .collect::, anyhow::Error>>()?; + + let properties = schema + .get("properties") + .ok_or(anyhow!("Missing `properties` field on schema"))? + .as_object() + .ok_or(anyhow!("`properties` field should be an object"))?; + + let mut rules = vec![]; + + for (key, val) in properties { + rules.push(( + key.clone(), + SchemaValidationRule::from_value(val) + .map_err(|e| anyhow!("Problem making rule for {key}: {e}"))?, + )); + } + + Ok(Self { required, rules }) + } +} + +impl JsonPrimitiveType { + fn from_str(typ: &str) -> Result { + match typ { + "string" => { + return Ok(JsonPrimitiveType::String); + } + "number" => { + return Ok(JsonPrimitiveType::Number); + } + "integer" => { + return Ok(JsonPrimitiveType::Integer); + } + "object" => { + return Ok(JsonPrimitiveType::Object); + } + "array" => { + return Ok(JsonPrimitiveType::Array); + } + "boolean" => { + return Ok(JsonPrimitiveType::Boolean); + } + "null" => { + return Ok(JsonPrimitiveType::Null); + } + other => return Err(anyhow!("Received unsupported type `{other}`").into()), + } + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + fn value_to_rawvalue_map( + value: Value, + ) -> Result>, anyhow::Error> { + match value { + Value::Object(map) => { + let mut result = HashMap::new(); + for (key, val) in map { + let raw = serde_json::to_string(&val)?; // Serialize the Value to a string + let raw_value: Box = serde_json::from_str(&raw)?; // Convert string to Box + result.insert(key, raw_value); + } + Ok(result) + } + _ => Err(anyhow!("Expected a JSON object")), + } + } + #[test] + fn test_parse_and_validate_schema() { + let schema = r#"{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "a": { + "contentEncoding": "base64", + "default": null, + "description": "", + "originalType": "bytes", + "type": "string" + }, + "b": { + "default": null, + "description": "", + "enum": [ + "my", + "enum" + ], + "originalType": "enum", + "type": "string" + }, + "e": { + "default": "inferred type string from default arg", + "description": "", + "originalType": "string", + "type": "string" + }, + "f": { + "default": { + "nested": "object" + }, + "description": "", + "properties": { + "nested": { + "description": "", + "type": "string", + "originalType": "string" + } + }, + "type": "object" + }, + "g": { + "default": null, + "description": "", + "oneOf": [ + { + "type": "object", + "title": "Variant 1", + "properties": { + "label": { + "description": "", + "type": "string", + "originalType": "enum", + "enum": [ + "Variant 1" + ] + }, + "foo": { + "description": "", + "type": "string", + "originalType": "string" + } + } + }, + { + "type": "object", + "title": "Variant 2", + "properties": { + "label": { + "description": "", + "type": "string", + "originalType": "enum", + "enum": [ + "Variant 2" + ] + }, + "bar": { + "description": "", + "type": "number" + } + } + } + ], + "type": "object" + } + }, + "required": [ + "a", + "b", + "g" + ], + "type": "object" +} +"#; + + let validator = SchemaValidator::from_schema(schema) + .expect("Schema couldn't be built from a valid schema"); + + let args = json!( + { + "g": { + "label": "Variant 1", + "foo": "" + }, + "f": { + "nested": "object" + }, + "e": "inferred type string from default arg", + "b": "my", + "a": null + } + ); + + validator + .validate(&value_to_rawvalue_map(args).unwrap()) + .err() + .expect("Validation should not work for this"); + + let args = json!( + { + "g": { + "label": "Variant 1", + "foo": "" + }, + "f": { + "nested": "object" + }, + "e": "inferred type string from default arg", + "b": "not_enum", + "a": "123" + } + ); + + validator + .validate(&value_to_rawvalue_map(args).unwrap()) + .err() + .expect("Validation should not work for this"); + + let args = json!( + { + "g": { + "label": "Variant 1", + "foo": "" + }, + "f": { + "nested": "object" + }, + "e": "inferred type string from default arg", + "b": "my", + "a": "123" + } + ); + + validator + .validate(&value_to_rawvalue_map(args).unwrap()) + .expect("Validation should work for this"); + } +} diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 5658d505b6..ffa7bd486a 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -9,6 +9,7 @@ use std::{ fmt::{self, Display}, hash::{Hash, Hasher}, + str::FromStr, }; use crate::{ @@ -21,6 +22,7 @@ use crate::worker::HUB_CACHE_DIR; use anyhow::Context; use backon::ConstantBuilder; use backon::{BackoffBuilder, Retryable}; +use itertools::Itertools; use serde::de::Error as _; use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize}; @@ -46,10 +48,13 @@ pub enum ScriptLang { Graphql, Mssql, OracleDB, + DuckDb, Php, Rust, Ansible, CSharp, + Nu, + Java, // for related places search: ADD_NEW_LANG } impl ScriptLang { @@ -70,14 +75,52 @@ impl ScriptLang { ScriptLang::Mssql => "mssql", ScriptLang::Graphql => "graphql", ScriptLang::OracleDB => "oracledb", + ScriptLang::DuckDb => "duckdb", ScriptLang::Php => "php", ScriptLang::Rust => "rust", ScriptLang::Ansible => "ansible", ScriptLang::CSharp => "csharp", + ScriptLang::Nu => "nu", + ScriptLang::Java => "java", + // for related places search: ADD_NEW_LANG } } } +impl FromStr for ScriptLang { + type Err = Error; + fn from_str(s: &str) -> Result { + let language = match s.to_lowercase().as_str() { + "bun" => ScriptLang::Bun, + "bunnative" => ScriptLang::Bunnative, + "nativets" => ScriptLang::Nativets, + "deno" => ScriptLang::Deno, + "python3" => ScriptLang::Python3, + "go" => ScriptLang::Go, + "bash" => ScriptLang::Bash, + "powershell" => ScriptLang::Powershell, + "postgresql" => ScriptLang::Postgresql, + "mysql" => ScriptLang::Mysql, + "bigquery" => ScriptLang::Bigquery, + "snowflake" => ScriptLang::Snowflake, + "mssql" => ScriptLang::Mssql, + "graphql" => ScriptLang::Graphql, + "oracledb" => ScriptLang::OracleDB, + "php" => ScriptLang::Php, + "rust" => ScriptLang::Rust, + "ansible" => ScriptLang::Ansible, + "csharp" => ScriptLang::CSharp, + "nu" => ScriptLang::Nu, + "java" => ScriptLang::Java, + language => { + return Err(anyhow::anyhow!("{} is currently not supported", language).into()) + } + }; + + Ok(language) + } +} + #[derive(Eq, PartialEq, Debug, Hash, Clone, Copy, sqlx::Type)] #[sqlx(transparent)] pub struct ScriptHash(pub i64); @@ -241,6 +284,7 @@ pub struct ListableScript { #[sqlx(default)] #[serde(skip_serializing_if = "Option::is_none")] pub deployment_msg: Option, + pub kind: ScriptKind, } fn is_false(x: &bool) -> bool { @@ -259,7 +303,7 @@ pub struct ScriptHistoryUpdate { pub deployment_msg: Option, } -#[derive(Serialize, Deserialize, Debug, sqlx::Type)] +#[derive(Serialize, Deserialize, Debug, sqlx::Type, Clone)] #[sqlx(transparent)] #[serde(transparent)] pub struct Schema(pub sqlx::types::Json>); @@ -359,7 +403,7 @@ where deserializer.deserialize_any(StringOrArrayVisitor) } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] pub struct ListScriptQuery { pub path_start: Option, pub path_exact: Option, @@ -376,6 +420,29 @@ pub struct ListScriptQuery { pub include_without_main: Option, pub include_draft_only: Option, pub with_deployment_msg: Option, + #[serde(default, deserialize_with = "from_seq")] + pub languages: Option>, +} + +fn from_seq<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let s = ::deserialize(deserializer)?; + + let languages: Vec = s + .split(",") + .map(ScriptLang::from_str) + .try_collect() + .map_err(|e| serde::de::Error::custom(e.to_string()))?; + + let languages = if languages.is_empty() { + None + } else { + Some(languages) + }; + + Ok(languages) } pub fn to_i64(s: &str) -> crate::error::Result { diff --git a/backend/windmill-common/src/stats_ee.rs b/backend/windmill-common/src/stats_oss.rs similarity index 73% rename from backend/windmill-common/src/stats_ee.rs rename to backend/windmill-common/src/stats_oss.rs index 5d2dc82b82..829e6e3a5b 100644 --- a/backend/windmill-common/src/stats_ee.rs +++ b/backend/windmill-common/src/stats_oss.rs @@ -1,17 +1,26 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::stats_ee::*; + +#[cfg(not(feature = "private"))] use sqlx::Postgres; +#[cfg(not(feature = "private"))] use crate::{error::Result, scripts::ScriptLang, DB}; +#[cfg(not(feature = "private"))] pub async fn get_disable_stats_setting(_db: &DB) -> bool { // stats details are closed source false } +#[cfg(not(feature = "private"))] pub async fn schedule_stats(_db: &DB, _http_client: &reqwest::Client) -> () { // stats details are closed source } +#[cfg(not(feature = "private"))] #[derive(Debug, sqlx::FromRow, serde::Serialize)] struct JobsUsage { language: Option, @@ -19,12 +28,14 @@ struct JobsUsage { count: i64, } +#[cfg(not(feature = "private"))] pub enum SendStatsReason { Manual, Schedule, OnStart, } +#[cfg(not(feature = "private"))] pub async fn send_stats( _http_client: &reqwest::Client, _db: &DB, @@ -34,11 +45,13 @@ pub async fn send_stats( Ok(()) } +#[cfg(not(feature = "private"))] pub struct ActiveUserUsage { pub author_count: Option, pub operator_count: Option, } +#[cfg(not(feature = "private"))] pub async fn get_user_usage<'c, E: sqlx::Executor<'c, Database = Postgres>>( _db: E, ) -> Result { diff --git a/backend/windmill-common/src/teams_ee.rs b/backend/windmill-common/src/teams_ee.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/windmill-common/src/teams_oss.rs b/backend/windmill-common/src/teams_oss.rs new file mode 100644 index 0000000000..347ad8e6d7 --- /dev/null +++ b/backend/windmill-common/src/teams_oss.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::teams_ee::*; + + diff --git a/backend/windmill-common/src/tracing_init.rs b/backend/windmill-common/src/tracing_init.rs index 5e6ef6940e..77194ac0a9 100644 --- a/backend/windmill-common/src/tracing_init.rs +++ b/backend/windmill-common/src/tracing_init.rs @@ -48,7 +48,7 @@ pub fn initialize_tracing( hostname: &str, mode: &Mode, environment: &str, -) -> (WorkerGuard, crate::otel_ee::OtelProvider) { +) -> (WorkerGuard, crate::otel_oss::OtelProvider) { let style = std::env::var("RUST_LOG_STYLE").unwrap_or_else(|_| "auto".into()); let rust_log_env = std::env::var("RUST_LOG"); @@ -60,23 +60,26 @@ pub fn initialize_tracing( "RUST_LOG", &format!("windmill={}", rust_log_env.as_ref().unwrap()), ) - } - let default_env_filter = if rust_log_env.is_ok_and(|x| x == "debug") { + } else if rust_log_env.as_ref().is_ok_and(|x| x == "sqlxdebug") { + std::env::set_var("RUST_LOG", "windmill=debug,sqlx=debug"); + }; + + let default_env_filter = if rust_log_env.is_ok_and(|x| x == "debug" || x == "sqlxdebug") { LevelFilter::DEBUG } else { LevelFilter::INFO }; - let meter_provider = crate::otel_ee::init_meter_provider(mode, hostname, environment); + let meter_provider = crate::otel_oss::init_meter_provider(mode, hostname, environment); #[cfg(all(feature = "otel", feature = "enterprise"))] - let opentelemetry = crate::otel_ee::init_otlp_tracer(mode, hostname, environment) + let opentelemetry = crate::otel_oss::init_otlp_tracer(mode, hostname, environment) .map(|x| tracing_opentelemetry::layer().with_tracer(x)); #[cfg(not(all(feature = "otel", feature = "enterprise")))] let opentelemetry: Option = None; - let logs_bridge = crate::otel_ee::init_logs_bridge(&mode, hostname, environment); + let logs_bridge = crate::otel_oss::init_logs_bridge(&mode, hostname, environment); use tracing_appender::rolling::{RollingFileAppender, Rotation}; diff --git a/backend/windmill-common/src/triggers.rs b/backend/windmill-common/src/triggers.rs new file mode 100644 index 0000000000..df68d0543c --- /dev/null +++ b/backend/windmill-common/src/triggers.rs @@ -0,0 +1,79 @@ +use quick_cache::sync::Cache; +use serde::{Deserialize, Serialize}; +use std::fmt; +use strum::EnumIter; + +#[derive(Eq, PartialEq, Hash)] +pub enum HubOrWorkspaceId { + Hub, + WorkspaceId(String), +} + +type RunnableFormatCacheKey = (HubOrWorkspaceId, i64, TriggerKind); + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] +pub struct RunnableFormat { + pub version: RunnableFormatVersion, + pub has_preprocessor: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] +pub enum RunnableFormatVersion { + V1, + V2, +} + +lazy_static::lazy_static! { + pub static ref RUNNABLE_FORMAT_VERSION_CACHE: Cache = Cache::new(1000); +} + +#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, EnumIter)] +#[sqlx(type_name = "TRIGGER_KIND", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum TriggerKind { + Webhook, + Http, + Websocket, + Kafka, + Email, + Nats, + Mqtt, + Sqs, + Postgres, + Gcp, +} + +impl TriggerKind { + pub fn to_key(&self) -> String { + match self { + TriggerKind::Webhook => "webhook".to_string(), + TriggerKind::Http => "http".to_string(), + TriggerKind::Websocket => "websocket".to_string(), + TriggerKind::Kafka => "kafka".to_string(), + TriggerKind::Email => "email".to_string(), + TriggerKind::Nats => "nats".to_string(), + TriggerKind::Mqtt => "mqtt".to_string(), + TriggerKind::Sqs => "sqs".to_string(), + TriggerKind::Postgres => "postgres".to_string(), + TriggerKind::Gcp => "gcp".to_string(), + } + } +} + +impl fmt::Display for TriggerKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + TriggerKind::Webhook => "webhook", + TriggerKind::Http => "http", + TriggerKind::Websocket => "websocket", + TriggerKind::Kafka => "kafka", + TriggerKind::Email => "email", + TriggerKind::Nats => "nats", + TriggerKind::Mqtt => "mqtt", + TriggerKind::Sqs => "sqs", + TriggerKind::Postgres => "postgres", + TriggerKind::Gcp => "gcp", + }; + write!(f, "{}", s) + } +} diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 539064fcf1..6b539dc73f 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -7,9 +7,9 @@ */ use crate::auth::is_devops_email; -use crate::ee::LICENSE_KEY_ID; +use crate::ee_oss::LICENSE_KEY_ID; #[cfg(feature = "enterprise")] -use crate::ee::{send_critical_alert, CriticalAlertKind}; +use crate::ee_oss::{send_critical_alert, CriticalAlertKind}; use crate::error::{to_anyhow, Error, Result}; use crate::global_settings::UNIQUE_ID_SETTING; use crate::DB; @@ -19,14 +19,17 @@ use git_version::git_version; use chrono::Utc; use croner::Cron; -use rand::distr::Alphanumeric; -use rand::{rng, Rng}; +use rand::{distr::Alphanumeric, rng, Rng}; use reqwest::Client; use semver::Version; -use serde::{Deserialize, Serialize}; +use serde::{de::Error as SerdeDeserializerError, Deserialize, Deserializer, Serialize}; use sha2::{Digest, Sha256}; use sqlx::{Pool, Postgres}; -use std::str::FromStr; +use std::borrow::Cow; +use std::fmt::Display; +use std::{fs::DirBuilder as SyncDirBuilder, str::FromStr}; +use tokio::fs::DirBuilder as AsyncDirBuilder; +use url::Url; pub const MAX_PER_PAGE: usize = 10000; pub const DEFAULT_PER_PAGE: usize = 1000; @@ -34,6 +37,10 @@ pub const DEFAULT_PER_PAGE: usize = 1000; pub const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version"); +pub const AGENT_JWT_PREFIX: &str = "jwt_agent_"; +pub const WORKER_NAME_PREFIX: &str = "wk"; +pub const AGENT_WORKER_NAME_PREFIX: &str = "ag"; + use crate::CRITICAL_ALERT_MUTE_UI_ENABLED; use std::panic::{self, AssertUnwindSafe, Location}; use std::sync::atomic::Ordering; @@ -54,6 +61,12 @@ lazy_static::lazy_static! { } ).unwrap_or(Version::new(0, 1, 0)); + pub static ref HOSTNAME :String = std::env::var("FORCE_HOSTNAME").unwrap_or_else(|_| { + gethostname() + .to_str() + .map(|x| x.to_string()) + .unwrap_or_else(|| rd_string(5)) + }); pub static ref MODE_AND_ADDONS: ModeAndAddons = { let mut search_addon = false; @@ -71,12 +84,12 @@ lazy_static::lazy_static! { } Mode::Worker } else if &x == "agent" { - println!("Binary is in 'agent' mode"); + println!("Binary is in 'agent' mode with BASE_INTERNAL_URL={}", std::env::var("BASE_INTERNAL_URL").unwrap_or_default()); if std::env::var("BASE_INTERNAL_URL").is_err() { panic!("BASE_INTERNAL_URL is required in agent mode") } - if std::env::var("JOB_TOKEN").is_err() { - println!("JOB_TOKEN is not passed, hence workers will still need to create permissions for each job and the DATABASE_URL needs to be of a role that can INSERT into the job_perms table") + if std::env::var("AGENT_TOKEN").is_err() { + println!("AGENT_TOKEN is not passed. This is required for the agent to work and contains the JWT to authenticate with the server.") } #[cfg(not(feature = "enterprise"))] @@ -98,8 +111,10 @@ lazy_static::lazy_static! { search_addon = true; println!("Binary is in 'standalone' mode with search enabled"); Mode::Standalone - } - else { + } else if &x == "mcp" { + println!("Binary is in 'mcp' mode"); + Mode::MCP + } else { if &x != "standalone" { eprintln!("mode not recognized, defaulting to standalone: {x}"); } else { @@ -119,6 +134,10 @@ lazy_static::lazy_static! { }; } +lazy_static::lazy_static! { + pub static ref AGENT_TOKEN: String = std::env::var("AGENT_TOKEN").unwrap_or_default(); +} + #[derive(Clone)] pub struct ModeAndAddons { pub indexer: bool, @@ -166,13 +185,41 @@ pub async fn require_admin_or_devops( Ok(()) } -pub fn hostname() -> String { - std::env::var("FORCE_HOSTNAME").unwrap_or_else(|_| { - gethostname() - .to_str() - .map(|x| x.to_string()) - .unwrap_or_else(|| rd_string(5)) - }) +fn instance_name(hostname: &str) -> String { + hostname + .replace(" ", "") + .split("-") + .last() + .unwrap() + .to_ascii_lowercase() + .to_string() +} + +const DEFAULT_WORKER_SUFFIX_LEN: usize = 5; +pub const SSH_AGENT_WORKER_SUFFIX: &'static str = "/ssh"; + +pub fn create_worker_suffix(hostname: &str, rd_string_len: usize, ssh_ag_worker: bool) -> String { + let mut wk_suffix = format!("{}-{}", instance_name(hostname), rd_string(rd_string_len)); + if ssh_ag_worker { + wk_suffix.push_str(SSH_AGENT_WORKER_SUFFIX); + } + wk_suffix +} + +pub fn create_ssh_agent_worker_suffix(hostname: &str) -> String { + create_worker_suffix(hostname, DEFAULT_WORKER_SUFFIX_LEN, true) +} + +pub fn create_default_worker_suffix(hostname: &str) -> String { + create_worker_suffix(hostname, DEFAULT_WORKER_SUFFIX_LEN, false) +} + +pub fn worker_name_with_suffix(is_agent: bool, worker_group: &str, suffix: &str) -> String { + if is_agent { + format!("{}-{}-{}", AGENT_WORKER_NAME_PREFIX, worker_group, suffix) + } else { + format!("{}-{}-{}", WORKER_NAME_PREFIX, worker_group, suffix) + } } pub fn paginate(pagination: Pagination) -> (usize, usize) { @@ -200,6 +247,21 @@ pub async fn now_from_db<'c, E: sqlx::PgExecutor<'c>>( .unwrap()) } +pub async fn create_directory_async(directory_path: &str) { + AsyncDirBuilder::new() + .recursive(true) + .create(directory_path) + .await + .expect("could not create dir"); +} + +pub fn create_directory_sync(directory_path: &str) { + SyncDirBuilder::new() + .recursive(true) + .create(directory_path) + .expect("could not create dir"); +} + pub fn not_found_if_none>(opt: Option, kind: &str, name: U) -> Result { if let Some(o) = opt { Ok(o) @@ -325,6 +387,7 @@ pub enum Mode { Server, Standalone, Indexer, + MCP, } impl std::fmt::Display for Mode { @@ -335,6 +398,7 @@ impl std::fmt::Display for Mode { Mode::Server => write!(f, "server"), Mode::Standalone => write!(f, "standalone"), Mode::Indexer => write!(f, "indexer"), + Mode::MCP => write!(f, "mcp"), } } } @@ -446,6 +510,63 @@ pub async fn report_recovered_critical_error( } } +pub trait IsEmpty { + fn is_empty(&self) -> bool; +} + +impl IsEmpty for String { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl IsEmpty for Vec { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl IsEmpty for Option +where + T: IsEmpty, +{ + fn is_empty(&self) -> bool { + match self { + Some(v) => v.is_empty(), + None => true, + } + } +} + +pub fn empty_as_none<'de, D, T>(deserializer: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + IsEmpty, +{ + let option = as serde::Deserialize>::deserialize(deserializer)?; + Ok(option.filter(|s| !s.is_empty())) +} + +pub fn is_empty(value: &T) -> bool +where + T: IsEmpty, +{ + value.is_empty() +} + +pub fn deserialize_url<'de, D: Deserializer<'de>>( + de: D, +) -> std::result::Result, D::Error> { + let intermediate = >>::deserialize(de)?; + + match intermediate.as_deref() { + None | Some("") => Ok(None), + Some(non_empty_string) => Url::parse(non_empty_string) + .map(Some) + .map_err(D::Error::custom), + } +} + pub async fn fetch_mute_workspace(_db: &DB, workspace_id: &str) -> Result { match sqlx::query!( "SELECT mute_critical_alerts FROM workspace_settings WHERE workspace_id = $1", @@ -669,7 +790,10 @@ impl Future for WarnAfterFuture { // Poll the timeout future to check if it has elapsed. if !*this.warned { if this.timeout.poll(cx).is_ready() { - tracing::warn!(location = this.location, "SLOW_QUERY: query to db taking longer than expected (> {} seconds). This is a sign the database is under heavy load, query is too heavy or database is undersized", + tracing::warn!( + location = this.location, + "SLOW_QUERY: query {} to db taking longer than expected (> {} seconds)", + this.location, this.seconds, ); *this.warned = true; @@ -683,7 +807,8 @@ impl Future for WarnAfterFuture { let elapsed = this.start_time.elapsed(); tracing::warn!( location = this.location, - "SLOW_QUERY: completed with total duration: {:.2?}", + "SLOW_QUERY: completed query {} with total duration: {:.2?}", + this.location, elapsed ); } @@ -693,3 +818,20 @@ impl Future for WarnAfterFuture { } } } + +#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum RunnableKind { + Script, + Flow, +} + +impl Display for RunnableKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let runnable_kind = match self { + RunnableKind::Script => "script", + RunnableKind::Flow => "flow", + }; + write!(f, "{}", runnable_kind) + } +} diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 1817945705..1dfa1438d8 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -7,9 +7,11 @@ */ use crate::error; +use crate::worker::Connection; use crate::{worker::WORKER_GROUP, BASE_URL, DB}; use chrono::{SecondsFormat, Utc}; use magic_crypt::{MagicCrypt256, MagicCryptError, MagicCryptTrait}; +use quick_cache::sync::Cache; use serde::{Deserialize, Serialize}; lazy_static::lazy_static! { @@ -160,8 +162,12 @@ pub fn decrypt(mc: &MagicCrypt256, value: String) -> error::Result { pub const WM_SCHEDULED_FOR: &str = "WM_SCHEDULED_FOR"; +lazy_static::lazy_static! { + pub static ref CUSTOM_ENVS_CACHE: Cache)> = Cache::new(100); +} + pub async fn get_reserved_variables( - db: &DB, + conn: &Connection, w_id: &str, token: &str, email: &str, @@ -174,7 +180,6 @@ pub async fn get_reserved_variables( schedule_path: Option, step_id: Option, root_flow_id: Option, - jwt_token: Option, scheduled_for: Option>, ) -> Vec { let state_path = { @@ -201,6 +206,8 @@ pub async fn get_reserved_variables( } }; + let custom_envs = get_cached_workspace_envs(conn, w_id).await; + let joined_schedule_path = schedule_path .clone() .unwrap_or("manual".to_string()) @@ -223,133 +230,154 @@ pub async fn get_reserved_variables( }; vec![ - ContextualVariable { - name: "WM_WORKSPACE".to_string(), - value: w_id.to_string(), - description: "Workspace id of the current script".to_string(), + ContextualVariable { + name: "WM_WORKSPACE".to_string(), + value: w_id.to_string(), + description: "Workspace id of the current script".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_TOKEN".to_string(), + value: token.to_string(), + description: "Token ephemeral to the current script with equal permission to the \ + permission of the run (Usable as a bearer token)" + .to_string(), is_custom: false, - }, - ContextualVariable { - name: "WM_TOKEN".to_string(), - value: token.to_string(), - description: "Token ephemeral to the current script with equal permission to the \ - permission of the run (Usable as a bearer token)" - .to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_EMAIL".to_string(), - value: email.to_string(), - description: "Email of the user that executed the current script".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_USERNAME".to_string(), - value: username.to_string(), - description: "Username of the user that executed the current script".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_BASE_URL".to_string(), - value: BASE_URL.read().await.clone(), - description: "base url of this instance".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_JOB_ID".to_string(), - value: job_id.to_string(), - description: "Job id of the current script".to_string(), - is_custom: false, - }, - ContextualVariable { - name: WM_SCHEDULED_FOR.to_string(), - value: scheduled_for - .map(|ts| ts.to_rfc3339_opts(SecondsFormat::Secs, true)) - .unwrap_or_else(|| "".to_string()), - description: "date-time in UTC (e.g: 2014-11-28T12:45:59.324310806Z) of when the job was scheduled".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_JOB_PATH".to_string(), - value: path.unwrap_or_else(|| "".to_string()), - description: "Path of the script or flow being run if any".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_FLOW_JOB_ID".to_string(), - value: flow_id.unwrap_or_else(|| "".to_string()), - description: "Job id of the encapsulating flow if the job is a flow step".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_ROOT_FLOW_JOB_ID".to_string(), - value: root_flow_id.unwrap_or_else(|| "".to_string()), - description: "Job id of the root flow if the job is a flow step".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_FLOW_PATH".to_string(), - value: flow_path.unwrap_or_else(|| "".to_string()), - description: "Path of the encapsulating flow if the job is a flow step".to_string(), - is_custom: false, - }, + }, + ContextualVariable { + name: "WM_EMAIL".to_string(), + value: email.to_string(), + description: "Email of the user that executed the current script".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_USERNAME".to_string(), + value: username.to_string(), + description: "Username of the user that executed the current script".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_BASE_URL".to_string(), + value: BASE_URL.read().await.clone(), + description: "base url of this instance".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_JOB_ID".to_string(), + value: job_id.to_string(), + description: "Job id of the current script".to_string(), + is_custom: false, + }, + ContextualVariable { + name: WM_SCHEDULED_FOR.to_string(), + value: scheduled_for + .map(|ts| ts.to_rfc3339_opts(SecondsFormat::Secs, true)) + .unwrap_or_else(|| "".to_string()), + description: "date-time in UTC (e.g: 2014-11-28T12:45:59.324310806Z) of when the job was scheduled".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_JOB_PATH".to_string(), + value: path.unwrap_or_else(|| "".to_string()), + description: "Path of the script or flow being run if any".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_FLOW_JOB_ID".to_string(), + value: flow_id.unwrap_or_else(|| "".to_string()), + description: "Job id of the encapsulating flow if the job is a flow step".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_ROOT_FLOW_JOB_ID".to_string(), + value: root_flow_id.unwrap_or_else(|| "".to_string()), + description: "Job id of the root flow if the job is a flow step".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_FLOW_PATH".to_string(), + value: flow_path.unwrap_or_else(|| "".to_string()), + description: "Path of the encapsulating flow if the job is a flow step".to_string(), + is_custom: false, + }, - ContextualVariable { - name: "WM_SCHEDULE_PATH".to_string(), - value: schedule_path.unwrap_or_else(|| "".to_string()), - description: "Path of the schedule if the job of the step or encapsulating step has \ - been triggered by a schedule" - .to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_PERMISSIONED_AS".to_string(), - value: permissioned_as.to_string(), - description: "Fully Qualified (u/g) owner name of executor of the job".to_string(), + ContextualVariable { + name: "WM_SCHEDULE_PATH".to_string(), + value: schedule_path.unwrap_or_else(|| "".to_string()), + description: "Path of the schedule if the job of the step or encapsulating step has \ + been triggered by a schedule" + .to_string(), is_custom: false, - }, - ContextualVariable { - name: "WM_STATE_PATH".to_string(), - value: state_path.clone(), - description: "State resource path unique to a script and its trigger".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_FLOW_STEP_ID".to_string(), - value: step_id.unwrap_or_else(|| "".to_string()), - description: "The node id in a flow (like 'a', 'b', or 'f')".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_OBJECT_PATH".to_string(), - value: object_path, - description: "Script or flow step execution unique path, useful for storing results in an external service".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_OIDC_JWT".to_string(), - value: jwt_token.unwrap_or_else(|| "".to_string()), - description: "OIDC JWT token (EE only)".to_string(), - is_custom: false, - }, - ContextualVariable { - name: "WM_WORKER_GROUP".to_string(), - value: WORKER_GROUP.clone(), - description: "name of the worker group the job is running on".to_string(), - is_custom: false, - }, - ].into_iter().chain( sqlx::query_as::<_, (String, String)>( - "SELECT name, value FROM workspace_env WHERE workspace_id = $1", - ) - .bind(w_id) - .fetch_all(db) - .await - .unwrap_or_default() - .into_iter().map(|(name, value)| ContextualVariable { - name, - value, - description: "Custom workspace environment variable".to_string(), - is_custom: true, - })).collect() + }, + ContextualVariable { + name: "WM_PERMISSIONED_AS".to_string(), + value: permissioned_as.to_string(), + description: "Fully Qualified (u/g) owner name of executor of the job".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_STATE_PATH".to_string(), + value: state_path.clone(), + description: "State resource path unique to a script and its trigger".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_FLOW_STEP_ID".to_string(), + value: step_id.unwrap_or_else(|| "".to_string()), + description: "The node id in a flow (like 'a', 'b', or 'f')".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_OBJECT_PATH".to_string(), + value: object_path, + description: "Script or flow step execution unique path, useful for storing results in an external service".to_string(), + is_custom: false, + }, + ContextualVariable { + name: "WM_WORKER_GROUP".to_string(), + value: WORKER_GROUP.clone(), + description: "name of the worker group the job is running on".to_string(), + is_custom: false, + }, +].into_iter().chain(custom_envs.into_iter().map(|(name, value)| ContextualVariable { + name, + value, + description: "Custom workspace environment variable".to_string(), + is_custom: true, +}) +).collect() } +async fn get_cached_workspace_envs(conn: &Connection, w_id: &str) -> Vec<(String, String)> { + let cached_envs_o = CUSTOM_ENVS_CACHE.get(w_id).and_then(|(ts, envs)| { + if ts > chrono::Utc::now().timestamp() - (60 * 15) { + Some(envs) + } else { + None + } + }); + + let custom_envs = if let Some(cached_envs) = cached_envs_o { + cached_envs + } else { + let custom_envs = match conn { + Connection::Sql(db) => sqlx::query_as::<_, (String, String)>( + "SELECT name, value FROM workspace_env WHERE workspace_id = $1", + ) + .bind(w_id) + .fetch_all(db) + .await + .unwrap_or_default(), + Connection::Http(client) => client + .get(&format!("/api/w/{w_id}/agent_workers/custom_envs")) + .await + .unwrap_or_default(), + }; + CUSTOM_ENVS_CACHE.insert( + w_id.to_string(), + (chrono::Utc::now().timestamp(), custom_envs.clone()), + ); + custom_envs + }; + custom_envs +} diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index b2429f5ae0..1f09176512 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -1,29 +1,57 @@ use anyhow::anyhow; +use axum::http::HeaderMap; use bytes::Bytes; use const_format::concatcp; use itertools::Itertools; use regex::Regex; +use reqwest_middleware::ClientWithMiddleware; use semver::Version; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::value::RawValue; +use sqlx::{types::Json, Pool, Postgres}; use std::{ cmp::Reverse, collections::{HashMap, HashSet}, - fs::File, + fs::{self, File}, io::Write, + panic::Location, path::{Component, Path, PathBuf}, str::FromStr, sync::{atomic::AtomicBool, Arc}, }; use tokio::sync::RwLock; +use uuid::Uuid; use windmill_macros::annotations; use crate::{ - error, global_settings::CUSTOM_TAGS_SETTING, indexer::TantivyIndexerSettings, server::Smtp, DB, + agent_workers::{PingJobStatusResponse, BASE_INTERNAL_URL}, + cache::{unwrap_or_error, RawNode, RawScript}, + error::{self, to_anyhow}, + global_settings::CUSTOM_TAGS_SETTING, + indexer::TantivyIndexerSettings, + server::Smtp, + KillpillSender, DB, }; +pub const DEFAULT_CLOUD_TIMEOUT: u64 = 900; +pub const DEFAULT_SELFHOSTED_TIMEOUT: u64 = 604800; // 7 days lazy_static::lazy_static! { - pub static ref WORKER_GROUP: String = std::env::var("WORKER_GROUP").unwrap_or_else(|_| "default".to_string()); + pub static ref WORKER_GROUP: String = std::env::var("WORKER_GROUP").unwrap_or_else(|_| { + #[cfg(not(feature = "enterprise"))] + { + "default".to_string() + } + + #[cfg(feature = "enterprise")] + { + if let Some(token) = crate::agent_workers::DECODED_AGENT_TOKEN.as_ref() { + token.worker_group.clone() + } else { + "default".to_string() + } + } + }); + pub static ref NO_LOGS: bool = std::env::var("NO_LOGS").ok().is_some_and(|x| x == "1" || x == "true"); pub static ref CGROUP_V2_PATH_RE: Regex = Regex::new(r#"(?m)^0::(/.*)$"#).unwrap(); @@ -49,6 +77,10 @@ lazy_static::lazy_static! { "rust".to_string(), "ansible".to_string(), "csharp".to_string(), + "nu".to_string(), + "java".to_string(), + "duckdb".to_string(), + // for related places search: ADD_NEW_LANG "dependency".to_string(), "flow".to_string(), "other".to_string() @@ -57,6 +89,15 @@ lazy_static::lazy_static! { pub static ref DEFAULT_TAGS_PER_WORKSPACE: AtomicBool = AtomicBool::new(false); pub static ref DEFAULT_TAGS_WORKSPACES: Arc>>> = Arc::new(RwLock::new(None)); + pub static ref MAX_TIMEOUT: u64 = std::env::var("TIMEOUT") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or_else(|| if *CLOUD_HOSTED { DEFAULT_CLOUD_TIMEOUT } else { DEFAULT_SELFHOSTED_TIMEOUT }); + + pub static ref SCRIPT_TOKEN_EXPIRY: u64 = std::env::var("SCRIPT_TOKEN_EXPIRY") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(*MAX_TIMEOUT); pub static ref WORKER_CONFIG: Arc> = Arc::new(RwLock::new(WorkerConfig { worker_tags: Default::default(), @@ -88,7 +129,7 @@ lazy_static::lazy_static! { pub static ref ALL_TAGS: Arc>> = Arc::new(RwLock::new(vec![])); - static ref CUSTOM_TAG_REGEX: Regex = Regex::new(r"^([\w-]+)\(((?:[\w-])+\+?)+\)$").unwrap(); + static ref CUSTOM_TAG_REGEX: Regex = Regex::new(r"^([\w-]+)\(((?:[\w-]+\+)*[\w-]+)\)$").unwrap(); pub static ref DISABLE_BUNDLING: bool = std::env::var("DISABLE_BUNDLING") .ok() @@ -111,6 +152,98 @@ pub const ROOT_CACHE_NOMOUNT_DIR: &str = concatcp!(TMP_DIR, "/cache_nomount/"); pub static MIN_VERSION_IS_LATEST: AtomicBool = AtomicBool::new(false); +#[derive(Clone)] +pub struct HttpClient(pub ClientWithMiddleware); + +impl HttpClient { + pub async fn post( + &self, + url: &str, + headers: Option, + body: &T, + ) -> anyhow::Result { + let response_builder = self + .0 + .post(format!("{}{}", *BASE_INTERNAL_URL, url)) + .json(body); + + let response_builder = match headers { + Some(headers) => response_builder.headers(headers), + None => response_builder, + }; + + let response = response_builder + .send() + .await + .map_err(|e| anyhow::anyhow!(e))?; + let status = response.status(); + if status.is_success() { + Ok(response.json().await?) + } else { + Err(anyhow::anyhow!(format!( + "HTTP agent request POST {} failed {}", + url, + response.status() + ))) + } + } + + pub async fn get(&self, url: &str) -> anyhow::Result { + let response = self + .0 + .get(format!("{}{}", *BASE_INTERNAL_URL, url)) + .send() + .await + .map_err(|e| anyhow::anyhow!(e))?; + let status = response.status(); + if status.is_success() { + Ok(response.json().await?) + } else { + Err(anyhow::anyhow!(format!( + "HTTP agent request GET {} failed {}", + url, + response.status() + ))) + } + } +} + +#[derive(Clone)] +pub enum Connection { + Sql(Pool), + Http(HttpClient), +} + +impl std::fmt::Debug for Connection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Connection::Sql(_) => write!(f, "Sql"), + Connection::Http(_) => write!(f, "Http"), + } + } +} + +impl Connection { + pub fn as_sql(&self) -> Option<&Pool> { + match self { + Connection::Sql(db) => Some(db), + Connection::Http(_) => None, + } + } +} + +impl From> for Connection { + fn from(value: Pool) -> Self { + Connection::Sql(value) + } +} + +impl From<&Pool> for Connection { + fn from(value: &Pool) -> Self { + Connection::Sql(value.clone()) + } +} + fn format_pull_query(peek: String) -> String { let r = format!( "WITH peek AS ( @@ -123,75 +256,86 @@ fn format_pull_query(peek: String) -> String { worker = $1 WHERE id = (SELECT id FROM peek) RETURNING - started_at, scheduled_for, running, - canceled_by, canceled_reason, canceled_by IS NOT NULL AS canceled, - suspend, suspend_until + started_at, scheduled_for, + canceled_by, canceled_reason, worker ), r AS NOT MATERIALIZED ( UPDATE v2_job_runtime SET ping = now() WHERE id = (SELECT id FROM peek) ), j AS NOT MATERIALIZED ( SELECT - id, workspace_id, parent_job, created_by, created_at, runnable_id AS script_hash, - runnable_path AS script_path, args, kind AS job_kind, - CASE WHEN trigger_kind = 'schedule' THEN trigger END AS schedule_path, - permissioned_as, permissioned_as_email AS email, script_lang AS language, - flow_innermost_root_job AS root_job, flow_step_id, flow_step_id IS NOT NULL AS is_flow_step, + id, workspace_id, parent_job, created_by, created_at, runnable_id, + runnable_path, args, kind, trigger, trigger_kind, + permissioned_as, permissioned_as_email, script_lang, + flow_innermost_root_job, flow_step_id, same_worker, pre_run_error, visible_to_owner, tag, concurrent_limit, concurrency_time_window_s, timeout, cache_ttl, priority, raw_code, raw_lock, raw_flow, script_entrypoint_override, preprocessed FROM v2_job WHERE id = (SELECT id FROM peek) - ) SELECT id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for, - running, script_hash, script_path, args, null as logs, canceled, canceled_by, - canceled_reason, null as last_ping, job_kind, schedule_path, permissioned_as, - flow_status, is_flow_step, language, suspend, suspend_until, - same_worker, pre_run_error, email, visible_to_owner, null as mem_peak, - root_job, flow_leaf_jobs as leaf_jobs, tag, concurrent_limit, concurrency_time_window_s, - timeout, flow_step_id, cache_ttl, priority, raw_code, raw_lock, raw_flow, - script_entrypoint_override, preprocessed + ) SELECT j.id, j.workspace_id, j.parent_job, j.created_by, started_at, scheduled_for, + j.runnable_id, j.runnable_path, j.args, canceled_by, + canceled_reason, j.kind, j.trigger, j.trigger_kind, j.permissioned_as, + flow_status, j.script_lang, + j.same_worker, j.pre_run_error, j.visible_to_owner, + j.tag, j.concurrent_limit, j.concurrency_time_window_s, j.flow_innermost_root_job, + j.timeout, j.flow_step_id, j.cache_ttl, j.priority, j.raw_code, j.raw_lock, j.raw_flow, + j.script_entrypoint_override, j.preprocessed, pj.runnable_path as parent_runnable_path, + COALESCE(p.email, j.permissioned_as_email) as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin, + p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders FROM q, j - LEFT JOIN v2_job_status f USING (id)", + LEFT JOIN v2_job_status f USING (id) + LEFT JOIN job_perms p ON p.job_id = j.id + LEFT JOIN v2_job pj ON j.parent_job = pj.id", peek ); tracing::debug!("pull query: {}", r); r } -pub async fn make_suspended_pull_query(wc: &WorkerConfig) { - if wc.worker_tags.len() == 0 { - tracing::error!("Empty tags in worker tags, skipping"); - return; - } - let query = format_pull_query(format!( +pub fn make_suspended_pull_query(tags: &[String]) -> String { + format_pull_query(format!( "SELECT id FROM v2_job_queue WHERE suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now()) AND tag IN ({}) ORDER BY priority DESC NULLS LAST, created_at FOR UPDATE SKIP LOCKED LIMIT 1", - wc.worker_tags.iter().map(|x| format!("'{x}'")).join(", ") - )); + tags.iter().map(|x| format!("'{x}'")).join(", ") + )) +} +// pub async fn make_suspended +pub async fn store_suspended_pull_query(wc: &WorkerConfig) { + if wc.worker_tags.len() == 0 { + tracing::error!("Empty tags in worker tags, skipping"); + return; + } + let query = make_suspended_pull_query(&wc.worker_tags); let mut l = WORKER_SUSPENDED_PULL_QUERY.write().await; *l = query; } -pub async fn make_pull_query(wc: &WorkerConfig) { +pub fn make_pull_query(tags: &[String]) -> String { + let query = format_pull_query(format!( + "SELECT id + FROM v2_job_queue + WHERE running = false AND tag IN ({}) AND scheduled_for <= now() + ORDER BY priority DESC NULLS LAST, scheduled_for + FOR UPDATE SKIP LOCKED + LIMIT 1", + tags.iter().map(|x| format!("'{x}'")).join(", ") + )); + query +} + +pub async fn store_pull_query(wc: &WorkerConfig) { let mut queries = vec![]; for tags in wc.priority_tags_sorted.iter() { if tags.tags.len() == 0 { tracing::error!("Empty tags in priority tags, skipping"); continue; } - let query = format_pull_query(format!( - "SELECT id - FROM v2_job_queue - WHERE running = false AND tag IN ({}) AND scheduled_for <= now() - ORDER BY priority DESC NULLS LAST, scheduled_for - FOR UPDATE SKIP LOCKED - LIMIT 1", - tags.tags.iter().map(|x| format!("'{x}'")).join(", ") - )); + let query = make_pull_query(&tags.tags); queries.push(query); } let mut l = WORKER_PULL_QUERIES.write().await; @@ -247,12 +391,8 @@ fn normalize_path(path: &Path) -> PathBuf { } ret } -pub fn write_file_at_user_defined_location( - job_dir: &str, - user_defined_path: &str, - content: &str, - mode: Option, -) -> error::Result { + +pub fn is_allowed_file_location(job_dir: &str, user_defined_path: &str) -> error::Result { let job_dir = Path::new(job_dir); let user_path = PathBuf::from(user_defined_path); @@ -271,6 +411,17 @@ pub fn write_file_at_user_defined_location( .into()); } + Ok(normalized_full_path) +} + +pub fn write_file_at_user_defined_location( + job_dir: &str, + user_defined_path: &str, + content: &str, + mode: Option, +) -> error::Result { + let normalized_full_path = is_allowed_file_location(job_dir, user_defined_path)?; + let full_path = normalized_full_path.as_path(); if let Some(parent_dir) = full_path.parent() { std::fs::create_dir_all(parent_dir)?; @@ -375,6 +526,8 @@ fn parse_file(path: &str) -> Option { pub struct PythonAnnotations { pub no_cache: bool, pub no_postinstall: bool, + pub py_select_latest: bool, + pub skip_result_postprocessing: bool, pub py310: bool, pub py311: bool, pub py312: bool, @@ -398,27 +551,68 @@ pub struct SqlAnnotations { pub struct BashAnnotations { pub docker: bool, } +/// length = 5 +/// value = "foo" +/// output = "foo " +/// 12345 +pub fn pad_string(value: &str, total_length: usize) -> String { + if value.len() >= total_length { + value.to_string() // Return the original string if it's already long enough + } else { + let padding_needed = total_length - value.len(); + format!("{value}{}", " ".repeat(padding_needed)) // Pad with spaces + } +} +pub fn copy_dir_recursively(src: &Path, dst: &Path) -> error::Result<()> { + if !dst.exists() { + fs::create_dir_all(dst)?; + } -pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) { + tracing::debug!("Copying recursively from {:?} to {:?}", src, dst); + + for entry in fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + + if src_path.is_dir() && !src_path.is_symlink() { + copy_dir_recursively(&src_path, &dst_path)?; + } else { + fs::copy(&src_path, &dst_path)?; + } + } + + tracing::debug!("Finished copying recursively from {:?} to {:?}", src, dst); + + Ok(()) +} + +pub async fn load_cache(bin_path: &str, _remote_path: &str, is_dir: bool) -> (bool, String) { if tokio::fs::metadata(&bin_path).await.is_ok() { (true, format!("loaded from local cache: {}\n", bin_path)) } else { #[cfg(all(feature = "enterprise", feature = "parquet"))] - if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone() - { + if let Some(os) = crate::s3_helpers::get_object_store().await { let started = std::time::Instant::now(); use crate::s3_helpers::attempt_fetch_bytes; if let Ok(mut x) = attempt_fetch_bytes(os, _remote_path).await { - if let Err(e) = write_binary_file(bin_path, &mut x) { - tracing::error!("could not write bundle/bin file locally: {e:?}"); - return ( - false, - "error writing bundle/bin file from object store".to_string(), - ); + if is_dir { + if let Err(e) = extract_tar(x, bin_path).await { + tracing::error!("could not write tar archive locally: {e:?}"); + return ( + false, + "error writing tar archive from object store".to_string(), + ); + } + } else { + if let Err(e) = write_binary_file(bin_path, &mut x) { + tracing::error!("could not write bundle/bin file locally: {e:?}"); + return ( + false, + "error writing bundle/bin file from object store".to_string(), + ); + } } tracing::info!("loaded from object store {}", bin_path); return ( @@ -431,6 +625,7 @@ pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) { ); } } + let _ = is_dir; (false, "".to_string()) } } @@ -440,11 +635,7 @@ pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool { return true; } else { #[cfg(all(feature = "enterprise", feature = "parquet"))] - if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone() - { + if let Some(os) = crate::s3_helpers::get_object_store().await { return os .get(&object_store::path::Path::from(_remote_path)) .await @@ -458,20 +649,39 @@ pub async fn save_cache( local_cache_path: &str, _remote_cache_path: &str, origin: &str, + is_dir: bool, ) -> crate::error::Result { let mut _cached_to_s3 = false; #[cfg(all(feature = "enterprise", feature = "parquet"))] - if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone() - { + if let Some(os) = crate::s3_helpers::get_object_store().await { use object_store::path::Path; + let file_to_cache = if is_dir { + let tar_path = format!( + "{ROOT_CACHE_DIR}/tar/{}_tar.tar", + local_cache_path + .split("/") + .last() + .unwrap_or(&uuid::Uuid::new_v4().to_string()) + ); + let tar_file = std::fs::File::create(&tar_path)?; + let mut tar = tar::Builder::new(tar_file); + tar.append_dir_all(".", &origin)?; + let tar_metadata = tokio::fs::metadata(&tar_path).await; + if tar_metadata.is_err() || tar_metadata.as_ref().unwrap().len() == 0 { + tracing::info!("Failed to tar cache: {origin}"); + return Err(error::Error::ExecutionErr(format!( + "Failed to tar cache: {origin}" + ))); + } + tar_path + } else { + origin.to_owned() + }; if let Err(e) = os .put( &Path::from(_remote_cache_path), - std::fs::read(origin)?.into(), + std::fs::read(&file_to_cache)?.into(), ) .await { @@ -481,12 +691,19 @@ pub async fn save_cache( ); } else { _cached_to_s3 = true; + if is_dir { + tokio::fs::remove_dir_all(&file_to_cache).await?; + } } } // if !*CLOUD_HOSTED { if true { - std::fs::copy(origin, local_cache_path)?; + if is_dir { + copy_dir_recursively(&PathBuf::from(origin), &PathBuf::from(local_cache_path))?; + } else { + std::fs::copy(origin, local_cache_path)?; + } Ok(format!( "\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n", local_cache_path @@ -501,6 +718,31 @@ pub async fn save_cache( } } +#[cfg(all(feature = "enterprise", feature = "parquet"))] +pub async fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> { + use std::time::Instant; + + use bytes::Buf; + use tokio::fs::{self}; + + let start: Instant = Instant::now(); + fs::create_dir_all(&folder).await?; + + let mut ar = tar::Archive::new(tar.reader()); + + if let Err(e) = ar.unpack(folder) { + tracing::info!("Failed to untar to {folder}. Error: {:?}", e); + fs::remove_dir_all(&folder).await?; + return Err(error::Error::ExecutionErr(format!( + "Failed to untar tar {folder}" + ))); + } + tracing::info!( + "Finished extracting tar to {folder}. Took {}ms", + start.elapsed().as_millis(), + ); + Ok(()) +} #[cfg(all(feature = "enterprise", feature = "parquet"))] fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> { use std::fs::{File, Permissions}; @@ -651,24 +893,33 @@ pub fn get_windmill_memory_usage() -> Option { } } -pub async fn update_min_version<'c, E: sqlx::Executor<'c, Database = sqlx::Postgres>>( - executor: E, -) -> bool { +pub async fn update_min_version(conn: &Connection) -> bool { use crate::utils::{GIT_SEM_VERSION, GIT_VERSION}; - // fetch all pings with a different version than self from the last 5 minutes. - let pings = sqlx::query_scalar!( - "SELECT wm_version FROM worker_ping WHERE wm_version != $1 AND ping_at > now() - interval '5 minutes'", - GIT_VERSION - ).fetch_all(executor).await.unwrap_or_default(); - let cur_version = GIT_SEM_VERSION.clone(); - let min_version = pings - .iter() - .filter(|x| !x.is_empty()) - .filter_map(|x| semver::Version::parse(if x.starts_with('v') { &x[1..] } else { x }).ok()) - .min() - .unwrap_or_else(|| cur_version.clone()); + + let min_version = match conn { + Connection::Sql(pool) => { + // fetch all pings with a different version than self from the last 5 minutes. + let pings = sqlx::query_scalar!( + "SELECT wm_version FROM worker_ping WHERE wm_version != $1 AND ping_at > now() - interval '5 minutes'", + GIT_VERSION + ).fetch_all(pool).await.unwrap_or_default(); + + pings + .iter() + .filter(|x| !x.is_empty()) + .filter_map(|x| { + semver::Version::parse(if x.starts_with('v') { &x[1..] } else { x }).ok() + }) + .min() + .unwrap_or_else(|| cur_version.clone()) + } + Connection::Http(_) => { + // TODO: get min version from server, for now we use the current version. Min version should be of no interest for http mode workers + cur_version.clone() + } + }; if min_version != cur_version { tracing::info!("Minimal worker version: {min_version}"); @@ -683,40 +934,333 @@ pub async fn update_min_version<'c, E: sqlx::Executor<'c, Database = sqlx::Postg min_version >= cur_version } -pub async fn update_ping(worker_instance: &str, worker_name: &str, ip: &str, db: &DB) { - let (tags, dw) = { - let wc = WORKER_CONFIG.read().await.clone(); - ( - wc.worker_tags, - wc.dedicated_worker - .as_ref() - .map(|x| format!("{}:{}", x.workspace_id, x.path)), - ) - }; +#[derive(Serialize, Deserialize)] +pub enum PingType { + Initial, + MainLoop, + Job, + InitScript, +} +#[derive(Serialize, Deserialize)] +pub struct Ping { + pub last_job_executed: Option, + pub last_job_workspace_id: Option, + pub worker_instance: Option, + pub ip: Option, + pub tags: Option>, + pub dw: Option, + pub version: Option, + pub vcpus: Option, + pub memory: Option, + pub memory_usage: Option, + pub wm_memory_usage: Option, + pub jobs_executed: Option, + pub occupancy_rate: Option, + pub occupancy_rate_15s: Option, + pub occupancy_rate_5m: Option, + pub occupancy_rate_30m: Option, + pub ping_type: PingType, +} +pub async fn update_ping_http( + insert_ping: Ping, + worker_name: &str, + worker_group: &str, + db: &DB, +) -> anyhow::Result<()> { + // tracing::info!("update ping: {}", insert_ping.tags.join(",")); + match insert_ping.ping_type { + PingType::MainLoop => { + update_worker_ping_main_loop_query( + worker_name, + insert_ping.tags.unwrap_or_default().as_slice(), + insert_ping.vcpus, + insert_ping.memory, + insert_ping.jobs_executed, + insert_ping.occupancy_rate, + insert_ping.memory_usage, + insert_ping.wm_memory_usage, + insert_ping.occupancy_rate_15s, + insert_ping.occupancy_rate_5m, + insert_ping.occupancy_rate_30m, + db, + ) + .await? + } + PingType::Initial => { + if insert_ping.worker_instance.is_none() + || insert_ping.version.is_none() + || insert_ping.ip.is_none() + { + return Err(anyhow::anyhow!( + "Worker instance, version and ip are required" + )); + } - let vcpus = get_vcpus(); - let memory = get_memory(); + insert_ping_query( + &insert_ping.worker_instance.unwrap(), + &worker_name, + worker_group, + &insert_ping.ip.unwrap(), + insert_ping.tags.unwrap_or_default().as_slice(), + insert_ping.dw, + &insert_ping.version.unwrap(), + insert_ping.vcpus, + insert_ping.memory, + db, + ) + .await?; + } + PingType::Job => { + update_worker_ping_from_job_query( + &insert_ping.last_job_executed.unwrap_or_default(), + &insert_ping.last_job_workspace_id.unwrap_or_default(), + worker_name, + insert_ping.memory_usage, + insert_ping.wm_memory_usage, + insert_ping.occupancy_rate, + insert_ping.occupancy_rate_15s, + insert_ping.occupancy_rate_5m, + insert_ping.occupancy_rate_30m, + db, + ) + .await?; + } + PingType::InitScript => { + update_ping_for_failed_init_script_query( + worker_name, + insert_ping.last_job_executed.unwrap_or_default(), + db, + ) + .await? + } + } + Ok(()) +} +#[derive(Debug, Serialize, Deserialize)] +pub struct JobCancelled { + pub canceled_by: String, + pub reason: String, +} + +pub async fn set_job_cancelled_query( + job_id: Uuid, + db: &DB, + canceled_by: &str, + reason: &str, +) -> anyhow::Result<()> { + sqlx::query!( + "UPDATE v2_job_queue + SET canceled_by = $1 + , canceled_reason = $2 +WHERE id = $3", + canceled_by, + reason, + job_id + ) + .execute(db) + .await?; + Ok(()) +} + +pub async fn update_ping_for_failed_init_script_query( + worker_name: &str, + last_job_id: Uuid, + db: &DB, +) -> anyhow::Result<()> { + sqlx::query!( + "UPDATE worker_ping SET +ping_at = now(), +jobs_executed = 1, +current_job_id = $1, +current_job_workspace_id = 'admins' +WHERE worker = $2", + last_job_id, + worker_name + ) + .execute(db) + .await?; + Ok(()) +} + +pub async fn fetch_flow_node_query( + db: &DB, + id: i64, + loc: &'static Location<'_>, +) -> error::Result { + let r = sqlx::query!( + "SELECT \ + code AS \"raw_code: String\", \ + lock AS \"raw_lock: String\", \ + flow AS \"raw_flow: Json>\" \ + FROM flow_node WHERE id = $1 LIMIT 1", + id, + ) + .fetch_optional(db) + .await + .map_err(Into::into) + .and_then(unwrap_or_error(loc, "Flow node", id)) + .map(|r| RawNode { + raw_code: r.raw_code, + raw_lock: r.raw_lock, + raw_flow: r.raw_flow.map(|Json(raw_flow)| raw_flow), + })?; + Ok(r) +} + +pub async fn fetch_raw_script_from_app_query( + db: &DB, + id: i64, + loc: &'static Location<'_>, +) -> error::Result { + sqlx::query!( + "SELECT lock, code FROM app_script WHERE id = $1 LIMIT 1", + id, + ) + .fetch_optional(db) + .await + .map_err(Into::into) + .and_then(unwrap_or_error(&loc, "Application script", id)) + .map(|r| RawScript { content: r.code, lock: r.lock, meta: None }) +} + +pub async fn insert_ping_query( + worker_instance: &str, + worker_name: &str, + worker_group: &str, + ip: &str, + tags: &[String], + dw: Option, + version: &str, + vcpus: Option, + memory: Option, + db: &DB, +) -> anyhow::Result<()> { sqlx::query!( "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) DO UPDATE set ip = $3, custom_tags = $4, worker_group = $5", worker_instance, worker_name, ip, - tags.as_slice(), - *WORKER_GROUP, + tags, + worker_group, dw, - crate::utils::GIT_VERSION, + version, vcpus, memory + ) + .execute(db) + .await?; + Ok(()) +} + +pub async fn update_worker_ping_from_job_query( + job_id: &Uuid, + w_id: &str, + worker_name: &str, + memory_usage: Option, + wm_memory_usage: Option, + occupancy_rate: Option, + occupancy_rate_15s: Option, + occupancy_rate_5m: Option, + occupancy_rate_30m: Option, + db: &DB, +) -> anyhow::Result<()> { + sqlx::query!( + "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4, + occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5", + job_id, + w_id, + memory_usage, + wm_memory_usage, + worker_name, + occupancy_rate, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, ) .execute(db) - .await - .expect("insert worker_ping initial value"); + .await?; + Ok(()) } +pub async fn update_job_ping_query( + job_id: &Uuid, + db: &DB, + mem_peak: Option, +) -> anyhow::Result { + let ro = sqlx::query!( + "UPDATE v2_job_runtime r SET + memory_peak = $1, + ping = now() + FROM v2_job_queue q + WHERE r.id = $2 AND q.id = r.id + RETURNING canceled_by, canceled_reason", + mem_peak, + job_id + ) + .map(|x| PingJobStatusResponse { + canceled_by: x.canceled_by, + canceled_reason: x.canceled_reason, + already_completed: false, + }) + .fetch_optional(db) + .await; + + // TODO: add memory metrics to memory time series + + if let Ok(r) = ro { + if let Some(i) = r { + Ok(i) + } else { + Err(anyhow::anyhow!("Job not found")) + } + } else { + Err(to_anyhow(ro.unwrap_err())) + } +} + +pub async fn update_worker_ping_main_loop_query( + worker_name: &str, + tags: &[String], + vcpus: Option, + memory: Option, + jobs_executed: Option, + occupancy_rate: Option, + memory_usage: Option, + wm_memory_usage: Option, + occupancy_rate_15s: Option, + occupancy_rate_5m: Option, + occupancy_rate_30m: Option, + db: &DB, +) -> anyhow::Result<()> { + sqlx::query!( + "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, + occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus), + memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", + jobs_executed, + tags, + occupancy_rate, + memory_usage, + wm_memory_usage, + worker_name, + vcpus, + memory, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, + ) + .execute(db) + .await?; + Ok(()) +} + +// "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, +// occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus), +// memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", + pub async fn load_worker_config( db: &DB, - killpill_tx: tokio::sync::broadcast::Sender<()>, + killpill_tx: KillpillSender, ) -> error::Result { tracing::info!("Loading config from WORKER_GROUP: {}", *WORKER_GROUP); let mut config: WorkerConfigOpt = sqlx::query_scalar!( @@ -750,7 +1294,7 @@ pub async fn load_worker_config( .map(|x| { let splitted = x.split(':').to_owned().collect_vec(); if splitted.len() != 2 { - killpill_tx.send(()).expect("send"); + killpill_tx.send(); return Err(anyhow::anyhow!( "Invalid dedicated_worker format. Got {x}, expects :" )); @@ -851,18 +1395,66 @@ pub async fn load_worker_config( tracing::debug!("Custom tags priority set: {:?}", priority_tags_sorted); let env_vars_static = config.env_vars_static.unwrap_or_default().clone(); - let resolved_env_vars: HashMap = env_vars_static - .keys() - .map(|x| x.to_string()) - .chain(config.env_vars_allowlist.unwrap_or_default()) - .chain( - std::env::var("WHITELIST_ENVS") - .ok() - .map(|x| x.split(',').map(|x| x.to_string()).collect_vec()) - .unwrap_or_default() - .into_iter(), - ) - .sorted() + let resolved_env_vars: HashMap = load_env_vars( + config + .env_vars_allowlist + .unwrap_or_default() + .into_iter() + .chain(load_whitelist_env_vars_from_env()) + .chain(env_vars_static.keys().map(|x| x.to_string())), + &env_vars_static, + ); + + Ok(WorkerConfig { + worker_tags, + priority_tags_sorted, + dedicated_worker, + init_bash: config + .init_bash + .or_else(|| load_init_bash_from_env()) + .and_then(|x| if x.is_empty() { None } else { Some(x) }), + cache_clear: config.cache_clear, + pip_local_dependencies: config + .pip_local_dependencies + .or_else(|| load_pip_local_dependencies_from_env()), + additional_python_paths: config + .additional_python_paths + .or_else(|| load_additional_python_paths_from_env()), + env_vars: resolved_env_vars, + }) +} + +pub fn load_init_bash_from_env() -> Option { + std::env::var("INIT_SCRIPT") + .ok() + .and_then(|x| if x.is_empty() { None } else { Some(x) }) +} + +pub fn load_pip_local_dependencies_from_env() -> Option> { + std::env::var("PIP_LOCAL_DEPENDENCIES") + .ok() + .map(|x| x.split(',').map(|x| x.to_string()).collect_vec()) +} + +pub fn load_additional_python_paths_from_env() -> Option> { + std::env::var("ADDITIONAL_PYTHON_PATHS") + .ok() + .map(|x| x.split(':').map(|x| x.to_string()).collect_vec()) +} + +pub fn load_whitelist_env_vars_from_env() -> std::vec::IntoIter { + std::env::var("WHITELIST_ENVS") + .ok() + .map(|x| x.split(',').map(|x| x.to_string()).collect_vec()) + .unwrap_or_default() + .into_iter() +} + +pub fn load_env_vars( + iter: impl Iterator, + env_vars_static: &HashMap, +) -> HashMap { + iter.sorted() .unique() .map(|envvar_name| { ( @@ -875,34 +1467,7 @@ pub async fn load_worker_config( }), ) }) - .collect(); - - Ok(WorkerConfig { - worker_tags, - priority_tags_sorted, - dedicated_worker, - init_bash: config - .init_bash - .or_else(|| std::env::var("INIT_SCRIPT").ok()) - .and_then(|x| if x.is_empty() { None } else { Some(x) }), - cache_clear: config.cache_clear, - pip_local_dependencies: config.pip_local_dependencies.or_else(|| { - let pip_local_dependencies = std::env::var("PIP_LOCAL_DEPENDENCIES") - .ok() - .map(|x| x.split(',').map(|x| x.to_string()).collect()); - if pip_local_dependencies == Some(vec!["".to_string()]) { - None - } else { - pip_local_dependencies - } - }), - additional_python_paths: config.additional_python_paths.or_else(|| { - std::env::var("ADDITIONAL_PYTHON_PATHS") - .ok() - .map(|x| x.split(':').map(|x| x.to_string()).collect()) - }), - env_vars: resolved_env_vars, - }) + .collect() } #[derive(Clone, PartialEq, Debug)] @@ -911,7 +1476,7 @@ pub struct WorkspacedPath { pub path: String, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct WorkerConfigOpt { pub worker_tags: Option>, pub priority_tags: Option>, @@ -954,7 +1519,7 @@ pub struct WorkerConfig { impl std::fmt::Debug for WorkerConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, init_bash: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?} }}", + write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, init_bash: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?} }}", self.worker_tags, self.priority_tags_sorted, self.dedicated_worker, self.init_bash, self.cache_clear, self.additional_python_paths, self.pip_local_dependencies, self.env_vars.iter().map(|(k, v)| format!("{}: {}{} ({} chars)", k, &v[..3.min(v.len())], "***", v.len())).collect::>().join(", ")) } } diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 8fd5471a00..1238d5b243 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -1,3 +1,4 @@ +use quick_cache::sync::Cache; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Default)] @@ -27,7 +28,7 @@ pub enum ObjectType { ResourceType, User, Group, - Trigger + Trigger, } #[derive(Serialize, Deserialize, Debug)] @@ -38,3 +39,21 @@ pub struct GitRepositorySettings { pub group_by_folder: Option, pub exclude_types_override: Option>, } + +lazy_static::lazy_static! { + pub static ref IS_PREMIUM_CACHE: Cache = Cache::new(5000); +} + +#[cfg(feature = "cloud")] +pub async fn is_premium_workspace(_db: &crate::DB, _w_id: &str) -> bool { + let cached = IS_PREMIUM_CACHE.get(_w_id); + if let Some(cached) = cached { + return cached; + } + let premium = sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", _w_id) + .fetch_one(_db) + .await + .unwrap_or(false); + IS_PREMIUM_CACHE.insert(_w_id.to_string(), premium); + premium +} diff --git a/backend/windmill-git-sync/Cargo.toml b/backend/windmill-git-sync/Cargo.toml index d5a8412fae..6a0499639a 100644 --- a/backend/windmill-git-sync/Cargo.toml +++ b/backend/windmill-git-sync/Cargo.toml @@ -9,6 +9,7 @@ name = "windmill_git_sync" path = "./src/lib.rs" [features] +private = [] enterprise = ["windmill-queue/enterprise", "windmill-common/enterprise"] default = [] diff --git a/backend/windmill-git-sync/src/git_sync_ee.rs b/backend/windmill-git-sync/src/git_sync_oss.rs similarity index 69% rename from backend/windmill-git-sync/src/git_sync_ee.rs rename to backend/windmill-git-sync/src/git_sync_oss.rs index cc245d3d0c..70e2b53640 100644 --- a/backend/windmill-git-sync/src/git_sync_ee.rs +++ b/backend/windmill-git-sync/src/git_sync_oss.rs @@ -1,7 +1,14 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::git_sync_ee::*; + +#[cfg(not(feature = "private"))] use windmill_common::error::Result; +#[cfg(not(feature = "private"))] use crate::{DeployedObject, DB}; +#[cfg(not(feature = "private"))] pub async fn handle_deployment_metadata<'c>( _email: &str, _created_by: &str, diff --git a/backend/windmill-git-sync/src/lib.rs b/backend/windmill-git-sync/src/lib.rs index 707203accf..d4e4ebf71f 100644 --- a/backend/windmill-git-sync/src/lib.rs +++ b/backend/windmill-git-sync/src/lib.rs @@ -10,9 +10,11 @@ use sqlx::{Pool, Postgres}; use windmill_common::scripts::ScriptHash; +#[cfg(feature = "private")] pub mod git_sync_ee; +pub mod git_sync_oss; -pub use git_sync_ee::handle_deployment_metadata; +pub use git_sync_oss::handle_deployment_metadata; pub type DB = Pool; #[derive(Clone, Debug)] @@ -27,6 +29,14 @@ pub enum DeployedObject { ResourceType { path: String }, User { email: String }, Group { name: String }, + HttpTrigger { path: String }, + WebsocketTrigger { path: String }, + KafkaTrigger { path: String }, + NatsTrigger { path: String }, + PostgresTrigger { path: String }, + MqttTrigger { path: String }, + SqsTrigger { path: String }, + GcpTrigger { path: String }, } impl DeployedObject { @@ -42,6 +52,14 @@ impl DeployedObject { DeployedObject::ResourceType { path, .. } => path.to_owned(), DeployedObject::User { email } => format!("users/{email}"), DeployedObject::Group { name } => format!("groups/{name}"), + DeployedObject::HttpTrigger { path } => path.to_owned(), + DeployedObject::WebsocketTrigger { path } => path.to_owned(), + DeployedObject::KafkaTrigger { path } => path.to_owned(), + DeployedObject::NatsTrigger { path } => path.to_owned(), + DeployedObject::PostgresTrigger { path } => path.to_owned(), + DeployedObject::MqttTrigger { path } => path.to_owned(), + DeployedObject::SqsTrigger { path } => path.to_owned(), + DeployedObject::GcpTrigger { path } => path.to_owned(), } } @@ -64,6 +82,14 @@ impl DeployedObject { DeployedObject::ResourceType { .. } => None, DeployedObject::User { .. } => None, DeployedObject::Group { .. } => None, + DeployedObject::HttpTrigger { .. } => None, + DeployedObject::WebsocketTrigger { .. } => None, + DeployedObject::KafkaTrigger { .. } => None, + DeployedObject::NatsTrigger { .. } => None, + DeployedObject::PostgresTrigger { .. } => None, + DeployedObject::MqttTrigger { .. } => None, + DeployedObject::SqsTrigger { .. } => None, + DeployedObject::GcpTrigger { .. } => None, } } } diff --git a/backend/windmill-indexer/Cargo.toml b/backend/windmill-indexer/Cargo.toml index 14ab53beef..ea95d6237d 100644 --- a/backend/windmill-indexer/Cargo.toml +++ b/backend/windmill-indexer/Cargo.toml @@ -11,6 +11,7 @@ path = "src/lib.rs" [features] default = [] parquet = ["dep:object_store"] +private = [] enterprise = [] [dependencies] diff --git a/backend/windmill-indexer/src/completed_runs_ee.rs b/backend/windmill-indexer/src/completed_runs_oss.rs similarity index 66% rename from backend/windmill-indexer/src/completed_runs_ee.rs rename to backend/windmill-indexer/src/completed_runs_oss.rs index f5c6c98cf8..083f8744fa 100644 --- a/backend/windmill-indexer/src/completed_runs_ee.rs +++ b/backend/windmill-indexer/src/completed_runs_oss.rs @@ -1,17 +1,28 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::completed_runs_ee::*; + +#[cfg(not(feature = "private"))] use anyhow::anyhow; +#[cfg(not(feature = "private"))] use sqlx::{Pool, Postgres}; +#[cfg(not(feature = "private"))] use windmill_common::error::Error; +#[cfg(not(feature = "private"))] #[derive(Clone)] pub struct IndexReader; +#[cfg(not(feature = "private"))] #[derive(Clone)] pub struct IndexWriter; +#[cfg(not(feature = "private"))] pub async fn init_index(_db: &Pool) -> Result<(IndexReader, IndexWriter), Error> { Err(anyhow!("Cannot initialize index: not in EE").into()) } +#[cfg(not(feature = "private"))] pub async fn run_indexer( _db: Pool, mut _index_writer: IndexWriter, diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/backend/windmill-indexer/src/indexer_ee.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/windmill-indexer/src/indexer_oss.rs b/backend/windmill-indexer/src/indexer_oss.rs new file mode 100644 index 0000000000..198c14e526 --- /dev/null +++ b/backend/windmill-indexer/src/indexer_oss.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::indexer_ee::*; + + diff --git a/backend/windmill-indexer/src/lib.rs b/backend/windmill-indexer/src/lib.rs index 59c6a627f7..6c13b551d1 100644 --- a/backend/windmill-indexer/src/lib.rs +++ b/backend/windmill-indexer/src/lib.rs @@ -1,3 +1,9 @@ +#[cfg(feature = "private")] pub mod completed_runs_ee; +pub mod completed_runs_oss; +#[cfg(feature = "private")] pub mod indexer_ee; +pub mod indexer_oss; +#[cfg(feature = "private")] pub mod service_logs_ee; +pub mod service_logs_oss; diff --git a/backend/windmill-indexer/src/service_logs_ee.rs b/backend/windmill-indexer/src/service_logs_oss.rs similarity index 61% rename from backend/windmill-indexer/src/service_logs_ee.rs rename to backend/windmill-indexer/src/service_logs_oss.rs index 49a4f12447..b6e9017f88 100644 --- a/backend/windmill-indexer/src/service_logs_ee.rs +++ b/backend/windmill-indexer/src/service_logs_oss.rs @@ -1,20 +1,32 @@ -use anyhow::anyhow; -use sqlx::{Pool, Postgres}; -use windmill_common::error::Error; +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::service_logs_ee::*; +#[cfg(not(feature = "private"))] +use anyhow::anyhow; +#[cfg(not(feature = "private"))] +use sqlx::{Pool, Postgres}; +#[cfg(not(feature = "private"))] +use windmill_common::error::Error; +#[cfg(not(feature = "private"))] +use windmill_common::KillpillSender; #[derive(Clone)] +#[cfg(not(feature = "private"))] pub struct ServiceLogIndexReader; #[derive(Clone)] +#[cfg(not(feature = "private"))] pub struct ServiceLogIndexWriter; +#[cfg(not(feature = "private"))] pub async fn init_index( _db: &Pool, - mut _killpill_rx: tokio::sync::broadcast::Sender<()>, + mut _killpill_tx: KillpillSender, ) -> Result<(ServiceLogIndexReader, ServiceLogIndexWriter), Error> { Err(anyhow!("Cannot initialize index: not in EE").into()) } +#[cfg(not(feature = "private"))] pub async fn run_indexer( _db: Pool, mut _index_writer: ServiceLogIndexWriter, diff --git a/backend/windmill-queue/Cargo.toml b/backend/windmill-queue/Cargo.toml index a90fa6ccf7..4458e8aa99 100644 --- a/backend/windmill-queue/Cargo.toml +++ b/backend/windmill-queue/Cargo.toml @@ -10,6 +10,7 @@ path = "src/lib.rs" [features] default = [] +private = [] enterprise = ["windmill-common/enterprise"] cloud = [] benchmark = ["windmill-common/benchmark"] diff --git a/backend/windmill-queue/src/flow_status.rs b/backend/windmill-queue/src/flow_status.rs new file mode 100644 index 0000000000..3bc6b3c815 --- /dev/null +++ b/backend/windmill-queue/src/flow_status.rs @@ -0,0 +1,135 @@ +use uuid::Uuid; +use windmill_common::{ + error::{self, Error}, + utils::WarnAfterExt, + DB, +}; + +#[derive(Debug, Copy, Clone)] +pub enum Step { + Step(usize), + PreprocessorStep, + FailureStep, +} + +impl Step { + pub fn from_i32_and_len(step: i32, len: usize) -> Self { + if step < 0 { + Step::PreprocessorStep + } else if (step as usize) < len { + Step::Step(step as usize) + } else { + Step::FailureStep + } + } +} + +pub async fn update_flow_status_in_progress( + db: &DB, + _w_id: &str, + flow: Uuid, + job_in_progress: Uuid, +) -> error::Result { + let step = get_step_of_flow_status(db, flow).await?; + match step { + Step::Step(step) => { + sqlx::query!( + "UPDATE v2_job_status SET + flow_status = jsonb_set( + jsonb_set(flow_status, ARRAY['modules', $3::INTEGER::TEXT, 'job'], to_jsonb($1::UUID::TEXT)), + ARRAY['modules', $3::INTEGER::TEXT, 'type'], + to_jsonb('InProgress'::text) + ) + WHERE id = $2", + job_in_progress, + flow, + step as i32 + ) + .execute(db) + .await?; + } + Step::PreprocessorStep => { + sqlx::query!( + "UPDATE v2_job_status SET + flow_status = jsonb_set( + jsonb_set(flow_status, ARRAY['preprocessor_module', 'job'], to_jsonb($1::UUID::TEXT)), + ARRAY['preprocessor_module', 'type'], + to_jsonb('InProgress'::text) + ) + WHERE id = $2", + job_in_progress, + flow + ) + .execute(db) + .await?; + } + Step::FailureStep => { + sqlx::query!( + "UPDATE v2_job_status SET + flow_status = jsonb_set( + jsonb_set(flow_status, ARRAY['failure_module', 'job'], to_jsonb($1::UUID::TEXT)), + ARRAY['failure_module', 'type'], + to_jsonb('InProgress'::text) + ) + WHERE id = $2", + job_in_progress, + flow + ) + .execute(db) + .await?; + } + } + + Ok(step) +} + +pub async fn update_workflow_as_code_status( + db: &DB, + id: &Uuid, + parent_job: &Uuid, +) -> error::Result<()> { + let _ = sqlx::query_scalar!( + "UPDATE v2_job_status SET + workflow_as_code_status = jsonb_set( + jsonb_set( + COALESCE(workflow_as_code_status, '{}'::jsonb), + array[$1], + COALESCE(workflow_as_code_status->$1, '{}'::jsonb) + ), + array[$1, 'started_at'], + to_jsonb(now()::text) + ) + WHERE id = $2", + id.to_string(), + parent_job + ) + .execute(db) + .warn_after_seconds(5) + .await + .inspect_err(|e| { + tracing::error!( + "Could not update parent job `started_at` in workflow as code status: {}", + e + ) + }); + Ok(()) +} + +// TODO: merge as a CTE +#[tracing::instrument(level = "trace", skip_all)] +async fn get_step_of_flow_status(db: &DB, id: Uuid) -> error::Result { + let r = sqlx::query!( + "SELECT (flow_status->'step')::integer as step, jsonb_array_length(flow_status->'modules') as len + FROM v2_job_status WHERE id = $1", + id + ) + .fetch_one(db) + .await + .map_err(|e| Error::internal_err(format!("fetching step flow status: {e:#}")))?; + + if let Some(step) = r.step { + Ok(Step::from_i32_and_len(step, r.len.unwrap_or(0) as usize)) + } else { + Err(Error::internal_err("step is null".to_string())) + } +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index c676d1e6f7..fe2e08d0a1 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -6,7 +6,7 @@ * LICENSE-AGPL for a copy of the license. */ -use std::{borrow::Borrow, collections::HashMap, sync::Arc, vec}; +use std::{collections::HashMap, sync::Arc, vec}; use anyhow::Context; use async_recursion::async_recursion; @@ -17,16 +17,24 @@ use itertools::Itertools; use prometheus::IntCounter; use regex::Regex; use reqwest::Client; +use serde::Deserialize; use serde::{ser::SerializeMap, Serialize}; use serde_json::{json, value::RawValue}; +use sqlx::PgExecutor; use sqlx::{types::Json, FromRow, Pool, Postgres, Transaction}; use tokio::{sync::RwLock, time::sleep}; use ulid::Ulid; use uuid::Uuid; -use windmill_audit::audit_ee::{audit_log, AuditAuthor}; +use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; +#[cfg(feature = "benchmark")] +use windmill_common::add_time; +use windmill_common::auth::JobPerms; +#[cfg(feature = "benchmark")] +use windmill_common::bench::BenchmarkIter; use windmill_common::utils::now_from_db; +use windmill_common::worker::{Connection, SCRIPT_TOKEN_EXPIRY}; use windmill_common::{ auth::{fetch_authed_from_permissioned_as, permissioned_as_to_username}, cache::{self, FlowData}, @@ -46,7 +54,7 @@ use windmill_common::{ users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL}, utils::{not_found_if_none, report_critical_error, StripPath, WarnAfterExt}, worker::{ - to_raw_value, CLOUD_HOSTED, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, + to_raw_value, CLOUD_HOSTED, DISABLE_FLOW_SCRIPT, MIN_VERSION_IS_AT_LEAST_1_432, MIN_VERSION_IS_AT_LEAST_1_440, NO_LOGS, WORKER_PULL_QUERIES, WORKER_SUSPENDED_PULL_QUERY, }, @@ -62,7 +70,10 @@ use windmill_common::BASE_URL; #[cfg(feature = "cloud")] use windmill_common::users::SUPERADMIN_SYNC_EMAIL; +use crate::flow_status::{update_flow_status_in_progress, update_workflow_as_code_status}; +use crate::jobs_oss::update_concurrency_counter; use crate::schedule::{get_schedule_opt, push_scheduled_job}; +use crate::tags::per_workspace_tag; #[cfg(feature = "prometheus")] lazy_static::lazy_static! { @@ -98,8 +109,6 @@ lazy_static::lazy_static! { .build().unwrap(); - pub static ref JOB_TOKEN: Option = std::env::var("JOB_TOKEN").ok(); - static ref JOB_ARGS_AUDIT_LOGS: bool = std::env::var("JOB_ARGS_AUDIT_LOGS") .ok() .and_then(|x| x.parse().ok()) @@ -121,12 +130,27 @@ const SCHEDULE_ERROR_HANDLER_USER_EMAIL: &str = "schedule_error_handler@windmill #[cfg(any(feature = "enterprise", feature = "cloud"))] const SCHEDULE_RECOVERY_HANDLER_USER_EMAIL: &str = "schedule_recovery_handler@windmill.dev"; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct CanceledBy { pub username: Option, pub reason: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JobCompleted { + pub job: Arc, + pub preprocessed_args: Option>>, + pub result: Arc>, + pub result_columns: Option>, + pub mem_peak: i32, + pub success: bool, + pub cached_res_path: Option, + pub token: String, + pub canceled_by: Option, + pub duration: Option, +} + + pub async fn cancel_single_job<'c>( username: &str, reason: Option, @@ -140,22 +164,24 @@ pub async fn cancel_single_job<'c>( let username = username.to_string(); let w_id = w_id.to_string(); let db = db.clone(); + tracing::info!("cancelling job {:?}", job_running.id); let job_running = job_running.clone(); tokio::task::spawn(async move { let reason: String = reason .clone() .unwrap_or_else(|| "unexplicited reasons".to_string()); let e = serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username}); + append_logs( &job_running.id, w_id.to_string(), format!("canceled by {username}: (force cancel: {force_cancel})"), - &db, + &Connection::from(db.clone()), ) .await; let add_job = add_completed_job_error( &db, - &job_running, + &MiniPulledJob::from(&job_running), job_running.mem_peak.unwrap_or(0), Some(CanceledBy { username: Some(username.to_string()), reason: Some(reason) }), e, @@ -241,22 +267,40 @@ pub async fn cancel_job<'c>( let job = Arc::new(job); - // get all children - let mut jobs = vec![job.id]; - let mut jobs_to_cancel = vec![]; - while !jobs.is_empty() { - let p_job = jobs.pop(); - let new_jobs = sqlx::query_scalar!( - "SELECT id AS \"id!\" FROM v2_job_queue INNER JOIN v2_job USING (id) WHERE parent_job = $1 AND v2_job.workspace_id = $2", - p_job, - w_id - ) - .fetch_all(&mut *tx) - .await?; - jobs.extend(new_jobs.clone()); - jobs_to_cancel.extend(new_jobs); - } - jobs.reverse(); + // get all children using recursive CTE + let mut jobs_to_cancel = sqlx::query!( + r#" +WITH RECURSIVE job_tree AS ( + -- Base case: direct children of the given parent job + SELECT id, parent_job, 1 AS depth + FROM v2_job_queue + INNER JOIN v2_job USING (id) + WHERE parent_job = $1 AND v2_job.workspace_id = $2 + + UNION ALL + + -- Recursive case: fetch children of previously found jobs + SELECT q.id, j.parent_job, t.depth + 1 + FROM v2_job_queue q + INNER JOIN v2_job j USING (id) + INNER JOIN job_tree t ON t.id = j.parent_job + WHERE j.workspace_id = $2 AND t.depth < 500 -- Limit recursion depth to 500 +) +SELECT id AS id, depth +FROM job_tree +ORDER BY depth, id + "#, + job.id, + w_id + ) + .fetch_all(&mut *tx) + .await? + .into_iter() + .filter_map(|r| r.id.clone()) + .collect_vec(); + + jobs_to_cancel.reverse(); + tracing::info!("Found {} child jobs to cancel", jobs_to_cancel.len()); let (ntx, _) = cancel_single_job( username, @@ -270,7 +314,23 @@ pub async fn cancel_job<'c>( .await?; tx = ntx; - // cancel children + if !force_cancel { + // cancel children in batch first + if !jobs_to_cancel.is_empty() { + let updated = sqlx::query_scalar!( + "UPDATE v2_job_queue SET canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = ANY($3) AND workspace_id = $4 AND (canceled_by IS NULL OR canceled_reason != $2) RETURNING id", + username, + reason, + jobs_to_cancel.as_slice(), + w_id + ) + .fetch_all(&mut *tx) + .await?; + + // Remove any jobs that were successfully updated + jobs_to_cancel.retain(|id| !updated.contains(&id)); + } + } for job_id in jobs_to_cancel { let job = get_queued_job_tx(job_id, &w_id, &mut tx).await?; @@ -297,7 +357,7 @@ pub async fn append_logs( job_id: &uuid::Uuid, workspace: impl AsRef, logs: impl AsRef, - db: impl Borrow>, + conn: &Connection, ) { if logs.as_ref().is_empty() { return; @@ -312,20 +372,82 @@ pub async fn append_logs( tracing::info!("NO LOGS [{job_id}]: {}", logs.as_ref()); return; } - if let Err(err) = sqlx::query!( - "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text)", - logs.as_ref(), - job_id, - workspace.as_ref(), - ) - .execute(db.borrow()) - .warn_after_seconds(1) - .await - { - tracing::error!(%job_id, %err, "error updating logs for large_log job {job_id}: {err}"); + match conn { + Connection::Sql(pool) => { + if let Err(err) = sqlx::query!( + "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text)", + logs.as_ref(), + job_id, + workspace.as_ref(), + ) + .execute(pool) + .warn_after_seconds(1) + .await + { + tracing::error!(%job_id, %err, "error updating logs for job {job_id}: {err}"); + } + } + Connection::Http(client) => { + if let Err(e) = client + .post::<_, String>( + &format!("/api/w/{}/agent_workers/push_logs/{}", workspace.as_ref(), job_id), + None, + &logs.as_ref(), + ) + .await { + tracing::error!(%job_id, %e, "error sending logs for job {job_id}: {e}"); + }; + } } } +pub async fn push_init_job<'c>( + db: &Pool, + content: String, + worker_name: &str, +) -> error::Result { + let tx = PushIsolationLevel::IsolatedRoot(db.clone()); + let ehm = HashMap::new(); + let (uuid, inner_tx) = push( + &db, + tx, + "admins", + windmill_common::jobs::JobPayload::Code(windmill_common::jobs::RawCode { + hash: None, + content, + path: Some(format!("init_script_{worker_name}")), + language: ScriptLang::Bash, + lock: None, + custom_concurrency_key: None, + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + }), + PushArgs::from(&ehm), + worker_name, + "worker@windmill.dev", + SUPERADMIN_SECRET_EMAIL.to_string(), + None, + None, + None, + None, + None, + false, + true, + None, + true, + Some("init_script".to_string()), + None, + None, + None, + None, + ) + .await?; + inner_tx.commit().await?; + Ok(uuid) +} + pub async fn cancel_persistent_script_jobs<'c>( username: &str, reason: Option, @@ -487,7 +609,7 @@ where pub async fn add_completed_job_error( db: &Pool, - queued_job: &QueuedJob, + queued_job: &MiniPulledJob, mem_peak: i32, canceled_by: Option, e: serde_json::Value, @@ -543,7 +665,7 @@ lazy_static::lazy_static! { pub async fn add_completed_job( db: &Pool, - queued_job: &QueuedJob, + queued_job: &MiniPulledJob, success: bool, skipped: bool, result: Json<&T>, @@ -580,7 +702,7 @@ pub async fn add_completed_job( serde_json::to_string(&result).unwrap_or_else(|_| "".to_string()) ); - let mem_peak = mem_peak.max(queued_job.mem_peak.unwrap_or(0)); + let mem_peak = mem_peak; // add_time!(bench, "add_completed_job query START"); let _duration = sqlx::query_scalar!( @@ -637,7 +759,7 @@ pub async fn add_completed_job( .map_err(|e| Error::InternalErr(format!("Could not update job labels: {e:#}")))?; } - if !queued_job.is_flow_step { + if !queued_job.is_flow_step() { if let Some(parent_job) = queued_job.parent_job { let _ = sqlx::query_scalar!( "UPDATE v2_job_status SET @@ -669,7 +791,7 @@ pub async fn add_completed_job( tx = delete_job(tx, &job_id).await?; // tracing::error!("3 {:?}", start.elapsed()); - if queued_job.is_flow_step { + if queued_job.is_flow_step() { if let Some(parent_job) = queued_job.parent_job { // persist the flow last progress timestamp to avoid zombie flow jobs tracing::debug!( @@ -703,12 +825,12 @@ pub async fn add_completed_job( } } } else { - if queued_job.schedule_path.is_some() && queued_job.script_path.is_some() { - let schedule_path = queued_job.schedule_path.as_ref().unwrap(); - let script_path = queued_job.script_path.as_ref().unwrap(); + if queued_job.schedule_path().is_some() && queued_job.runnable_path.is_some() { + let schedule_path = queued_job.schedule_path().unwrap(); + let script_path = queued_job.runnable_path.as_ref().unwrap(); let schedule = - get_schedule_opt(&mut *tx, &queued_job.workspace_id, schedule_path).await?; + get_schedule_opt(&mut *tx, &queued_job.workspace_id, &schedule_path).await?; if let Some(schedule) = schedule { #[cfg(feature = "enterprise")] @@ -742,7 +864,7 @@ pub async fn add_completed_job( db, queued_job, &schedule, - script_path, + &script_path, &queued_job.workspace_id, ) .await @@ -759,7 +881,7 @@ pub async fn add_completed_job( if let Err(err) = apply_schedule_handlers( db, &schedule, - script_path, + &script_path, &queued_job.workspace_id, success, result, @@ -798,27 +920,29 @@ pub async fn add_completed_job( } } if queued_job.concurrent_limit.is_some() { - let concurrency_key = match concurrency_key(db, queued_job).await { + let concurrency_key = match concurrency_key(db, &queued_job.id).await { Ok(c) => c, Err(e) => { tracing::error!( "Could not get concurrency key for job {} defaulting to default key: {e:?}", queued_job.id ); - legacy_concurrency_key(db, queued_job) - .await - .unwrap_or_else(|| queued_job.full_path_with_workspace()) + "".to_string() } }; - if let Err(e) = sqlx::query_scalar!( - "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1", - concurrency_key, - queued_job.id.hyphenated().to_string(), - ) - .execute(&mut *tx) - .await - { - tracing::error!("Could not decrement concurrency counter: {}", e); + if *DISABLE_CONCURRENCY_LIMIT || concurrency_key.is_empty() { + tracing::warn!("Concurrency limit is disabled, skipping"); + } else { + if let Err(e) = sqlx::query_scalar!( + "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1", + concurrency_key, + queued_job.id.hyphenated().to_string(), + ) + .execute(&mut *tx) + .await + { + tracing::error!("Could not decrement concurrency counter: {}", e); + } } if let Err(e) = sqlx::query_scalar!( @@ -837,26 +961,27 @@ pub async fn add_completed_job( tracing::debug!("decremented concurrency counter"); } - if JOB_TOKEN.is_none() { sqlx::query!("DELETE FROM job_perms WHERE job_id = $1", job_id) .execute(&mut *tx) .await?; - } + tx.commit().await?; tracing::info!( %job_id, - root_job = ?queued_job.root_job.map(|x| x.to_string()).unwrap_or_else(|| String::new()), - path = &queued_job.script_path(), - job_kind = ?queued_job.job_kind, + root_job = ?queued_job.flow_innermost_root_job.map(|x| x.to_string()).unwrap_or_else(|| String::new()), + path = &queued_job.runnable_path(), + job_kind = ?queued_job.kind, started_at = ?queued_job.started_at.map(|x| x.to_string()).unwrap_or_else(|| String::new()), duration = ?_duration, permissioned_as = ?queued_job.permissioned_as, - email = ?queued_job.email, + email = ?queued_job.permissioned_as_email, created_by = queued_job.created_by, - is_flow_step = queued_job.is_flow_step, - language = ?queued_job.language, + is_flow_step = queued_job.is_flow_step(), + language = ?queued_job.script_lang, + scheduled_for = ?queued_job.scheduled_for, + workspace_id = ?queued_job.workspace_id, success, "inserted completed job: {} (success: {success})", queued_job.id @@ -886,44 +1011,48 @@ pub async fn add_completed_job( #[cfg(feature = "cloud")] if *CLOUD_HOSTED && !queued_job.is_flow() && _duration > 1000 { - let additional_usage = _duration / 1000; - let w_id = &queued_job.workspace_id; - let premium_workspace = - sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", w_id) - .fetch_one(db) - .await - .map_err(|e| { - Error::internal_err(format!("fetching if {w_id} is premium: {e:#}")) - })?; - let _ = sqlx::query!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", - w_id, - additional_usage as i32 - ) - .execute(db) - .await - .map_err(|e| Error::internal_err(format!("updating usage: {e:#}"))); - - if !premium_workspace { + let db = db.clone(); + let w_id = queued_job.workspace_id.clone(); + let email = queued_job.permissioned_as_email.clone(); + let w_id2 = w_id.clone(); + let email2 = email.clone(); + tokio::task::spawn(async move { + let additional_usage = _duration / 1000; + let premium_workspace = windmill_common::workspaces::is_premium_workspace(&db, &w_id).await; + tokio::time::timeout(std::time::Duration::from_secs(10), async move { let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) + VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", - queued_job.email, + w_id, additional_usage as i32 ) - .execute(db) + .execute(&db) .await .map_err(|e| Error::internal_err(format!("updating usage: {e:#}"))); - } + + if !premium_workspace { + let _ = sqlx::query!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + email, + additional_usage as i32 + ) + .execute(&db) + .await + .map_err(|e| Error::internal_err(format!("updating usage: {e:#}"))); + }}).await.unwrap_or_else(|_| { + tracing::error!("Could not update usage for workspace {w_id2} and permissioned as {email2}, stopped after 10s"); + }); + }); } + #[cfg(feature = "enterprise")] if !success { - async fn has_failure_module(db: &Pool, job: &QueuedJob) -> bool { - if let Ok(flow) = cache::job::fetch_flow(db, job.job_kind, job.script_hash).await { + async fn has_failure_module(db: &Pool, job: &MiniPulledJob) -> bool { + if let Ok(flow) = cache::job::fetch_flow(db, job.kind, job.runnable_id).await { return flow.value().failure_module.is_some(); } sqlx::query_scalar!( @@ -936,7 +1065,7 @@ pub async fn add_completed_job( .unwrap_or(false) } - if queued_job.email == ERROR_HANDLER_USER_EMAIL { + if queued_job.permissioned_as_email == ERROR_HANDLER_USER_EMAIL { let base_url = BASE_URL.read().await; let w_id = &queued_job.workspace_id; report_critical_error( @@ -955,7 +1084,7 @@ pub async fn add_completed_job( None, ) .await; - } else if queued_job.email == SCHEDULE_ERROR_HANDLER_USER_EMAIL { + } else if queued_job.permissioned_as_email == SCHEDULE_ERROR_HANDLER_USER_EMAIL { let base_url = BASE_URL.read().await; let w_id = &queued_job.workspace_id; report_error_to_workspace_handler_or_critical_side_channel( @@ -974,8 +1103,8 @@ pub async fn add_completed_job( ) .await; } else if !_skip_downstream_error_handlers - && (matches!(queued_job.job_kind, JobKind::Script) - || matches!(queued_job.job_kind, JobKind::Flow) + && (matches!(queued_job.kind, JobKind::Script) + || matches!(queued_job.kind, JobKind::Flow) && !has_failure_module(db, queued_job).await) && queued_job.parent_job.is_none() { @@ -1025,8 +1154,8 @@ pub async fn add_completed_job( } } - if !queued_job.is_flow_step && queued_job.job_kind == JobKind::Script && canceled_by.is_none() { - if let Some(hash) = queued_job.script_hash { + if !queued_job.is_flow_step() && queued_job.kind == JobKind::Script && canceled_by.is_none() { + if let Some(hash) = queued_job.runnable_id { let p = sqlx::query_scalar!( "SELECT restart_unless_cancelled FROM script WHERE hash = $1 AND workspace_id = $2", hash.0, @@ -1049,7 +1178,7 @@ pub async fn add_completed_job( { let next_run = queued_job.started_at.unwrap_or(now) + chrono::Duration::try_seconds(10).unwrap(); - tracing::warn!("Perpetual script {:?} is running too fast, only 1 job per 10s it supported. Scheduling next run for {:?}", queued_job.script_path, next_run); + tracing::warn!("Perpetual script {:?} is running too fast, only 1 job per 10s it supported. Scheduling next run for {:?}", queued_job.runnable_path, next_run); Some(next_run) } else { None @@ -1062,14 +1191,14 @@ pub async fn add_completed_job( &queued_job.workspace_id, JobPayload::ScriptHash { hash, - path: queued_job.script_path().to_string(), - custom_concurrency_key: custom_concurrency_key(db, queued_job.id).await?, + path: queued_job.runnable_path().to_string(), + custom_concurrency_key: custom_concurrency_key(db, &queued_job.id).await?, concurrent_limit: queued_job.concurrent_limit, concurrency_time_window_s: queued_job.concurrency_time_window_s, cache_ttl: queued_job.cache_ttl, dedicated_worker: None, language: queued_job - .language + .script_lang .clone() .unwrap_or_else(|| ScriptLang::Deno), priority: queued_job.priority, @@ -1081,10 +1210,10 @@ pub async fn add_completed_job( .map(|x| PushArgs::from(&x.0)) .unwrap_or_else(|| PushArgs::from(&ehm)), &queued_job.created_by, - &queued_job.email, + &queued_job.permissioned_as_email, queued_job.permissioned_as.clone(), scheduled_for, - queued_job.schedule_path.clone(), + queued_job.schedule_path(), None, None, None, @@ -1111,7 +1240,7 @@ pub async fn add_completed_job( } pub async fn send_error_to_global_handler<'a, T: Serialize + Send + Sync>( - queued_job: &QueuedJob, + queued_job: &MiniPulledJob, db: &Pool, result: Json<&T>, ) -> Result<(), Error> { @@ -1126,8 +1255,8 @@ pub async fn send_error_to_global_handler<'a, T: Serialize + Send + Sync>( push_error_handler( db, queued_job.id, - queued_job.schedule_path.clone(), - queued_job.script_path.clone(), + queued_job.schedule_path(), + queued_job.runnable_path.clone(), queued_job.is_flow(), &queued_job.workspace_id, &prefixed_global_error_handler_path, @@ -1135,7 +1264,7 @@ pub async fn send_error_to_global_handler<'a, T: Serialize + Send + Sync>( None, queued_job.started_at, None, - &queued_job.email, + &queued_job.permissioned_as_email, false, true, None, @@ -1147,7 +1276,7 @@ pub async fn send_error_to_global_handler<'a, T: Serialize + Send + Sync>( } pub async fn report_error_to_workspace_handler_or_critical_side_channel( - queued_job: &QueuedJob, + queued_job: &MiniPulledJob, db: &Pool, error_message: String, ) -> () { @@ -1166,8 +1295,8 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( if let Err(err) = push_error_handler( db, queued_job.id, - queued_job.schedule_path.clone(), - queued_job.script_path.clone(), + queued_job.schedule_path(), + queued_job.runnable_path.clone(), queued_job.is_flow(), w_id, &error_handler, @@ -1179,7 +1308,7 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( None, queued_job.started_at, error_handler_extra_args, - &queued_job.email, + &queued_job.permissioned_as_email, false, false, None, @@ -1199,7 +1328,7 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( } pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync>( - queued_job: &QueuedJob, + queued_job: &MiniPulledJob, is_canceled: bool, db: &Pool, result: Json<&'a T>, @@ -1219,12 +1348,12 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> } if let Some(error_handler) = error_handler { - let ws_error_handler_muted: Option = match queued_job.job_kind { + let ws_error_handler_muted: Option = match queued_job.kind { JobKind::Script => { sqlx::query_scalar!( "SELECT ws_error_handler_muted FROM script WHERE workspace_id = $1 AND hash = $2", queued_job.workspace_id, - queued_job.script_hash.unwrap().0, + queued_job.runnable_id.map(|x| x.0), ) .fetch_optional(db) .await? @@ -1233,7 +1362,7 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> sqlx::query_scalar!( "SELECT ws_error_handler_muted FROM flow WHERE workspace_id = $1 AND path = $2", queued_job.workspace_id, - queued_job.script_path.as_ref().unwrap(), + queued_job.runnable_path.clone(), ) .fetch_optional(db) .await? @@ -1248,8 +1377,8 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> push_error_handler( db, queued_job.id, - queued_job.schedule_path.clone(), - queued_job.script_path.clone(), + queued_job.schedule_path(), + queued_job.runnable_path.clone(), queued_job.is_flow(), &queued_job.workspace_id, &error_handler, @@ -1257,7 +1386,7 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> None, queued_job.started_at, error_handler_extra_args, - &queued_job.email, + &queued_job.permissioned_as_email, false, false, None, @@ -1271,7 +1400,7 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> pub async fn handle_maybe_scheduled_job<'c>( db: &Pool, - job: &QueuedJob, + job: &MiniPulledJob, schedule: &Schedule, script_path: &str, w_id: &str, @@ -1826,39 +1955,408 @@ async fn handle_successful_schedule<'a, 'c, T: Serialize + Send + Sync>( Ok(()) } -#[derive(sqlx::FromRow)] +#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone)] +#[sqlx(type_name = "JOB_TRIGGER_KIND", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum JobTriggerKind { + Webhook, + Http, + Websocket, + Kafka, + Email, + Nats, + Mqtt, + Sqs, + Postgres, + Schedule, + Gcp +} + + +#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] +pub struct MiniPulledJob { + pub workspace_id: String, + pub id: Uuid, + pub args: Option>>>, + pub parent_job: Option, + pub created_by: String, + pub scheduled_for: chrono::DateTime, + pub started_at: Option>, + pub runnable_path: Option, + pub kind: JobKind, + pub runnable_id: Option, + pub canceled_reason: Option, + pub canceled_by: Option, + pub permissioned_as: String, + pub permissioned_as_email: String, + pub flow_status: Option>>, + pub tag: String, + pub script_lang: Option, + pub same_worker: bool, + pub pre_run_error: Option, + pub concurrent_limit: Option, + pub concurrency_time_window_s: Option, + pub flow_innermost_root_job: Option, + pub timeout: Option, + pub flow_step_id: Option, + pub cache_ttl: Option, + pub priority: Option, + pub preprocessed: Option, + pub script_entrypoint_override: Option, + pub trigger: Option, + pub trigger_kind: Option, + pub visible_to_owner: bool, +} + +impl MiniPulledJob { + pub fn runnable_path(&self) -> &str { + self.runnable_path + .as_ref() + .map(String::as_str) + .unwrap_or("tmp/main") + } + + pub fn is_flow_step(&self) -> bool { + self.flow_step_id.is_some() + } + + pub fn is_canceled(&self) -> bool { + self.canceled_by.is_some() + } + + pub fn parse_flow_status(&self) -> Option { + // tracing::error!("parse_flow_status: {:?}", self.flow_status); + + self.flow_status + .as_ref() + .and_then(|v| serde_json::from_str::((**v).get()).ok()) + } + + pub fn from(job: &QueuedJob) -> MiniPulledJob { + MiniPulledJob { + workspace_id: job.workspace_id.clone(), + id: job.id, + args: job.args.clone(), + parent_job: job.parent_job.clone(), + created_by: job.created_by.clone(), + started_at: job.started_at.clone(), + scheduled_for: job.scheduled_for, + runnable_path: job.script_path.clone(), + kind: job.job_kind, + runnable_id: job.script_hash.clone(), + canceled_reason: job.canceled_reason.clone(), + canceled_by: job.canceled_by.clone(), + permissioned_as: job.permissioned_as.clone(), + permissioned_as_email: job.email.clone(), + flow_status: job.flow_status.clone(), + tag: job.tag.clone(), + script_lang: job.language.clone(), + same_worker: job.same_worker, + pre_run_error: job.pre_run_error.clone(), + concurrent_limit: job.concurrent_limit.clone(), + concurrency_time_window_s: job.concurrency_time_window_s.clone(), + flow_innermost_root_job: job.root_job.clone(), + timeout: job.timeout.clone(), + flow_step_id: job.flow_step_id.clone(), + cache_ttl: job.cache_ttl.clone(), + priority: job.priority.clone(), + preprocessed: job.preprocessed.clone(), + script_entrypoint_override: job.script_entrypoint_override.clone(), + trigger: job.schedule_path.clone(), + trigger_kind: if job.schedule_path.is_some() { + Some(JobTriggerKind::Schedule) + } else { + None + }, + visible_to_owner: job.visible_to_owner.clone(), + } + } + pub fn is_flow(&self) -> bool { + self.kind.is_flow() + } + + pub fn is_dependency(&self) -> bool { + self.kind.is_dependency() + } + + pub fn schedule_path(&self) -> Option { + if self + .trigger_kind + .as_ref() + .is_some_and(|t| matches!(t, JobTriggerKind::Schedule)) + { + self.trigger.clone() + } else { + None + } + } + + + pub async fn mark_as_started_if_step(&self, db: &DB) -> Result<(), Error> { + if self.is_flow_step() { + let _ = update_flow_status_in_progress( + db, + &self.workspace_id, + self.parent_job + .ok_or_else(|| Error::internal_err(format!("expected parent job")))?, + self.id, + ) + .warn_after_seconds(5) + .await?; + } else if let Some(parent_job) = self.parent_job { + let _ = update_workflow_as_code_status( + db, + &self.id, + &parent_job, + ) + .await?; + } + Ok(()) + } + +} + + + +#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] pub struct PulledJob { #[sqlx(flatten)] - pub job: QueuedJob, + pub job: MiniPulledJob, pub raw_code: Option, pub raw_lock: Option, pub raw_flow: Option>>, + pub parent_runnable_path: Option, + pub permissioned_as_email: Option, + pub permissioned_as_username: Option, + pub permissioned_as_is_admin: Option, + pub permissioned_as_is_operator: Option, + pub permissioned_as_groups: Option>, + pub permissioned_as_folders: Option>, } + +// NOTE: +// Precomputed by the server +// Used to offload work from agent workers to server +#[derive(Debug, Serialize, Deserialize)] +pub enum PrecomputedAgentInfo { + Bun { local: String, remote: String }, + Python { + // V1, not used anymore. Exists for compat. + // TODO: Needs to be removed eventually + py_version: Option, + py_version_v2: Option, + requirements: Option }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JobAndPerms { + pub job: MiniPulledJob, + pub raw_code: Option, + pub raw_flow: Option>>, + pub raw_lock: Option, + pub parent_runnable_path: Option, + pub token: String, + pub precomputed_agent_info: Option, +} +impl PulledJob { + pub async fn get_job_and_perms(self, db: &DB) -> JobAndPerms { + let job_perms = match ( + self.permissioned_as_email, + self.permissioned_as_username, + self.permissioned_as_is_admin, + self.permissioned_as_is_operator, + self.permissioned_as_groups, + self.permissioned_as_folders, + ) { + ( + Some(email), + Some(username), + Some(is_admin), + Some(is_operator), + Some(groups), + Some(folders), + ) => Some(JobPerms { + email, + username, + is_admin, + is_operator, + groups, + folders, + }), + _ => None, + }; + + let token = create_token(&db, &self.job, job_perms).await; + JobAndPerms { + job: self.job, + raw_code: self.raw_code, + raw_flow: self.raw_flow, + raw_lock: self.raw_lock, + parent_runnable_path: self.parent_runnable_path, + token, + precomputed_agent_info: None, + } + } +} + +// struct Permission +pub async fn create_token(db: &DB, job: &MiniPulledJob, perms: Option) -> String { + // skipping test runs + if job.workspace_id != "" { + let label = if job.permissioned_as != format!("u/{}", job.created_by) + && job.permissioned_as != job.created_by + { + format!("ephemeral-script-end-user-{}", job.created_by) + } else { + "ephemeral-script".to_string() + }; + windmill_common::auth::create_token_for_owner( + db, + &job.workspace_id, + &job.permissioned_as, + &label, + *SCRIPT_TOKEN_EXPIRY, + &job.permissioned_as_email, + &job.id, + perms, + ) + .warn_after_seconds(5) + .await + .expect("could not create job token") + } else { + return "".to_string(); + } +} + + + + impl std::ops::Deref for PulledJob { - type Target = QueuedJob; + type Target = MiniPulledJob; fn deref(&self) -> &Self::Target { &self.job } } +lazy_static::lazy_static! { + static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true"); +} + +pub async fn get_mini_pulled_job<'c>( + e: impl PgExecutor<'c>, + job_id: &Uuid, +) -> windmill_common::error::Result> { + let job = sqlx::query_as!( + MiniPulledJob, + "SELECT + v2_job_queue.workspace_id, + v2_job_queue.id, + v2_job.args as \"args: sqlx::types::Json>>\", + v2_job.parent_job, + v2_job.created_by, + v2_job_queue.started_at, + scheduled_for, + runnable_path, + kind as \"kind: JobKind\", + runnable_id as \"runnable_id: ScriptHash\", + canceled_reason, + canceled_by, + permissioned_as, + permissioned_as_email, + flow_status as \"flow_status: sqlx::types::Json>\", + v2_job.tag, + script_lang as \"script_lang: ScriptLang\", + same_worker, + pre_run_error, + concurrent_limit, + concurrency_time_window_s, + flow_innermost_root_job, + timeout, + flow_step_id, + cache_ttl, + v2_job_queue.priority, + preprocessed, + script_entrypoint_override, + trigger, + trigger_kind as \"trigger_kind: JobTriggerKind\", + visible_to_owner + FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1", + job_id, + ) + .fetch_optional(e) + .await?; + Ok(job) +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PulledJobResult { + pub job: Option, + pub suspended: bool, +} + + + pub async fn pull( db: &Pool, suspend_first: bool, worker_name: &str, -) -> windmill_common::error::Result<(Option, bool)> { + query_o: Option<&(String, String)>, + #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, +) -> windmill_common::error::Result { loop { + if let Some((query_suspended, query_no_suspend)) = query_o { + let njob = { + let job = if query_suspended.is_empty() { + None + } else { + sqlx::query_as::<_, PulledJob>(query_suspended) + .bind(worker_name) + .fetch_optional(db) + .await? + }; + if let Some(job) = job { + PulledJobResult { job: Some(job), suspended: true } + } else { + let job = sqlx::query_as::<_, PulledJob>(query_no_suspend) + .bind(worker_name) + .fetch_optional(db) + .await?; + PulledJobResult { job, suspended: false } + } + }; + if let Some(job) = njob.job.as_ref() { + if job.is_flow() || job.is_dependency() { + let per_workspace = per_workspace_tag(&job.workspace_id).await; + let base_tag = if job.is_flow() { + "flow".to_string() + } else { + "dependency".to_string() + }; + let tag = if per_workspace { + format!("{}-{}", base_tag, job.workspace_id) + } else { + base_tag + }; + sqlx::query!("UPDATE v2_job_queue SET tag = $1, running = false WHERE id = $2", tag, job.id).execute(db).await?; + continue; + } + } + return Ok(njob); + }; let (job, suspended) = pull_single_job_and_mark_as_running_no_concurrency_limit( - db, - suspend_first, - worker_name, - ) - .await?; + db, + suspend_first, + worker_name, + #[cfg(feature = "benchmark")] bench, + ) + .await?; let Some(job) = job else { - return Ok((None, suspended)); + return Ok(PulledJobResult { job: None, suspended }); }; + let has_concurent_limit = job.concurrent_limit.is_some(); #[cfg(not(feature = "enterprise"))] @@ -1871,29 +2369,25 @@ pub async fn pull( // concurrency check. If more than X jobs for this path are already running, we re-queue and pull another job from the queue let pulled_job = job; - if pulled_job.script_path.is_none() || !has_concurent_limit || pulled_job.canceled { + if pulled_job.runnable_path.is_none() + || !has_concurent_limit + || pulled_job.canceled_by.is_some() + { #[cfg(feature = "prometheus")] if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) { QUEUE_PULL_COUNT.inc(); } - return Ok((Option::Some(pulled_job), suspended)); + return Ok(PulledJobResult { job: Some(pulled_job), suspended }); } - let mut tx = db.begin().await?; - - // Else the job is subject to concurrency limits - let job_script_path = pulled_job.script_path.clone().unwrap(); - - let job_concurrency_key = match concurrency_key(db, &pulled_job).await { + let job_concurrency_key = match concurrency_key(db, &pulled_job.id).await { Ok(key) => key, Err(e) => { tracing::error!( "Could not get concurrency key for job {} defaulting to default key: {e:?}", pulled_job.id ); - legacy_concurrency_key(db, &pulled_job) - .await - .unwrap_or_else(|| pulled_job.full_path_with_workspace()) + "".to_string() } }; tracing::debug!("Concurrency key is '{}'", job_concurrency_key); @@ -1907,110 +2401,68 @@ pub async fn pull( job_custom_concurrency_time_window_s ); - sqlx::query_scalar!( - "SELECT null FROM v2_job_queue WHERE id = $1 FOR UPDATE", - pulled_job.id - ) - .fetch_one(&mut *tx) - .await - .context("lock job in queue")?; - let jobs_uuids_init_json_value = serde_json::from_str::( format!("{{\"{}\": {{}}}}", pulled_job.id.hyphenated().to_string()).as_str(), ) .expect("Unable to serialize job_uuids column to proper JSON"); - let running_job = sqlx::query_scalar!( - "INSERT INTO concurrency_counter(concurrency_id, job_uuids) VALUES ($1, $2) - ON CONFLICT (concurrency_id) - DO UPDATE SET job_uuids = jsonb_set(concurrency_counter.job_uuids, array[$3], '{}') - RETURNING (SELECT COUNT(*) FROM jsonb_object_keys(job_uuids))", - job_concurrency_key, - jobs_uuids_init_json_value, - pulled_job.id.hyphenated().to_string(), - ) - .fetch_one(&mut *tx) - .await - .map_err(|e| { - Error::internal_err(format!( - "Error getting concurrency count for script path {job_script_path}: {e:#}" - )) - })?; - tracing::debug!("running_job: {}", running_job.unwrap_or(0)); - let completed_count = sqlx::query!( - "SELECT COUNT(*) as count, COALESCE(MAX(ended_at), now() - INTERVAL '1 second' * $2) as max_ended_at FROM concurrency_key WHERE key = $1 AND ended_at >= (now() - INTERVAL '1 second' * $2)", - job_concurrency_key, - f64::from(job_custom_concurrency_time_window_s), - ).fetch_one(&mut *tx).await.map_err(|e| { - Error::internal_err(format!( - "Error getting completed count for key {job_concurrency_key}: {e:#}" - )) - })?; - - let min_started_at = sqlx::query!( - "SELECT COALESCE((SELECT MIN(started_at) as min_started_at - FROM v2_as_queue - WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now", - job_script_path, - &pulled_job.workspace_id, - completed_count.max_ended_at - ) - .fetch_one(&mut *tx) - .await - .map_err(|e| { - Error::internal_err(format!( - "Error getting concurrency count for script path {job_script_path}: {e:#}" - )) - })?; - - let concurrent_jobs_for_this_script = - completed_count.count.unwrap_or_default() as i32 + running_job.unwrap_or(0) as i32; - tracing::debug!( - "Current concurrent jobs for this script: {}", - concurrent_jobs_for_this_script - ); - if concurrent_jobs_for_this_script <= job_custom_concurrent_limit { + let (within_limit, max_ended_at) = + if *DISABLE_CONCURRENCY_LIMIT || job_concurrency_key.is_empty() { + tracing::warn!("Concurrency limit is disabled, skipping"); + (true, None) + } else { + update_concurrency_counter( + db, + &pulled_job.id, + job_concurrency_key.clone(), + jobs_uuids_init_json_value, + pulled_job.id.hyphenated().to_string(), + job_custom_concurrency_time_window_s, + job_custom_concurrent_limit, + ) + .await? + }; + if within_limit { #[cfg(feature = "prometheus")] if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) { QUEUE_PULL_COUNT.inc(); } - tx.commit().await?; - return Ok((Option::Some(pulled_job), suspended)); + return Ok(PulledJobResult { job: Some(pulled_job), suspended }); } - let x = sqlx::query_scalar!( - "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1 RETURNING (SELECT COUNT(*) FROM jsonb_object_keys(job_uuids))", - job_concurrency_key, - pulled_job.id.hyphenated().to_string(), + let job_script_path = pulled_job.runnable_path.clone().unwrap_or_default(); + + let min_started_at = sqlx::query!( + "SELECT COALESCE((SELECT MIN(started_at) as min_started_at + FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id + WHERE v2_job.runnable_path = $1 AND v2_job.kind != 'dependencies' AND v2_job_queue.running = true AND v2_job_queue.workspace_id = $2 AND v2_job_queue.canceled_by IS NULL AND v2_job.concurrent_limit > 0), $3) as min_started_at, now() AS now", + job_script_path, + &pulled_job.workspace_id, + max_ended_at ) - .fetch_one(&mut *tx) + .fetch_one(db) .await .map_err(|e| { Error::internal_err(format!( - "Error decreasing concurrency count for script path {job_script_path}: {e:#}" + "Error getting min started at for script path {job_script_path}: {e:#}" )) })?; - tracing::debug!("running_job after decrease: {}", x.unwrap_or(0)); - let job_uuid: Uuid = pulled_job.id; let avg_script_duration: Option = sqlx::query_scalar!( "SELECT CAST(ROUND(AVG(duration_ms), 0) AS BIGINT) AS avg_duration_s FROM - (SELECT duration_ms FROM concurrency_key LEFT JOIN v2_as_completed_job ON v2_as_completed_job.id = concurrency_key.job_id WHERE key = $1 AND ended_at IS NOT NULL + (SELECT duration_ms FROM concurrency_key LEFT JOIN v2_job_completed ON v2_job_completed.id = concurrency_key.job_id WHERE key = $1 AND ended_at IS NOT NULL ORDER BY ended_at DESC LIMIT 10) AS t", job_concurrency_key ) - .fetch_one(&mut *tx) + .fetch_one(db) .await?; - tracing::info!("avg script duration computed: {:?}", avg_script_duration); + tracing::debug!( + "avg script duration computed: {}", + avg_script_duration.unwrap_or(0) + ); - // let before_me = sqlx::query!( - // "SELECT schedu FROM queue WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND started_at < $3 ORDER BY started_at DESC LIMIT 1", - // job_script_path, - // &pulled_job.workspace_id, - // min_started_at.now.unwrap() - // ) // optimal scheduling is: 'older_job_in_concurrency_time_window_started_timestamp + script_avg_duration + concurrency_time_window_s' let inc = Duration::try_milliseconds( avg_script_duration.map(|x| i64::from(x + 100)).unwrap_or(0), @@ -2021,38 +2473,62 @@ pub async fn pull( .unwrap_or_default(); let now = min_started_at.now.unwrap(); - let min_started_p_inc = (min_started_at.min_started_at.unwrap_or(now) + inc) - .max(now + Duration::try_seconds(3).unwrap_or_default()); + let min_started_at_or_now = min_started_at.min_started_at.unwrap_or(now); + let min_started_p_inc = + (min_started_at_or_now + inc).max(now + Duration::try_seconds(3).unwrap_or_default()); let mut estimated_next_schedule_timestamp = min_started_p_inc; + let all_jobs = sqlx::query_scalar!( + "SELECT scheduled_for FROM v2_job_queue INNER JOIN concurrency_key ON concurrency_key.job_id = v2_job_queue.id + WHERE key = $1 AND running = false AND canceled_by IS NULL AND scheduled_for >= $2", + job_concurrency_key, + estimated_next_schedule_timestamp - inc + ).fetch_all(db).await?; + + tracing::debug!( + "all_jobs: {:?}, estimated_next_schedule_timestamp: {:?}, inc: {:?}", + all_jobs, + estimated_next_schedule_timestamp, + inc + ); + let mut i = 0; loop { - let nestimated = estimated_next_schedule_timestamp + inc; - let jobs_in_window = sqlx::query_scalar!( - "SELECT COUNT(*) FROM v2_as_queue LEFT JOIN concurrency_key ON concurrency_key.job_id = v2_as_queue.id - WHERE key = $1 AND running = false AND canceled = false AND scheduled_for >= $2 AND scheduled_for < $3", - job_concurrency_key, - estimated_next_schedule_timestamp, - nestimated - ).fetch_optional(&mut *tx).await?.flatten().unwrap_or(0) as i32; - tracing::info!("estimated_next_schedule_timestamp: {:?}, jobs_in_window: {jobs_in_window}, nestimated: {nestimated}, inc: {inc}", estimated_next_schedule_timestamp); - if jobs_in_window < job_custom_concurrent_limit { + let jobs_in_window = all_jobs + .iter() + .filter(|&scheduled_for| scheduled_for <= &estimated_next_schedule_timestamp) + .count() as i32 + - (job_custom_concurrent_limit * i); + + tracing::debug!("estimated_next_schedule_timestamp: {:?}, jobs_in_window: {jobs_in_window}, inc: {inc}", estimated_next_schedule_timestamp); + + if jobs_in_window < job_custom_concurrent_limit || *DISABLE_CONCURRENCY_LIMIT { break; } else { - estimated_next_schedule_timestamp = nestimated; + i += 1; + estimated_next_schedule_timestamp = estimated_next_schedule_timestamp + inc; } } - tracing::info!("Job '{}' from path '{}' with concurrency key '{}' has reached its concurrency limit of {} jobs run in the last {} seconds. This job will be re-queued for next execution at {}", - job_uuid, job_script_path, job_concurrency_key, job_custom_concurrent_limit, job_custom_concurrency_time_window_s, estimated_next_schedule_timestamp); + tracing::info!("Job '{}' from path '{}' with concurrency key '{}' has reached its concurrency limit of {} jobs run in the last {} seconds. This job will be re-queued for next execution at {} (min_started_at: {min_started_at_or_now}, avg script duration: {:?}, number of time windows full: {})", + job_uuid, job_script_path, job_concurrency_key, job_custom_concurrent_limit, job_custom_concurrency_time_window_s, estimated_next_schedule_timestamp, avg_script_duration, i); let job_log_event = format!( - "\nRe-scheduled job to {estimated_next_schedule_timestamp} due to concurrency limits with key {job_concurrency_key} and limit {job_custom_concurrent_limit} in the last {job_custom_concurrency_time_window_s} seconds", + "\nRe-scheduled job to {estimated_next_schedule_timestamp} due to concurrency limits with key {job_concurrency_key} and limit {job_custom_concurrent_limit} in the last {job_custom_concurrency_time_window_s} seconds (min_started_at: {min_started_at_or_now}, avg script duration: {:?}, number of time windows full: {})\n", + avg_script_duration, i ); - let _ = append_logs(&job_uuid, &pulled_job.workspace_id, job_log_event, db).await; + let _ = append_logs( + &job_uuid, + &pulled_job.workspace_id, + job_log_event, + &Connection::from(db.clone()), + ) + .await; - // if using posgtres, then we're able to re-queue the entire batch of scheduled job for this script_path, so we do it sqlx::query!( - "WITH ping AS (UPDATE v2_job_runtime SET ping = NULL WHERE id = $2 RETURNING id) + " + WITH ping AS ( + UPDATE v2_job_runtime SET ping = null WHERE id = $2 + ) UPDATE v2_job_queue SET running = false, started_at = null, @@ -2061,11 +2537,9 @@ pub async fn pull( estimated_next_schedule_timestamp, job_uuid, ) - .execute(&mut *tx) + .execute(db) .await .map_err(|e| Error::internal_err(format!("Could not update and re-queue job {job_uuid}. The job will be marked as running but it is not running: {e:#}")))?; - - tx.commit().await? } } @@ -2073,6 +2547,7 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( db: &Pool, suspend_first: bool, worker_name: &str, + #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, ) -> windmill_common::error::Result<(Option, bool)> { let job_and_suspended: (Option, bool) = { /* Jobs can be started if they: @@ -2086,9 +2561,9 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( if query.is_empty() { tracing::warn!("No suspended pull queries available"); + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; return Ok((None, false)); } - let r = if suspend_first { // tracing::info!("Pulling job with query: {}", query); sqlx::query_as::<_, PulledJob>(&query) @@ -2107,17 +2582,25 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( if queries.is_empty() { tracing::warn!("No pull queries available"); + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; return Ok((None, false)); } for query in queries.iter() { // tracing::info!("Pulling job with query: {}", query); // let instant = std::time::Instant::now(); + + #[cfg(feature = "benchmark")] + add_time!(bench, "pre pull"); + let r = sqlx::query_as::<_, PulledJob>(query) .bind(worker_name) .fetch_optional(db) .await?; + #[cfg(feature = "benchmark")] + add_time!(bench, "post pull"); + if let Some(pulled_job) = r { // tracing::info!("pulled job: {:?}", instant.elapsed().as_micros()); @@ -2139,73 +2622,59 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( pub async fn custom_concurrency_key( db: &Pool, - job_id: Uuid, + job_id: &Uuid, ) -> Result, sqlx::Error> { sqlx::query_scalar!("SELECT key FROM concurrency_key WHERE job_id = $1", job_id) .fetch_optional(db) // this should no longer be fetch optional .await } -async fn legacy_concurrency_key(db: &Pool, queued_job: &QueuedJob) -> Option { - let r = if queued_job.is_flow() { - sqlx::query_scalar!( - "SELECT flow_version.value->>'concurrency_key' - FROM flow - LEFT JOIN flow_version - ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 AND flow.workspace_id = $2", - queued_job.script_path, - queued_job.workspace_id - ) - .fetch_optional(db) - .await - } else { - sqlx::query_scalar!( - "SELECT concurrency_key FROM script WHERE hash = $1 AND workspace_id = $2", - queued_job.script_hash.unwrap_or(ScriptHash(0)).0, - queued_job.workspace_id - ) - .fetch_optional(db) - .await - } - .ok() - .flatten() - .flatten(); - - let ehm = HashMap::new(); - let push_args = queued_job - .args - .as_ref() - .map(|x| PushArgs::from(&x.0)) - .unwrap_or_else(|| PushArgs::from(&ehm)); - r.map(|x| interpolate_args(x, &push_args, &queued_job.workspace_id)) -} - -async fn concurrency_key( - db: &Pool, - queued_job: &QueuedJob, -) -> windmill_common::error::Result { +async fn concurrency_key(db: &Pool, id: &Uuid) -> windmill_common::error::Result { not_found_if_none( - custom_concurrency_key(db, queued_job.id).await?, + custom_concurrency_key(db, id).await?, "ConcurrencyKey", - queued_job.id.to_string(), + id.to_string(), ) } -fn interpolate_args(x: String, args: &PushArgs, workspace_id: &str) -> String { +pub fn interpolate_args(x: String, args: &PushArgs, workspace_id: &str) -> String { // Save this value to avoid parsing twice let workspaced = x.as_str().replace("$workspace", workspace_id).to_string(); if RE_ARG_TAG.is_match(&workspaced) { let mut interpolated = workspaced.clone(); for cap in RE_ARG_TAG.captures_iter(&workspaced) { let arg_name = cap.get(1).unwrap().as_str(); - let arg_value = args - .args - .get(arg_name) - .or(args.extra.as_ref().and_then(|x| x.get(arg_name))) - .map(|x| x.get()) - .unwrap_or_default() - .trim_matches('"'); + let arg_value = if arg_name.contains('.') { + let parts: Vec<&str> = arg_name.split('.').collect(); + let root = parts[0]; + let mut value = args + .args + .get(root) + .or(args.extra.as_ref().and_then(|x| x.get(root))) + .map(|x| x.get()) + .unwrap_or_default().to_string(); + + for part in parts.iter().skip(1) { + if let Ok(obj) = serde_json::from_str::(&value) { + value = obj.get(part) + .and_then(|v| Some(v.to_string())) + .unwrap_or_default() + .as_str().to_string(); + } else { + value = "".to_string(); // Invalid JSON or missing field + break; + } + } + value.trim_matches('"').to_string() + } else { + args.args + .get(arg_name) + .or(args.extra.as_ref().and_then(|x| x.get(arg_name))) + .map(|x| x.get()) + .unwrap_or_default() + .trim_matches('"') + .to_string() + }; interpolated = interpolated.replace(format!("$args[{}]", arg_name).as_str(), &arg_value); } @@ -2247,15 +2716,14 @@ pub async fn get_result_by_id( .await { Ok(res) => Ok(res), - Err(_) => { + Err(e) => { let root = sqlx::query!( "SELECT id As \"id!\", flow_status->'restarted_from'->'flow_job_id' AS \"restarted_from: Json\" - FROM v2_as_queue - WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $1), $1) = id AND workspace_id = $2", - flow_id, - &w_id + FROM v2_job_status + WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $1), $1) = id", + flow_id ) .fetch_optional(&db) .await?; @@ -2264,7 +2732,7 @@ pub async fn get_result_by_id( let restarted_from_id = not_found_if_none( root.restarted_from, "Id not found in the result's mapping of the root job and root job had no restarted from information", - format!("parent: {}, root: {}, id: {}", flow_id, root.id, node_id), + format!("parent: {}, root: {}, id: {}, error: {e:#}", flow_id, root.id, node_id), )?; get_result_by_id_from_original_flow( @@ -2331,8 +2799,8 @@ pub async fn get_result_and_success_by_id_from_flow( let success = match &job_result { JobResult::SingleJob(job_id) => { sqlx::query_scalar!( - "SELECT success AS \"success!\" - FROM v2_as_completed_job WHERE id = $1 AND workspace_id = $2", + "SELECT status = 'success' OR status = 'skipped' AS \"success!\" + FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", job_id, w_id ) @@ -2344,20 +2812,19 @@ pub async fn get_result_and_success_by_id_from_flow( r#"WITH modules AS ( SELECT jsonb_array_elements(flow_status->'modules') AS module FROM {} - WHERE id = $1 AND workspace_id = $2 + WHERE id = $1 ) SELECT module->>'type' = 'Success' FROM modules - WHERE module->>'id' = $3"#, + WHERE module->>'id' = $2"#, if completed { - "v2_as_completed_job" + "v2_job_completed" } else { - "v2_as_queue" + "v2_job_status" } ); sqlx::query_scalar(&query) .bind(flow_id) - .bind(w_id) .bind(node_id) .fetch_optional(db) .await? @@ -2395,9 +2862,10 @@ pub async fn get_result_by_id_from_running_flow_inner( node_id: &str, ) -> error::Result { let flow_job_result = sqlx::query!( - "SELECT leaf_jobs->$1::text AS \"leaf_jobs: Json>\", parent_job - FROM v2_as_queue - WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $2), $2) = id AND workspace_id = $3", + "SELECT flow_leaf_jobs->$1::text AS \"leaf_jobs: Json>\", v2_job.parent_job + FROM v2_job_status + LEFT JOIN v2_job ON v2_job.id = v2_job_status.id AND v2_job.workspace_id = $3 + WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $2), $2) = v2_job_status.id", node_id, flow_id, w_id, @@ -2405,11 +2873,13 @@ pub async fn get_result_by_id_from_running_flow_inner( .fetch_optional(db) .await?; + // tracing::error!("flow_job_result: {:?} {:?}", flow_job_result, flow_id); let flow_job_result = windmill_common::utils::not_found_if_none( flow_job_result, "Root job of parent runnnig flow", format!("parent: {}, id: {}", flow_id, node_id), )?; + // tracing::error!("flow_job_result: {:?}, {:?}", flow_job_result.leaf_jobs, flow_job_result.parent_job); let job_result = flow_job_result .leaf_jobs @@ -2441,7 +2911,8 @@ pub async fn get_result_by_id_from_running_flow_inner( async fn get_completed_flow_node_result_rec( db: &Pool, w_id: &str, - subflows: impl std::iter::Iterator, + created_at: DateTime, + subflows: Vec<(Uuid, FlowStatus)>, node_id: &str, ) -> error::Result> { for (id, flow_status) in subflows { @@ -2461,18 +2932,19 @@ async fn get_completed_flow_node_result_rec( }; } else { let subflows = sqlx::query!( - "SELECT id AS \"id!\", flow_status AS \"flow_status!: Json\" - FROM v2_as_completed_job - WHERE parent_job = $1 AND workspace_id = $2 AND flow_status IS NOT NULL", + "SELECT j.id, jc.flow_status AS \"flow_status!: Json\" + FROM v2_job j + JOIN v2_job_completed jc ON j.id = jc.id + WHERE j.parent_job = $1 AND j.workspace_id = $2 AND j.created_at >= $3 AND jc.flow_status IS NOT NULL", id, - w_id + w_id, + created_at ) .map(|record| (record.id, record.flow_status.0)) .fetch_all(db) - .await? - .into_iter(); + .await?; match Box::pin(get_completed_flow_node_result_rec( - db, w_id, subflows, node_id, + db, w_id, created_at, subflows, node_id, )) .await? { @@ -2492,22 +2964,26 @@ async fn get_result_by_id_from_original_flow_inner( node_id: &str, ) -> error::Result { let flow_job = sqlx::query!( - "SELECT id, flow_status AS \"flow_status!: Json\" - FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", + "SELECT jc.id, jc.flow_status AS \"flow_status!: Json\", j.created_at + FROM v2_job_completed jc + JOIN v2_job j ON j.id = jc.id + WHERE jc.id = $1 AND jc.workspace_id = $2 AND jc.flow_status IS NOT NULL", completed_flow_id, w_id ) - .map(|record| (record.id, record.flow_status.0)) + .map(|record| (record.id, record.flow_status.0, record.created_at)) .fetch_optional(db) .await?; - let flow_job = not_found_if_none( + let (id, flow_status, created_at) = not_found_if_none( flow_job, "Root completed job", format!("root: {}, id: {}", completed_flow_id, node_id), )?; - match get_completed_flow_node_result_rec(db, w_id, [flow_job].into_iter(), node_id).await? { + match get_completed_flow_node_result_rec(db, w_id, created_at, vec![(id, flow_status)], node_id) + .await? + { Some(res) => Ok(res), None => Err(Error::NotFound(format!( "Flow result by id not found going top-down from {}, (id: {})", @@ -2768,7 +3244,7 @@ pub fn empty_result() -> Box { // } lazy_static::lazy_static! { - pub static ref RE_ARG_TAG: Regex = Regex::new(r#"\$args\[(\w+)\]"#).unwrap(); + pub static ref RE_ARG_TAG: Regex = Regex::new(r#"\$args\[((?:\w+\.)*\w+)\]"#).unwrap(); } // #[instrument(level = "trace", skip_all)] @@ -2777,7 +3253,7 @@ pub async fn push<'c, 'd>( mut tx: PushIsolationLevel<'c>, workspace_id: &str, job_payload: JobPayload, - mut args: PushArgs<'d>, + args: PushArgs<'d>, user: &str, mut email: &str, mut permissioned_as: String, @@ -2799,49 +3275,46 @@ pub async fn push<'c, 'd>( #[cfg(feature = "cloud")] if *CLOUD_HOSTED { let premium_workspace = - sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", workspace_id) - .fetch_one(_db) - .await - .map_err(|e| { - Error::internal_err(format!( - "fetching if {workspace_id} is premium and overquota: {e:#}" - )) - })?; - + windmill_common::workspaces::is_premium_workspace(_db, workspace_id).await; // we track only non flow steps let (workspace_usage, user_usage) = if !matches!( job_payload, JobPayload::Flow { .. } | JobPayload::RawFlow { .. } ) { - let workspace_usage = sqlx::query_scalar!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 - RETURNING usage.usage", - workspace_id - ) - .fetch_one(_db) - .await - .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?; + tokio::time::timeout(std::time::Duration::from_secs(10), async move { + let workspace_usage = sqlx::query_scalar!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 + RETURNING usage.usage", + workspace_id + ) + .fetch_one(_db) + .await + .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?; - let user_usage = if !premium_workspace { - Some(sqlx::query_scalar!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 - RETURNING usage.usage", - email - ) - .fetch_one(_db) - .await - .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?) - } else { - None - }; - (Some(workspace_usage), user_usage) + let user_usage = if !premium_workspace { + Some(sqlx::query_scalar!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 + RETURNING usage.usage", + email + ) + .fetch_one(_db) + .await + .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?) + } else { + None + }; + Ok((Some(workspace_usage), user_usage)) + }).await.unwrap_or_else(|e| { + tracing::error!("Could not update usage for workspace {workspace_id} and permissioned as {email}, stopped after 10s: {e:#}"); + Err(Error::internal_err(format!("Could not update usage for workspace {workspace_id} and permissioned as {email}, stopped after 10s: {e:#}"))) + }) } else { - (None, None) - }; + Ok((None, None)) + }?; if !premium_workspace { let is_super_admin = @@ -2893,7 +3366,7 @@ pub async fn push<'c, 'd>( .await? .unwrap_or(0); - if in_queue > MAX_FREE_EXECS.into() { + if in_queue > MAX_FREE_EXECS as i64 { return Err(error::Error::QuotaExceeded(format!( "User {email} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." ))); @@ -2907,7 +3380,7 @@ pub async fn push<'c, 'd>( .await? .unwrap_or(0); - if concurrent_runs > MAX_FREE_CONCURRENT_RUNS.into() { + if concurrent_runs > MAX_FREE_CONCURRENT_RUNS as i64 { return Err(error::Error::QuotaExceeded(format!( "User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." ))); @@ -2949,7 +3422,7 @@ pub async fn push<'c, 'd>( .await? .unwrap_or(0); - if in_queue_workspace > MAX_FREE_EXECS.into() { + if in_queue_workspace > MAX_FREE_EXECS as i64 { return Err(error::Error::QuotaExceeded(format!( "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." ))); @@ -2963,7 +3436,7 @@ pub async fn push<'c, 'd>( .await? .unwrap_or(0); - if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS.into() { + if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 { return Err(error::Error::QuotaExceeded(format!( "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." ))); @@ -3001,17 +3474,10 @@ pub async fn push<'c, 'd>( priority, apply_preprocessor, } => { - let extra = args.extra.get_or_insert_with(HashMap::new); if apply_preprocessor { preprocessed = Some(false); - extra.entry("wm_trigger".to_string()).or_insert_with(|| { - to_raw_value(&serde_json::json!({ - "kind": "webhook", - })) - }); - } else { - extra.remove("wm_trigger"); - } + } + ( Some(hash.0), Some(path), @@ -3100,12 +3566,18 @@ pub async fn push<'c, 'd>( None, None, ), - JobPayload::ScriptHub { path } => { - if path == "hub/7771/slack" || path == "hub/7836/slack" { + JobPayload::ScriptHub { path, apply_preprocessor } => { + if path == "hub/7771/slack" || path == "hub/7836/slack" || path == "hub/9084/slack" { + // these scripts send app reports to slack + // they use the slack bot token and should therefore be run with permissions to access it permissioned_as = SUPERADMIN_NOTIFICATION_EMAIL.to_string(); email = SUPERADMIN_NOTIFICATION_EMAIL; } + if apply_preprocessor { + preprocessed = Some(false); + } + let hub_script = get_full_hub_script_by_path(StripPath(path.clone()), &HTTP_CLIENT, Some(_db)) .await?; @@ -3375,19 +3847,8 @@ pub async fn push<'c, 'd>( priority, ) } - JobPayload::Flow { path, dedicated_worker, apply_preprocessor } => { + JobPayload::Flow { path, dedicated_worker, apply_preprocessor, version } => { let mut ntx = tx.into_tx().await?; - // Fetch the latest version of the flow. - let version = sqlx::query_scalar!( - "SELECT flow.versions[array_upper(flow.versions, 1)] AS \"version!: i64\" - FROM flow WHERE path = $1 AND workspace_id = $2", - &path, - &workspace_id - ) - .fetch_optional(&mut *ntx) - .await? - .ok_or_else(|| Error::internal_err(format!("not found flow at path {:?}", path)))?; - // Do not use the lite version unless all workers are updated. let data = if *DISABLE_FLOW_SCRIPT || (!*MIN_VERSION_IS_AT_LEAST_1_432.read().await && !*CLOUD_HOSTED) @@ -3404,27 +3865,24 @@ pub async fn push<'c, 'd>( }?; tx = PushIsolationLevel::Transaction(ntx); - let value = data.value().clone(); + let mut value = data.value().clone(); let priority = value.priority; let cache_ttl = value.cache_ttl.map(|x| x as i32); let custom_concurrency_key = value.concurrency_key.clone(); let concurrency_time_window_s = value.concurrency_time_window_s; - let concurrent_limit = value.concurrent_limit; + let mut concurrent_limit = value.concurrent_limit; + + if !apply_preprocessor { + value.preprocessor_module = None; + } else { + tag = None; + concurrent_limit = None; + preprocessed = Some(false); + } // this is a new flow being pushed, status is set to `value`. - let mut status = FlowStatus::new(&value); - let extra = args.extra.get_or_insert_with(HashMap::new); - if !apply_preprocessor { - status.preprocessor_module = None; - extra.remove("wm_trigger"); - } else { - preprocessed = Some(false); - extra.entry("wm_trigger".to_string()).or_insert_with(|| { - to_raw_value(&serde_json::json!({ - "kind": "webhook", - })) - }); - } + let status = FlowStatus::new(&value); + // Keep inserting `value` if not all workers are updated. // Starting at `v1.440`, the value is fetched on pull from the version id. let value_o = if !*MIN_VERSION_IS_AT_LEAST_1_440.read().await { @@ -3433,9 +3891,6 @@ pub async fn push<'c, 'd>( if same_worker { value.same_worker = true; } - if !apply_preprocessor { - value.preprocessor_module = None; - } Some(value) } else { // `raw_flow` is fetched on pull, the mutations from the other branch are replaced @@ -3575,6 +4030,7 @@ pub async fn push<'c, 'd>( ), }; + let final_priority: Option; #[cfg(not(feature = "enterprise"))] { @@ -3626,14 +4082,6 @@ pub async fn push<'c, 'd>( .map(|e| (Some(e.0), e.1)) .unwrap_or_else(|| (None, None)); - let per_workspace_workspaces = DEFAULT_TAGS_WORKSPACES.read().await; - let per_workspace = DEFAULT_TAGS_PER_WORKSPACE.load(std::sync::atomic::Ordering::Relaxed) - && (per_workspace_workspaces.is_none() - || per_workspace_workspaces - .as_ref() - .unwrap() - .contains(&workspace_id.to_string())); - let tag = if dedicated_worker.is_some_and(|x| x) { format!( "{}:{}{}", @@ -3651,6 +4099,7 @@ pub async fn push<'c, 'd>( } let interpolated_tag = tag.map(|x| interpolate_args(x, &args, workspace_id)); + let per_workspace = per_workspace_tag(&workspace_id).await; let default = || { let ntag = if job_kind.is_flow() || job_kind == JobKind::Identity { @@ -3658,6 +4107,7 @@ pub async fn push<'c, 'd>( } else if job_kind == JobKind::Dependencies || job_kind == JobKind::FlowDependencies || job_kind == JobKind::DeploymentCallback + || job_kind == JobKind::AppDependencies { // using the dependency tag for deployment callback for now. We can create a separate tag when we need "dependency".to_string() @@ -3709,21 +4159,7 @@ pub async fn push<'c, 'd>( }; if concurrent_limit.is_some() { - let concurrency_key = custom_concurrency_key - .map(|x| interpolate_args(x, &args, workspace_id)) - .unwrap_or(fullpath_with_workspace( - workspace_id, - script_path.as_ref(), - &job_kind, - )); - sqlx::query!( - "INSERT INTO concurrency_key(key, job_id) VALUES ($1, $2)", - concurrency_key, - job_id, - ) - .execute(&mut *tx) - .await - .map_err(|e| Error::internal_err(format!("Could not insert concurrency_key={concurrency_key} for job_id={job_id} script_path={script_path:?} workspace_id={workspace_id}: {e:#}")))?; + insert_concurrency_key(workspace_id, &args, &script_path, job_kind, custom_concurrency_key, &mut tx, job_id).await?; } let stringified_args = if *JOB_ARGS_AUDIT_LOGS { @@ -3741,17 +4177,79 @@ pub async fn push<'c, 'd>( Some("preprocessor") => Some(false), _ => None, }); + + let job_authed = match authed { + Some(authed) + if authed.email == email + && authed.username == permissioned_as_to_username(&permissioned_as) => + { + authed.clone() + } + _ => { + if authed.is_some() { + tracing::warn!("Authed passed to push is not the same as permissioned_as, refetching direclty permissions for job {job_id}...") + } + fetch_authed_from_permissioned_as( + permissioned_as.clone(), + email.to_string(), + workspace_id, + _db, + ) + .await + .map_err(|e| { + Error::internal_err(format!( + "Could not get permissions directly for job {job_id}: {e:#}" + )) + })? + } + }; + + let folders = job_authed + .folders + .iter() + .filter_map(|x| serde_json::to_value(x).ok()) + .collect::>(); + + // if let Err(err) = sqlx::query!("INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) + // values ($1, $2, $3, $4, $5, $6, $7, $8) + // ON CONFLICT (job_id) DO UPDATE SET email = $2, username = $3, is_admin = $4, is_operator = $5, folders = $6, groups = $7, workspace_id = $8", + // job_id, + // job_authed.email, + // job_authed.username, + // job_authed.is_admin, + // job_authed.is_operator, + // folders.as_slice(), + // job_authed.groups.as_slice(), + // workspace_id, + // ).execute(&mut *tx).await { + // tracing::error!("Could not insert job_perms for job {job_id}: {err:#}"); + // } + + sqlx::query!( - "INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job, - created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger, + "WITH inserted_job AS ( + INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job, + created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger, script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner, - flow_innermost_root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, + flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, - $19, $20, $21, $22, $23, $24, $25, $26, + $19, $20, $38, $21, $22, $23, $24, $25, $26, CASE WHEN $14::VARCHAR IS NOT NULL THEN 'schedule'::job_trigger_kind END, - ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)", + ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27) + ), + inserted_runtime AS ( + INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null) + ), + inserted_job_perms AS ( + INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) + values ($1, $32, $33, $34, $35, $36, $37, $2) + ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2 + ) + INSERT INTO v2_job_queue + (workspace_id, id, running, scheduled_for, started_at, tag, priority) + VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 THEN now() END, $30, $31)", job_id, workspace_id, raw_code, @@ -3783,35 +4281,43 @@ pub async fn push<'c, 'd>( cache_ttl, final_priority, preprocessed, - ) - .execute(&mut *tx) - .warn_after_seconds(1) - .await?; - - tracing::debug!("Pushing job {job_id} with tag {tag}, schedule_path {schedule_path:?}, script_path: {script_path:?}, email {email}, workspace_id {workspace_id}"); - let uuid = sqlx::query_scalar!( - "INSERT INTO v2_job_queue - (workspace_id, id, running, scheduled_for, started_at, tag, priority) - VALUES ($1, $2, $3, COALESCE($4, now()), CASE WHEN $3 THEN now() END, $5, $6) \ - RETURNING id AS \"id!\"", - workspace_id, - job_id, is_running, scheduled_for_o, tag, final_priority, - ) - .fetch_one(&mut *tx) - .warn_after_seconds(1) - .await - .map_err(|e| Error::internal_err(format!("Could not insert into queue {job_id} with tag {tag}, schedule_path {schedule_path:?}, script_path: {script_path:?}, email {email}, workspace_id {workspace_id}: {e:#}")))?; - - sqlx::query!( - "INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)", - job_id + job_authed.email, + job_authed.username, + job_authed.is_admin, + job_authed.is_operator, + folders.as_slice(), + job_authed.groups.as_slice(), + root_job.or(parent_job) ) .execute(&mut *tx) + .warn_after_seconds(1) .await?; + +// tracing::debug!("Pushing job {job_id} with tag {tag}, schedule_path {schedule_path:?}, script_path: {script_path:?}, email {email}, workspace_id {workspace_id}"); +// let uuid = sqlx::query_scalar!( +// "INSERT INTO v2_job_queue +// (workspace_id, id, running, scheduled_for, started_at, tag, priority) +// VALUES ($1, $2, $3, COALESCE($4, now()), CASE WHEN $3 THEN now() END, $5, $6) \ +// RETURNING id AS \"id!\"", +// workspace_id, +// job_id, +// , +// ) +// .fetch_one(&mut *tx) +// .warn_after_seconds(1) +// .await +// .map_err(|e| Error::internal_err(format!("Could not insert into queue {job_id} with tag {tag}, schedule_path {schedule_path:?}, script_path: {script_path:?}, email {email}, workspace_id {workspace_id}: {e:#}")))?; + + // sqlx::query!( + // "INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)", + // job_id + // ) + // .execute(&mut *tx) + // .await?; if let Some(flow_status) = flow_status { sqlx::query!( "INSERT INTO v2_job_status (id, flow_status) VALUES ($1, $2)", @@ -3819,6 +4325,7 @@ pub async fn push<'c, 'd>( Json(flow_status) as Json, ) .execute(&mut *tx) + .warn_after_seconds(1) .await?; } @@ -3829,54 +4336,7 @@ pub async fn push<'c, 'd>( QUEUE_PUSH_COUNT.inc(); } - if JOB_TOKEN.is_none() { - let job_authed = match authed { - Some(authed) - if authed.email == email - && authed.username == permissioned_as_to_username(&permissioned_as) => - { - authed.clone() - } - _ => { - if authed.is_some() { - tracing::warn!("Authed passed to push is not the same as permissioned_as, refetching direclty permissions for job {job_id}...") - } - fetch_authed_from_permissioned_as( - permissioned_as.clone(), - email.to_string(), - workspace_id, - _db, - ) - .await - .map_err(|e| { - Error::internal_err(format!( - "Could not get permissions directly for job {job_id}: {e:#}" - )) - })? - } - }; - let folders = job_authed - .folders - .iter() - .filter_map(|x| serde_json::to_value(x).ok()) - .collect::>(); - - if let Err(err) = sqlx::query!("INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) - values ($1, $2, $3, $4, $5, $6, $7, $8) - ON CONFLICT (job_id) DO UPDATE SET email = $2, username = $3, is_admin = $4, is_operator = $5, folders = $6, groups = $7, workspace_id = $8", - job_id, - job_authed.email, - job_authed.username, - job_authed.is_admin, - job_authed.is_operator, - folders.as_slice(), - job_authed.groups.as_slice(), - workspace_id, - ).execute(&mut *tx).await { - tracing::error!("Could not insert job_perms for job {job_id}: {err:#}"); - } - } { let uuid_string = job_id.to_string(); @@ -3934,13 +4394,39 @@ pub async fn push<'c, 'd>( script_path.as_ref().map(|x| x.as_str()), Some(hm), ) + .warn_after_seconds(1) .await?; } - Ok((uuid, tx)) + Ok((job_id, tx)) } -pub fn canceled_job_to_result(job: &QueuedJob) -> serde_json::Value { +pub async fn insert_concurrency_key<'d, 'c>(workspace_id: &str, args: &PushArgs<'d>, script_path: &Option, job_kind: JobKind, custom_concurrency_key: Option, tx: &mut Transaction<'c, Postgres>, job_id: Uuid) -> Result<(), Error> { + let concurrency_key = custom_concurrency_key + .map(|x| interpolate_args(x, args, workspace_id)) + .unwrap_or(fullpath_with_workspace( + workspace_id, + script_path.as_ref(), + &job_kind, + )); + sqlx::query!( + "WITH inserted_concurrency_counter AS ( + INSERT INTO concurrency_counter (concurrency_id, job_uuids) + VALUES ($1, '{}'::jsonb) + ON CONFLICT DO NOTHING + ) + INSERT INTO concurrency_key(key, job_id) VALUES ($1, $2)", + concurrency_key, + job_id, + ) + .execute(&mut **tx) + .warn_after_seconds(3) + .await + .map_err(|e| Error::internal_err(format!("Could not insert concurrency_key={concurrency_key} for job_id={job_id} script_path={script_path:?} workspace_id={workspace_id}: {e:#}")))?; + Ok(()) +} + +pub fn canceled_job_to_result(job: &MiniPulledJob) -> serde_json::Value { let reason = job .canceled_reason .as_deref() @@ -3987,7 +4473,7 @@ async fn restarted_flows_resolution( })?; let flow_data = cache::job::fetch_flow(db, row.job_kind, row.script_hash) - .or_else(|_| cache::job::fetch_preview_flow(db, &completed_flow_id, row.raw_flow)) + .or_else(|_| cache::job::fetch_preview_flow(db.into(), &completed_flow_id, row.raw_flow)) .await?; let flow_value = flow_data.value(); let flow_status = row @@ -4140,3 +4626,78 @@ async fn restarted_flows_resolution( flow_status.cleanup_module, )) } + + +#[derive(Serialize, Deserialize)] +pub struct SameWorkerPayload { + pub job_id: Uuid, + pub recoverable: bool, +} + +pub async fn get_same_worker_job( + db: &DB, + same_worker_job: &SameWorkerPayload, +) -> windmill_common::error::Result> { + sqlx::query_as::<_, PulledJob>( + "WITH ping AS ( + UPDATE v2_job_runtime SET ping = NOW() WHERE id = $1 + ), + started_at AS ( + UPDATE v2_job_queue SET started_at = NOW() WHERE id = $1 + ) + SELECT + v2_job_queue.workspace_id, + v2_job_queue.id, + v2_job.args, + v2_job.parent_job, + v2_job.created_by, + v2_job_queue.started_at, + scheduled_for, + v2_job.runnable_path, + v2_job.kind, + v2_job.runnable_id, + v2_job_queue.canceled_reason, + v2_job_queue.canceled_by, + v2_job.permissioned_as, + v2_job.permissioned_as_email, + v2_job_status.flow_status, + v2_job.tag, + v2_job.script_lang, + v2_job.same_worker, + v2_job.pre_run_error, + v2_job.concurrent_limit, + v2_job.concurrency_time_window_s, + v2_job.flow_innermost_root_job, + v2_job.timeout, + v2_job.flow_step_id, + v2_job.cache_ttl, + v2_job_queue.priority, + v2_job.preprocessed, + v2_job.script_entrypoint_override, + v2_job.trigger, + v2_job.trigger_kind, + v2_job.visible_to_owner, + v2_job.raw_code, + v2_job.raw_lock, + v2_job.raw_flow, + pj.runnable_path as parent_runnable_path, + p.email as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin, + p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders + FROM v2_job_queue + INNER JOIN v2_job ON v2_job.id = v2_job_queue.id + LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id + LEFT JOIN job_perms p ON p.job_id = v2_job.id + LEFT JOIN v2_job pj ON v2_job.parent_job = pj.id + WHERE v2_job_queue.id = $1 +", + ) + .bind(same_worker_job.job_id) + .fetch_optional(db) + .await + .map_err(|e| { + Error::internal_err(format!( + "Impossible to fetch same_worker job {}: {}", + same_worker_job.job_id, e + )) + }) +} \ No newline at end of file diff --git a/backend/windmill-queue/src/jobs_oss.rs b/backend/windmill-queue/src/jobs_oss.rs new file mode 100644 index 0000000000..987a56bf0d --- /dev/null +++ b/backend/windmill-queue/src/jobs_oss.rs @@ -0,0 +1,24 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::jobs_ee::*; + +#[cfg(not(feature = "private"))] +use chrono::{DateTime, Utc}; +#[cfg(not(feature = "private"))] +use uuid::Uuid; +#[cfg(not(feature = "private"))] +use windmill_common::DB; + +#[cfg(not(feature = "private"))] +#[allow(dead_code)] +pub(crate) async fn update_concurrency_counter( + _db: &DB, + _job_id: &Uuid, + _job_concurrency_key: String, + _jobs_uuids_init_json_value: serde_json::Value, + _pulled_job_id: String, + _job_custom_concurrency_time_window_s: i32, + _limit: i32, +) -> anyhow::Result<(bool, Option>)> { + Ok((true, None)) +} diff --git a/backend/windmill-queue/src/lib.rs b/backend/windmill-queue/src/lib.rs index b47a3c3238..9b6025e515 100644 --- a/backend/windmill-queue/src/lib.rs +++ b/backend/windmill-queue/src/lib.rs @@ -7,6 +7,10 @@ */ mod jobs; +#[cfg(feature = "private")] +pub mod jobs_ee; +pub mod jobs_oss; pub mod schedule; - pub use jobs::*; +pub mod flow_status; +pub mod tags; diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index af1fca6f5d..294ece7509 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -13,10 +13,12 @@ use sqlx::{PgExecutor, Postgres, Transaction}; use std::collections::HashMap; use std::str::FromStr; use windmill_common::db::Authed; -use windmill_common::ee::LICENSE_KEY_VALID; +use windmill_common::ee_oss::LICENSE_KEY_VALID; use windmill_common::flows::Retry; +use windmill_common::get_latest_flow_version_info_for_path; use windmill_common::jobs::JobPayload; use windmill_common::schedule::schedule_to_user; +use windmill_common::FlowVersionInfo; use windmill_common::DB; use windmill_common::{ error::{self, Result}, @@ -114,21 +116,21 @@ pub async fn push_scheduled_job<'c>( } let (payload, tag, timeout, on_behalf_of_email, created_by) = if schedule.is_flow { - let r = sqlx::query!( - "SELECT tag, dedicated_worker, on_behalf_of_email, edited_by from flow WHERE path = $1 and workspace_id = $2", - &schedule.script_path, + let FlowVersionInfo { + version, tag, dedicated_worker, on_behalf_of_email, edited_by, .. + } = get_latest_flow_version_info_for_path( + &mut *tx, &schedule.workspace_id, + &schedule.script_path, + false, ) - .fetch_optional(&mut *tx) .await?; - let (tag, dedicated_worker, on_behalf_of_email, edited_by) = r - .map(|x| (x.tag, x.dedicated_worker, x.on_behalf_of_email, x.edited_by)) - .unwrap_or_else(|| (None, None, None, "".to_string())); ( JobPayload::Flow { path: schedule.script_path.clone(), dedicated_worker, apply_preprocessor: false, + version, }, tag, None, diff --git a/backend/windmill-queue/src/tags.rs b/backend/windmill-queue/src/tags.rs new file mode 100644 index 0000000000..6cf6620039 --- /dev/null +++ b/backend/windmill-queue/src/tags.rs @@ -0,0 +1,11 @@ +use windmill_common::worker::{DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES}; + +pub async fn per_workspace_tag(workspace_id: &str) -> bool { + let per_workspace_workspaces = DEFAULT_TAGS_WORKSPACES.read().await; + DEFAULT_TAGS_PER_WORKSPACE.load(std::sync::atomic::Ordering::Relaxed) + && (per_workspace_workspaces.is_none() + || per_workspace_workspaces + .as_ref() + .unwrap() + .contains(&workspace_id.to_string())) +} diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 73e91da388..a3b36a5c04 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -10,6 +10,7 @@ path = "src/lib.rs" [features] default = [] +private = [] prometheus = ["dep:prometheus", "windmill-common/prometheus"] enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:pem", "dep:tokio-util"] mssql = ["dep:tiberius"] @@ -20,7 +21,7 @@ flow_testing = [] cloud = [] sqlx = [] deno_core = ["dep:deno_fetch", "dep:deno_webidl", "dep:deno_web", "dep:deno_net", "dep:deno_console", "dep:deno_url", "dep:deno_core", - "dep:deno_ast", "dep:deno_tls", "dep:deno_permissions", "dep:deno_io", "dep:deno_runtime", "dep:deno_telemetry", "dep:deno_error"] + "dep:deno_ast", "dep:deno_tls", "dep:deno_permissions", "dep:deno_io", "dep:deno_runtime", "dep:deno_telemetry", "dep:deno_error", "dep:winapi"] otel = ["windmill-common/otel", "dep:opentelemetry"] dind = ["dep:bollard"] php = ["dep:windmill-parser-php"] @@ -29,16 +30,22 @@ oracledb = ["dep:oracle"] python = ["dep:windmill-parser-py", "dep:windmill-parser-py-imports"] csharp = ["dep:windmill-parser-csharp"] rust = ["dep:windmill-parser-rust"] +nu = ["dep:windmill-parser-nu"] +java = ["dep:windmill-parser-java"] +duckdb = ["dep:duckdb"] [dependencies] windmill-queue.workspace = true windmill-audit.workspace = true # there isn't really a reason for audit-worth actions to happen in the worker. windmill-common = { workspace = true, default-features = false } +windmill-macros.workspace = true windmill-parser.workspace = true windmill-parser-ts.workspace = true windmill-parser-go.workspace = true windmill-parser-rust = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } +windmill-parser-nu = { workspace = true, optional = true } +windmill-parser-java = { workspace = true, optional = true } windmill-parser-py = { workspace = true, optional = true } windmill-parser-yaml.workspace = true windmill-parser-py-imports = { workspace = true, optional = true } @@ -47,10 +54,12 @@ windmill-parser-sql.workspace = true windmill-parser-graphql.workspace = true windmill-parser-php = { workspace = true, optional = true } windmill-git-sync.workspace = true +flume.workspace = true sqlx.workspace = true uuid.workspace = true tracing.workspace = true tokio.workspace = true +tokio-stream.workspace = true serde.workspace = true serde_json.workspace = true futures.workspace = true @@ -70,6 +79,7 @@ dyn-iter.workspace = true once_cell.workspace = true tokio-postgres.workspace = true bit-vec.workspace = true +url.workspace = true deno_telemetry = { workspace = true, optional = true } deno_fetch = { workspace = true, optional = true } deno_webidl = { workspace = true, optional = true } @@ -83,6 +93,8 @@ deno_tls = { workspace = true, optional = true } deno_permissions = { workspace = true, optional = true } deno_io = { workspace = true, optional = true } deno_error = { workspace = true, optional = true } +async-stream.workspace = true +duckdb = { workspace = true, optional = true } postgres-native-tls.workspace = true native-tls.workspace = true @@ -97,6 +109,7 @@ urlencoding.workspace = true nix.workspace = true bytes.workspace = true reqwest.workspace = true +reqwest-middleware.workspace = true hex.workspace = true tiberius = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true } @@ -105,6 +118,8 @@ object_store = { workspace = true, optional = true} convert_case.workspace = true yaml-rust.workspace = true backon.workspace = true +winapi = { workspace = true, optional = true } +pep440_rs.workspace = true opentelemetry = { workspace = true, optional = true } bollard = { workspace = true, optional = true } @@ -123,4 +138,5 @@ deno_tls = { workspace = true, optional = true } deno_permissions = { workspace = true, optional = true } deno_io = { workspace = true, optional = true } deno_runtime = { workspace = true, optional = true } -deno_telemetry = { workspace = true, optional = true } \ No newline at end of file +deno_telemetry = { workspace = true, optional = true } +winapi = { workspace = true, optional = true } diff --git a/backend/windmill-worker/nsjail/download.rust.config.proto b/backend/windmill-worker/nsjail/download.rust.config.proto new file mode 100644 index 0000000000..7239a77b74 --- /dev/null +++ b/backend/windmill-worker/nsjail/download.rust.config.proto @@ -0,0 +1,117 @@ +name: "rust download script" + +mode: ONCE +hostname: "rust" +log_level: ERROR + +disable_rl: true + +cwd: "/tmp" + +clone_newnet: false + +keep_caps: false +keep_env: true +mount_proc: true + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=500000000" +} + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/random" + dst: "/dev/random" + is_bind: true +} +mount { + src: "{JOB_DIR}" + dst: "/tmp" + is_bind: true + rw: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +iface_no_lo: true + +mount { + src: "{BUILD}" + dst: "{BUILD}" + is_bind: true + mandatory: false + rw: true +} +mount { + src: "{CARGO_HOME}" + dst: "{CARGO_HOME}" + is_bind: true + # Readonly + rw: false +} + +mount { + src: "{BUILD}/registry" + dst: "{CARGO_HOME}/registry" + is_bind: true + mandatory: true + # Read-write + rw: true +} + +mount { + src: "{BUILD}/git" + dst: "{CARGO_HOME}/git" + is_bind: true + mandatory: true + # Read-write + rw: true +} + +{DEV} diff --git a/backend/windmill-worker/nsjail/run.java.config.proto b/backend/windmill-worker/nsjail/run.java.config.proto new file mode 100644 index 0000000000..032072fb7c --- /dev/null +++ b/backend/windmill-worker/nsjail/run.java.config.proto @@ -0,0 +1,107 @@ +name: "java run script" + +mode: ONCE +hostname: "java" +log_level: ERROR + +disable_rl: true + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +keep_caps: false +keep_env: true +mount_proc: true + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=500000000" +} + + +mount { + src: "{JOB_DIR}/target" + dst: "/tmp/target" + is_bind: true + mandatory: false +} + +mount { + src: "{JOB_DIR}/args.json" + dst: "/tmp/args.json" + is_bind: true +} + +mount { + src: "{JOB_DIR}/result.json" + dst: "/tmp/result.json" + rw: true + is_bind: true +} + +mount { + src: "{CACHE_DIR}" + dst: "{CACHE_DIR}" + is_bind: true + mandatory: false +} + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/random" + dst: "/dev/random" + is_bind: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +iface_no_lo: true + +{SHARED_MOUNT} diff --git a/backend/windmill-worker/nsjail/run.nu.config.proto b/backend/windmill-worker/nsjail/run.nu.config.proto new file mode 100644 index 0000000000..8d27d92455 --- /dev/null +++ b/backend/windmill-worker/nsjail/run.nu.config.proto @@ -0,0 +1,106 @@ +name: "nu run script" + +mode: ONCE +hostname: "nu" +log_level: ERROR + +disable_rl: true + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +keep_caps: false +keep_env: true +mount_proc: true + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=800000000" +} + +mount { + src: "{NU_PATH}" + dst: "{NU_PATH}" + is_bind: true +} + +mount { + src: "{JOB_DIR}/main.nu" + dst: "/tmp/main.nu" + is_bind: true +} + +mount { + src: "{JOB_DIR}/result.json" + dst: "/tmp/result.json" + rw: true + is_bind: true +} + +mount { + src: "{JOB_DIR}/args.json" + dst: "/tmp/args.json" + rw: true + is_bind: true +} +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/random" + dst: "/dev/random" + is_bind: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +iface_no_lo: true + +{SHARED_MOUNT} + +envar: "HOME=/tmp" diff --git a/backend/windmill-worker/nsjail/run.rust.config.proto b/backend/windmill-worker/nsjail/run.rust.config.proto index 3357cd88a9..d5265f8ddd 100644 --- a/backend/windmill-worker/nsjail/run.rust.config.proto +++ b/backend/windmill-worker/nsjail/run.rust.config.proto @@ -104,3 +104,6 @@ mount { } {SHARED_MOUNT} + +{DEV} + diff --git a/backend/windmill-worker/src/agent_workers.rs b/backend/windmill-worker/src/agent_workers.rs new file mode 100644 index 0000000000..f5f5a3b0cc --- /dev/null +++ b/backend/windmill-worker/src/agent_workers.rs @@ -0,0 +1,40 @@ +use reqwest::header::HeaderMap; +use uuid::Uuid; +use windmill_common::{agent_workers::QueueInitJob, worker::HttpClient}; +use windmill_queue::{JobAndPerms, JobCompleted}; + +pub async fn queue_init_job(client: &HttpClient, content: &str) -> anyhow::Result { + client + .post( + "/api/agent_workers/queue_init_job", + None, + &QueueInitJob { content: content.to_string() }, + ) + .await + .and_then(|x: String| Uuid::parse_str(&x).map_err(|e| anyhow::anyhow!(e))) +} + +pub async fn pull_job( + client: &HttpClient, + headers: Option, + body: Option, +) -> anyhow::Result> { + client + .post("/api/agent_workers/pull_job", headers, &body) + .await +} + +pub async fn send_result(client: &HttpClient, jc: JobCompleted) -> anyhow::Result { + client + .post( + &format!( + "/api/w/{}/agent_workers/send_result/{}", + jc.job.workspace_id, jc.job.id + ), + None, + &jc, + ) + .await +} + +pub const UPDATE_PING_URL: &str = "/api/agent_workers/update_ping"; diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index a1b03acf1e..1783641da3 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -5,16 +5,22 @@ use std::{collections::HashMap, os::unix::fs::PermissionsExt, path::PathBuf, pro use std::{collections::HashMap, path::PathBuf, process::Stdio}; use anyhow::anyhow; +use futures::future::try_join_all; use itertools::Itertools; +use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; use tokio::process::Command; use uuid::Uuid; use windmill_common::{ error, - jobs::QueuedJob, - worker::{to_raw_value, write_file, write_file_at_user_defined_location, WORKER_CONFIG}, + worker::{ + is_allowed_file_location, to_raw_value, write_file, write_file_at_user_defined_location, + Connection, WORKER_CONFIG, + }, }; -use windmill_parser_yaml::{AnsibleRequirements, ResourceOrVariablePath}; +use windmill_queue::MiniPulledJob; + +use windmill_parser_yaml::{AnsibleRequirements, GitRepo, ResourceOrVariablePath}; use windmill_queue::{append_logs, CanceledBy}; use crate::{ @@ -24,10 +30,11 @@ use crate::{ start_child_process, transform_json, OccupancyMetrics, }, handle_child::handle_child, - python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile, PyVersion}, - AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, - PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, + python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile}, + PyVAlias, DISABLE_NSJAIL, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, + PY_INSTALL_DIR, TZ_ENV, }; +use windmill_common::client::AuthedClient; lazy_static::lazy_static! { static ref ANSIBLE_PLAYBOOK_PATH: String = @@ -38,14 +45,301 @@ lazy_static::lazy_static! { } const NSJAIL_CONFIG_RUN_ANSIBLE_CONTENT: &str = include_str!("../nsjail/run.ansible.config.proto"); +const WINDMILL_ANSIBLE_PASSWORD_FILENAME: &str = ".windmill.ansible_vault_password_file"; +async fn clone_repo( + repo: &GitRepo, + job_dir: &str, + job_id: &Uuid, + worker_name: &str, + conn: &Connection, + mem_peak: &mut i32, + canceled_by: &mut Option, + w_id: &str, + occupancy_metrics: &mut OccupancyMetrics, + git_ssh_cmd: &str, +) -> error::Result { + let target_path = is_allowed_file_location(job_dir, &repo.target_path)?; + + let mut clone_cmd = Command::new(GIT_PATH.as_str()); + clone_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .args(["clone", "--quiet"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(branch) = &repo.branch { + clone_cmd.args(["--branch", branch]); + } + clone_cmd.arg(&repo.url); + clone_cmd.arg(&target_path); + + let clone_cmd_child = start_child_process(clone_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + clone_cmd_child, + false, + worker_name, + w_id, + "git clone", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + // Checkout specific commit if provided + if let Some(commit) = &repo.commit { + let mut checkout_cmd = Command::new(GIT_PATH.as_str()); + checkout_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .arg("-C") + .arg(&target_path) + .args(["checkout", "--quiet", commit]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + checkout_cmd_child, + false, + worker_name, + w_id, + "git checkout", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + } + + let mut rev_parse_cmd = Command::new(GIT_PATH.as_str()); + + let commit_hash_output = rev_parse_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .arg("-C") + .arg(&target_path) + .args(["rev-parse", "HEAD"]) + .stderr(Stdio::piped()) + .output() + .await?; + + if !commit_hash_output.status.success() { + let stderr = String::from_utf8(commit_hash_output.stderr)?; + return Err(anyhow!("Error getting git repo commit hash: {stderr}").into()); + } + + let commit_hash = String::from_utf8(commit_hash_output.stdout)? + .trim() + .to_string(); + + Ok(commit_hash) +} + +pub fn create_empty_dir(path: &PathBuf) -> std::io::Result<()> { + if path.exists() { + if path.is_dir() { + let mut entries = std::fs::read_dir(&path)?; + if entries.next().is_some() { + return Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!( + "Directory '{}' already exists and is not empty", + path.display() + ), + )); + } + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!("Path '{}' exists and is not a directory", path.display()), + )) + } + } else { + std::fs::create_dir_all(path) + } +} + +async fn clone_repo_without_history( + repo: &GitRepo, + full_commit: &str, + job_dir: &str, + job_id: &Uuid, + worker_name: &str, + conn: &Connection, + mem_peak: &mut i32, + canceled_by: &mut Option, + w_id: &str, + occupancy_metrics: &mut OccupancyMetrics, + git_ssh_cmd: &str, +) -> error::Result<()> { + let target_path = is_allowed_file_location(job_dir, &repo.target_path)?; + + create_empty_dir(&target_path)?; + + let mut init_cmd = Command::new(GIT_PATH.as_str()); + init_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .arg("-C") + .arg(&target_path) + .args(["init", "--quiet"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(branch) = &repo.branch { + init_cmd.args(["--initial-branch", branch]); + } + + let init_cmd_child = start_child_process(init_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + init_cmd_child, + false, + worker_name, + w_id, + "git init", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + let mut add_remote_cmd = Command::new(GIT_PATH.as_str()); + add_remote_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .arg("-C") + .arg(&target_path) + .args(vec!["remote", "add", "origin", &repo.url]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let add_remote_cmd_child = start_child_process(add_remote_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + add_remote_cmd_child, + false, + worker_name, + w_id, + "git add remote", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + let mut fetch_cmd = Command::new(GIT_PATH.as_str()); + fetch_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .arg("-C") + .arg(&target_path) + .args(vec!["fetch", "--depth=1", "--quiet", "origin", full_commit]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let fetch_cmd_child = start_child_process(fetch_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + fetch_cmd_child, + false, + worker_name, + w_id, + "git fetch", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + let mut checkout_cmd = Command::new(GIT_PATH.as_str()); + checkout_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .arg("-C") + .arg(&target_path) + .args(["checkout", "--quiet", "FETCH_HEAD"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + checkout_cmd_child, + false, + worker_name, + w_id, + "git checkout", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + Ok(()) +} async fn handle_ansible_python_deps( job_dir: &str, requirements_o: Option<&String>, ansible_reqs: Option<&AnsibleRequirements>, w_id: &str, job_id: &Uuid, - db: &sqlx::Pool, + conn: &Connection, worker_name: &str, worker_dir: &str, mem_peak: &mut i32, @@ -76,11 +370,11 @@ async fn handle_ansible_python_deps( mem_peak, canceled_by, job_dir, - db, + conn, worker_name, w_id, &mut Some(occupancy_metrics), - PyVersion::Py311, + PyVAlias::Py311.into(), false, ) .await @@ -94,20 +388,17 @@ async fn handle_ansible_python_deps( if requirements.len() > 0 { let mut venv_path = handle_python_reqs( - requirements - .split("\n") - .filter(|x| !x.starts_with("--")) - .collect(), + crate::python_executor::split_requirements(requirements), job_id, w_id, mem_peak, canceled_by, - db, + conn, worker_name, job_dir, worker_dir, &mut Some(occupancy_metrics), - crate::python_executor::PyVersion::Py311, + PyVAlias::default().into(), ) .await?; additional_python_paths.append(&mut venv_path); @@ -115,7 +406,7 @@ async fn handle_ansible_python_deps( Ok(additional_python_paths) } -async fn install_galaxy_collections( +pub async fn install_galaxy_collections( collections_yml: &str, job_dir: &str, job_id: &Uuid, @@ -123,8 +414,9 @@ async fn install_galaxy_collections( w_id: &str, mem_peak: &mut i32, canceled_by: &mut Option, - db: &sqlx::Pool, + conn: &Connection, occupancy_metrics: &mut OccupancyMetrics, + git_ssh_cmd: &str, ) -> anyhow::Result<()> { write_file(job_dir, "requirements.yml", collections_yml)?; @@ -132,18 +424,55 @@ async fn install_galaxy_collections( job_id, w_id, "\n\n--- ANSIBLE GALAXY INSTALL ---\n".to_string(), - db, + conn, ) .await; - let mut galaxy_command = Command::new(ANSIBLE_GALAXY_PATH.as_str()); - galaxy_command + + let mut galaxy_roles_cmd = Command::new(ANSIBLE_GALAXY_PATH.as_str()); + galaxy_roles_cmd .current_dir(job_dir) .env_clear() .envs(PROXY_ENVS.clone()) .env("PATH", PATH_ENV.as_str()) .env("TZ", TZ_ENV.as_str()) - // .env("BASE_INTERNAL_URL", base_internal_url) - // .env("HOME", HOME_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .args(vec![ + "role", + "install", + "-r", + "requirements.yml", + "-p", + "./roles", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let child = start_child_process(galaxy_roles_cmd, ANSIBLE_GALAXY_PATH.as_str()).await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + w_id, + "ansible-galaxy role install", + None, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + let mut galaxy_collections_cmd = Command::new(ANSIBLE_GALAXY_PATH.as_str()); + galaxy_collections_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("GIT_SSH_COMMAND", git_ssh_cmd) .args(vec![ "collection", "install", @@ -155,36 +484,290 @@ async fn install_galaxy_collections( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let child = start_child_process(galaxy_command, ANSIBLE_GALAXY_PATH.as_str()).await?; + let child = start_child_process(galaxy_collections_cmd, ANSIBLE_GALAXY_PATH.as_str()).await?; handle_child( job_id, - db, + conn, mem_peak, canceled_by, child, !*DISABLE_NSJAIL, worker_name, w_id, - "ansible galaxy install", + "ansible-galaxy collection install", None, false, &mut Some(occupancy_metrics), + None, ) .await?; Ok(()) } +#[derive(Serialize, Deserialize)] +pub struct AnsibleDependencyLocks { + pub python_lockfile: String, + pub git_repos: HashMap, // URL to full commit hash + pub collections_and_roles: String, + pub collections_and_roles_logs: String, + // pub collection_versions: HashMap, // + // pub role_versions: HashMap, +} + +pub async fn get_collection_locks( + job_dir: &str, +) -> anyhow::Result<(HashMap, String)> { + let mut ansible_cmd = Command::new(ANSIBLE_GALAXY_PATH.as_str()); + + ansible_cmd + .current_dir(job_dir) + .args(["collection", "list", "--format", "json", "-p", "./"]); + + let output = ansible_cmd.output().await?; + + let mut ret = HashMap::new(); + let mut logs = String::new(); + + if !output.status.success() { + let stderr = String::from_utf8(output.stderr)?; + return Err(anyhow!( + "Error getting ansible collection versions: {stderr}" + )); + } + + let stdout = String::from_utf8(output.stdout)?; + + let val: serde_json::Value = serde_json::from_str(&stdout)?; + + let Some(own_collections) = val.get(format!("{}/ansible_collections", job_dir)) else { + return Ok((ret, logs)); + }; + + let collections = own_collections.as_object().ok_or(anyhow!( + "Expected an object (map) for the `ansible-galaxy collection list` command output and got {}", + own_collections + ))?; + + for (c_name, c) in collections.iter() { + if let Some(v) = c.get("version").and_then(|v| v.as_str()) { + // TODO: Check if version is not something like `(undefined)` + ret.insert(c_name.clone(), v.to_string()); + } else { + logs.push_str(&format!("Failed to get version for collection `{}`. Expected an object with a string in the `version` field but received {}\n", c_name, c)); + } + } + + Ok((ret, logs)) +} + +pub async fn get_role_locks(job_dir: &str) -> anyhow::Result<(HashMap, String)> { + let mut ansible_cmd = Command::new(ANSIBLE_GALAXY_PATH.as_str()); + + ansible_cmd + .current_dir(job_dir) + .args(["role", "list", "-p", "./roles"]); + + let output = ansible_cmd.output().await?; + let mut ret = HashMap::new(); + let mut logs = String::new(); + + if !output.status.success() { + let stderr = String::from_utf8(output.stderr)?; + logs.push_str(&format!("Error getting ansible role versions: {stderr}")); + return Ok((ret, logs)); + } + + let stdout = String::from_utf8(output.stdout)?; + + let mut lines = stdout.lines(); + + while let Some(line) = lines.next() { + if line == format!("# {}/roles", job_dir) { + break; + } + } + + for line in lines { + let line = line.strip_prefix("-").unwrap_or(line); + let mut cols = line.split(","); + + if let Some(name) = cols.next().map(|n| n.trim()) { + if let Some(version) = cols.next().map(|v| v.trim()) { + // TODO: Check if version is not something like `(undefined)` + ret.insert(name.to_string(), version.to_string()); + } else { + logs.push_str(&format!("Failed to get version for role `{}`.", name)); + } + } + } + + Ok((ret, logs)) +} + +pub async fn get_git_repo_full_head_commit_hash( + repo: &GitRepo, + git_ssh_cmd: &str, +) -> anyhow::Result { + let mut git_cmd = Command::new(GIT_PATH.as_str()); + + git_cmd + .env("GIT_SSH_COMMAND", git_ssh_cmd) + .args(["ls-remote", &repo.url, "HEAD"]); + + let output = git_cmd.stderr(Stdio::piped()).output().await?; + + if !output.status.success() { + let stderr = String::from_utf8(output.stderr)?; + return Err(anyhow!("Error getting git repo commit hash: {stderr}")); + } + + let stdout = String::from_utf8(output.stdout)?; + + let lines: Vec<&str> = stdout.lines().collect(); + + if lines.len() != 1 { + return Err(anyhow!("Unexpected output format for git ls-remote",)); + } + + Ok(lines + .first() + .ok_or(anyhow!( + "The HEAD commit hash was not found for repo `{}`", + &repo.url + ))? + .split_whitespace() + .next() + .map(|s| s.to_string()) + .ok_or(anyhow!("Unexpected output format for git ls-remote"))?) +} + +pub async fn get_git_repos_lock( + repos: &Vec, + job_dir: &str, + job_id: &Uuid, + worker_name: &str, + conn: &Connection, + mem_peak: &mut i32, + canceled_by: &mut Option, + w_id: &str, + occupancy_metrics: &mut OccupancyMetrics, + git_ssh_cmd: &str, +) -> anyhow::Result> { + let mut ret = HashMap::new(); + + for repo in repos { + if repo.commit.is_some() { + ret.insert( + repo.url.to_string(), + clone_repo( + repo, + job_dir, + job_id, + worker_name, + conn, + mem_peak, + canceled_by, + w_id, + occupancy_metrics, + git_ssh_cmd, + ) + .await?, + ); + } else { + ret.insert( + repo.url.to_string(), + get_git_repo_full_head_commit_hash(repo, git_ssh_cmd).await?, + ); + } + } + + Ok(ret) +} + +pub fn create_ansible_cfg( + reqs: Option<&AnsibleRequirements>, + job_dir: &str, + vault_password_file_exists: bool, +) -> error::Result<()> { + let mut passwords_cfg = String::new(); + if vault_password_file_exists { + passwords_cfg.push_str(&format!( + "vault_password_file = {WINDMILL_ANSIBLE_PASSWORD_FILENAME}\n" + )); + } + if let Some(vault_ids) = reqs.as_ref().map(|r| &r.vault_id) { + if !vault_ids.is_empty() { + let password_files = vault_ids.join(","); + + passwords_cfg.push_str(&format!("vault_identity_list = {password_files}\n")); + } + } + let ansible_cfg_content = format!( + r#" +[defaults] +collections_path = ./ +roles_path = ./roles +home={job_dir}/.ansible +local_tmp={job_dir}/.ansible/tmp +remote_tmp={job_dir}/.ansible/tmp +{passwords_cfg} +"# + ); + + write_file(job_dir, "ansible.cfg", &ansible_cfg_content)?; + + Ok(()) +} + +pub async fn get_git_ssh_cmd( + reqs: &AnsibleRequirements, + job_dir: &str, + client: &AuthedClient, +) -> error::Result { + let ssh_id_files = try_join_all(reqs.git_ssh_identity.iter().enumerate().map( + async |(i, var_path)| -> error::Result { + let id_file_name = format!(".ssh_id_priv_{}", i); + let loc = is_allowed_file_location(job_dir, &id_file_name)?; + + let mut content = client.get_variable_value(var_path).await.map_err(|e| { + error::Error::NotFound(format!( + "Variable {var_path} not found for git ssh identity: {e:#}" + )) + })?; + content.push_str("\n"); + + let file = write_file(job_dir, &id_file_name, &content)?; + + #[cfg(unix)] + { + let perm = std::os::unix::fs::PermissionsExt::from_mode(0o600); + file.set_permissions(perm)?; + } + + Ok(format!( + " -i '{}'", + loc.to_string_lossy().replace('\'', r"'\''") + )) + }, + )) + .await?; + + let git_ssh_cmd = format!("ssh -o StrictHostKeyChecking=no{}", ssh_id_files.join("")); + Ok(git_ssh_cmd) +} + pub async fn handle_ansible_job( requirements_o: Option<&String>, job_dir: &str, worker_dir: &str, worker_name: &str, - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, inner_content: &String, shared_mount: &str, base_internal_url: &str, @@ -197,17 +780,50 @@ pub async fn handle_ansible_job( "ansible", )?; + let req_lockfiles: Option = if let Some(s) = requirements_o { + if let Ok(lockfile) = serde_json::from_str(s) { + Some(lockfile) + } else { + if !s.trim_start().starts_with('{') { + append_logs( + &job.id, + &job.workspace_id, + format!("WARN: lockfile seems to be in an older version, roles and collections are therefore using the latest version and not the one locked at deployment. Redeploy the script to correct this"), + conn, + ) + .await; + Some(AnsibleDependencyLocks { + python_lockfile: s.to_string(), + git_repos: HashMap::new(), + collections_and_roles: String::new(), + collections_and_roles_logs: String::new(), + }) + } else { + append_logs( + &job.id, + &job.workspace_id, + format!("WARN: lockfile could not be parsed: {s}"), + conn, + ) + .await; + None + } + } + } else { + None + }; + let (logs, reqs, playbook) = windmill_parser_yaml::parse_ansible_reqs(inner_content)?; - append_logs(&job.id, &job.workspace_id, logs, db).await; + append_logs(&job.id, &job.workspace_id, logs, conn).await; write_file(job_dir, "main.yml", &playbook)?; let additional_python_paths = handle_ansible_python_deps( job_dir, - requirements_o, + req_lockfiles.as_ref().map(|r| &r.python_lockfile), reqs.as_ref(), &job.workspace_id, &job.id, - db, + conn, worker_name, worker_dir, mem_peak, @@ -216,6 +832,11 @@ pub async fn handle_ansible_job( ) .await?; + let git_ssh_cmd = &match &reqs { + Some(r) => get_git_ssh_cmd(r, job_dir, client).await?, + None => "ssh".to_string(), + }; + let interpolated_args; if let Some(args) = &job.args { let mut args = args.0.clone(); @@ -224,7 +845,7 @@ pub async fn handle_ansible_job( args.insert(name.clone(), to_raw_value(path)); } } - if let Some(x) = transform_json(client, &job.workspace_id, &args, job, db).await? { + if let Some(x) = transform_json(client, &job.workspace_id, &args, job, conn).await? { write_file( job_dir, "args.json", @@ -262,55 +883,135 @@ pub async fn handle_ansible_job( }) .unwrap_or_else(|| vec![]); - let authed_client = client.get_authed().await; let mut nsjail_extra_mounts = vec![]; - if let Some(r) = reqs { + if let Some(r) = reqs.as_ref() { nsjail_extra_mounts = create_file_resources( &job.id, &job.workspace_id, job_dir, interpolated_args.as_ref(), &r, - &authed_client, - db, + &client, + conn, ) .await?; - if let Some(collections) = r.collections { + for repo in &r.git_repos { + append_logs( + &job.id, + &job.workspace_id, + format!("\nCloning {}...\n", &repo.url), + conn, + ) + .await; + if let Some(full_commit_hash) = req_lockfiles + .as_ref() + .and_then(|r| r.git_repos.get(&repo.url)) + { + clone_repo_without_history( + repo, + full_commit_hash, + job_dir, + &job.id, + worker_name, + conn, + mem_peak, + canceled_by, + &job.workspace_id, + occupancy_metrics, + git_ssh_cmd, + ) + .await + .map_err(|e| anyhow!("Failed to clone git repo `{}`: {e}", repo.url))?; + } else { + if req_lockfiles.is_some() { + append_logs( + &job.id, + &job.workspace_id, + format!("Warning: `{}` is using latest commit because the lockfile didn't store a commit hash for this repo. Updates to the repo could break the deployed playbook.\n", &repo.url), + conn, + ) + .await; + } + clone_repo( + repo, + job_dir, + &job.id, + worker_name, + conn, + mem_peak, + canceled_by, + &job.workspace_id, + occupancy_metrics, + git_ssh_cmd, + ) + .await + .map_err(|e| anyhow!("Failed to clone git repo `{}`: {e}", repo.url))?; + } + + append_logs( + &job.id, + &job.workspace_id, + format!("Cloned {} into {}\n", &repo.url, &repo.target_path), + conn, + ) + .await; + } + + if let Some(collections) = r.roles_and_collections.as_ref() { + let empty = String::new(); + let (lockfile, logs) = req_lockfiles + .as_ref() + .and_then(|r| { + if r.collections_and_roles.is_empty() { + None + } else { + Some((&r.collections_and_roles, &r.collections_and_roles_logs)) + } + }) + .unwrap_or((collections, &empty)); + + if !logs.is_empty() { + append_logs(&job.id, &job.workspace_id, logs, conn).await; + } + install_galaxy_collections( - collections.as_str(), + lockfile, job_dir, &job.id, worker_name, &job.workspace_id, mem_peak, canceled_by, - db, + conn, occupancy_metrics, + git_ssh_cmd, ) .await?; } } + append_logs( &job.id, &job.workspace_id, "\n\n--- ANSIBLE PLAYBOOK EXECUTION ---\n".to_string(), - db, + conn, ) .await; - let ansible_cfg_content = format!( - r#" -[defaults] -collections_path = ./ -roles_path = ./roles -home={job_dir}/.ansible -local_tmp={job_dir}/.ansible/tmp -remote_tmp={job_dir}/.ansible/tmp -"# - ); - write_file(job_dir, "ansible.cfg", &ansible_cfg_content)?; - let mut reserved_variables = get_reserved_variables(job, &authed_client.token, db).await?; + let vault_password_file_exists = match reqs.as_ref().and_then(|x| x.vault_password.as_ref()) { + Some(var_path) => { + let password = client.get_variable_value(&var_path).await?; + write_file(job_dir, WINDMILL_ANSIBLE_PASSWORD_FILENAME, &password)?; + true + } + None => false, + }; + + create_ansible_cfg(reqs.as_ref(), job_dir, vault_password_file_exists)?; + + let mut reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; let additional_python_paths_folders = additional_python_paths.join(":"); if !*DISABLE_NSJAIL { @@ -419,7 +1120,7 @@ fi handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -430,6 +1131,7 @@ fi job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_and_check_result(job_dir).await @@ -489,8 +1191,8 @@ async fn create_file_resources( job_dir: &str, args: Option<&HashMap>>, r: &AnsibleRequirements, - client: &crate::AuthedClient, - db: &sqlx::Pool, + client: &AuthedClient, + conn: &Connection, ) -> error::Result> { let mut logs = String::new(); let mut nsjail_mounts: Vec = vec![]; @@ -560,13 +1262,13 @@ async fn create_file_resources( file_res.target_path, file_res.resource_path )); } - append_logs(job_id, w_id, logs, db).await; + append_logs(job_id, w_id, logs, conn).await; Ok(nsjail_mounts) } async fn get_resource_or_variable_content( - client: &crate::AuthedClient, + client: &AuthedClient, path: &ResourceOrVariablePath, job_id: String, ) -> anyhow::Result { diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 9eac965ad2..fc92544522 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -15,17 +15,13 @@ use tokio::process::Command; use uuid::Uuid; use windmill_common::{ error::Error, - jobs::QueuedJob, - worker::{to_raw_value, write_file}, + worker::{to_raw_value, write_file, Connection}, }; -#[cfg(feature = "dind")] -use windmill_common::DB; - #[cfg(feature = "dind")] use windmill_common::error::to_anyhow; -use windmill_queue::{append_logs, CanceledBy}; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; lazy_static::lazy_static! { pub static ref BIN_BASH: String = std::env::var("BASH_PATH").unwrap_or_else(|_| "/bin/bash".to_string()); @@ -47,9 +43,10 @@ use crate::{ OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, - POWERSHELL_CACHE_DIR, POWERSHELL_PATH, PROXY_ENVS, TZ_ENV, + DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, POWERSHELL_CACHE_DIR, + POWERSHELL_PATH, PROXY_ENVS, TZ_ENV, }; +use windmill_common::client::AuthedClient; #[cfg(windows)] use crate::SYSTEM_ROOT; @@ -63,9 +60,10 @@ lazy_static::lazy_static! { pub async fn handle_bash_job( mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, content: &str, job_dir: &str, shared_mount: &str, @@ -81,7 +79,7 @@ pub async fn handle_bash_job( if annotation.docker { logs1.push_str("docker mode\n"); } - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, &conn).await; write_file(job_dir, "main.sh", &format!("set -e\n{content}"))?; let script = format!( @@ -136,11 +134,11 @@ exit $exit_status ); write_file(job_dir, "wrapper.sh", &script)?; - let token = client.get_token().await; - let mut reserved_variables = get_reserved_variables(job, &token, db).await?; + let mut reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; reserved_variables.insert("RUST_LOG".to_string(), "info".to_string()); - let args = build_args_map(job, client, db).await?.map(Json); + let args = build_args_map(job, client, conn).await?.map(Json); let job_args = if args.is_some() { args.as_ref() } else { @@ -163,7 +161,7 @@ exit $exit_status let nsjail = !*DISABLE_NSJAIL && job - .script_path + .runnable_path .as_ref() .map(|x| !x.starts_with("init_script_")) .unwrap_or(true); @@ -215,7 +213,7 @@ exit $exit_status }; handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -226,6 +224,7 @@ exit $exit_status job.timeout, true, &mut Some(occupancy_metrics), + None, ) .await?; @@ -234,7 +233,7 @@ exit $exit_status return handle_docker_job( job.id, &job.workspace_id, - db, + conn, job.timeout, mem_peak, canceled_by, @@ -275,11 +274,24 @@ exit $exit_status ))) } +#[cfg(feature = "dind")] +async fn rm_container(client: &bollard::Docker, container_id: &str) { + if let Err(e) = client + .remove_container( + container_id, + Some(RemoveContainerOptions { force: true, ..Default::default() }), + ) + .await + { + tracing::error!("Error removing container: {:?}", e); + } +} + #[cfg(feature = "dind")] async fn handle_docker_job( job_id: Uuid, workspace_id: &str, - db: &DB, + conn: &Connection, job_timeout: Option, mem_peak: &mut i32, canceled_by: &mut Option, @@ -287,6 +299,8 @@ async fn handle_docker_job( occupancy_metrics: &mut OccupancyMetrics, killpill_rx: &mut tokio::sync::broadcast::Receiver<()>, ) -> Result, Error> { + use crate::job_logger::append_logs_with_compaction; + let client = bollard::Docker::connect_with_unix_defaults().map_err(to_anyhow)?; let container_id = job_id.to_string(); @@ -299,24 +313,37 @@ async fn handle_docker_job( } let wait_f = async { - let wait = client + let waited = client .wait_container::(&container_id, None) .try_collect::>() - .await - .map_err(|e| { + .await; + match waited { + Ok(wait) => Ok(wait.first().map(|x| x.status_code)), + Err(bollard::errors::Error::DockerResponseServerError { status_code, message }) => { + append_logs(&job_id, &workspace_id, &format!(": {message}"), conn).await; + Ok(Some(status_code as i64)) + } + Err(bollard::errors::Error::DockerContainerWaitError { error, code }) => { + append_logs(&job_id, &workspace_id, &format!("{error}"), conn).await; + Ok(Some(code as i64)) + } + Err(e) => { tracing::error!("Error waiting for container: {:?}", e); - anyhow::anyhow!("Error waiting for container") - })?; - let waited = wait.first().map(|x| x.status_code); - Ok(waited) + Err(Error::ExecutionErr(format!( + "Error waiting for container: {:?}", + e + ))) + } + } }; let ncontainer_id = container_id.to_string(); let w_id = workspace_id.to_string(); let j_id = job_id.clone(); - let db2 = db.clone(); + let conn2 = conn.clone(); + let worker_name2 = worker_name.to_string(); let (tx, mut rx) = tokio::sync::broadcast::channel::<()>(1); - + let workspace_id2 = workspace_id.to_string(); let mut killpill_rx = killpill_rx.resubscribe(); let logs = tokio::spawn(async move { let client = bollard::Docker::connect_with_unix_defaults().map_err(to_anyhow); @@ -331,18 +358,39 @@ async fn handle_docker_job( ..Default::default() }), ); + append_logs( + &job_id, + &workspace_id2, + "\ndocker logs stream started\n", + &conn2, + ) + .await; loop { tokio::select! { log = log_stream.next() => { match log { Some(Ok(log)) => { - append_logs(&j_id, w_id.clone(), log.to_string(), db2.clone()).await; + match &conn2 { + Connection::Sql(db) => { + append_logs_with_compaction( + &j_id, + &w_id, + &log.to_string(), + &db, + &worker_name2, + ) + .await; + } + c @ Connection::Http(_) => { + append_logs(&j_id, &w_id, &log.to_string(), &c).await; + } + } } Some(Err(e)) => { tracing::error!("Error getting logs: {:?}", e); } _ => { - tracing::error!("End of stream"); + tracing::info!("End of docker logs stream"); return } }; @@ -370,7 +418,7 @@ async fn handle_docker_job( let result = run_future_with_polling_update_job_poller( job_id, job_timeout, - db, + conn, mem_peak, canceled_by, wait_f, @@ -410,27 +458,23 @@ async fn handle_docker_job( } } } + rm_container(&client, &container_id).await; return Err(e); } - if let Err(e) = client - .remove_container( - &container_id, - Some(RemoveContainerOptions { force: true, ..Default::default() }), - ) - .await - { - tracing::error!("Error removing container: {:?}", e); - } + rm_container(&client, &container_id).await; let result = result.unwrap(); + if result.is_some_and(|x| x > 0) { + return Err(Error::ExecutionErr(format!( + "Docker job completed with unsuccessful exit status: {}", + result.unwrap() + ))); + } return Ok(to_raw_value(&json!(format!( - "Docker exit status: {}", - result - .map(|x| x.to_string()) - .unwrap_or_else(|| "none".to_string()) + "Docker job completed with success exit status" )))); } @@ -466,13 +510,31 @@ fn raw_to_string(x: &str) -> String { _ => String::new(), } } + +const POWERSHELL_INSTALL_CODE: &str = r#" +$availableModules = Get-Module -ListAvailable +$path = '{path}' + +$moduleNames = @({modules}) + +foreach ($module in $moduleNames) { + if (-not ($availableModules | Where-Object { $_.Name -eq $module })) { + Write-Host "Installing module $module..." + Save-Module -Name $module -Path $path -Force + } else { + Write-Host "Module $module already installed" + } +} +"#; + #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_powershell_job( mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + db: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, content: &str, job_dir: &str, shared_mount: &str, @@ -482,7 +544,7 @@ pub async fn handle_powershell_job( occupancy_metrics: &mut OccupancyMetrics, ) -> Result, Error> { let pwsh_args = { - let args = build_args_map(job, client, db).await?.map(Json); + let args = build_args_map(job, client, &db).await?.map(Json); let job_args = if args.is_some() { args.as_ref() } else { @@ -528,27 +590,34 @@ pub async fn handle_powershell_job( }) .collect::>(); - let mut install_string: String = String::new(); + let mut modules_to_install: Vec = Vec::new(); let mut logs1 = String::new(); for line in content.lines() { for cap in RE_POWERSHELL_IMPORTS.captures_iter(line) { let module = cap.get(1).unwrap().as_str(); if !installed_modules.contains(&module.to_lowercase()) { - logs1.push_str(&format!("\n{} not found in cache", module.to_string())); - // instead of using Install-Module, we use Save-Module so that we can specify the installation path - install_string.push_str(&format!( - "Save-Module -Path {} -Force {};", - POWERSHELL_CACHE_DIR, module - )); + modules_to_install.push(module.to_string()); } else { logs1.push_str(&format!("\n{} found in cache", module.to_string())); } } } - if !install_string.is_empty() { - logs1.push_str("\n\nInstalling modules..."); + if !logs1.is_empty() { append_logs(&job.id, &job.workspace_id, logs1, db).await; + } + + if !modules_to_install.is_empty() { + let install_string = POWERSHELL_INSTALL_CODE + .replace("{path}", POWERSHELL_CACHE_DIR) + .replace( + "{modules}", + &modules_to_install + .iter() + .map(|x| format!("'{x}'")) + .collect::>() + .join(", "), + ); let child = Command::new(POWERSHELL_PATH.as_str()) .args(&["-Command", &install_string]) .stdout(Stdio::piped()) @@ -568,6 +637,7 @@ pub async fn handle_powershell_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; } @@ -654,8 +724,8 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", ), )?; - let token = client.get_token().await; - let mut reserved_variables = get_reserved_variables(job, &token, db).await?; + let mut reserved_variables = + get_reserved_variables(job, &client.token, db, parent_runnable_path).await?; reserved_variables.insert("RUST_LOG".to_string(), "info".to_string()); let _ = write_file(job_dir, "result.json", "")?; @@ -779,6 +849,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; diff --git a/backend/windmill-worker/src/bigquery_executor.rs b/backend/windmill-worker/src/bigquery_executor.rs index e4674ed6ee..a93a067b02 100644 --- a/backend/windmill-worker/src/bigquery_executor.rs +++ b/backend/windmill-worker/src/bigquery_executor.rs @@ -1,25 +1,28 @@ use std::collections::HashMap; use futures::future::BoxFuture; -use futures::{FutureExt, TryFutureExt}; +use futures::{FutureExt, StreamExt}; use reqwest::Client; use serde_json::{json, value::RawValue, Value}; +use windmill_common::client::AuthedClient; use windmill_common::error::to_anyhow; -use windmill_common::jobs::QueuedJob; +use windmill_common::s3_helpers::convert_json_line_stream; +use windmill_common::worker::Connection; use windmill_common::{error::Error, worker::to_raw_value}; use windmill_parser_sql::{ - parse_bigquery_sig, parse_db_resource, parse_sql_blocks, parse_sql_statement_named_params, + parse_bigquery_sig, parse_db_resource, parse_s3_mode, parse_sql_blocks, + parse_sql_statement_named_params, }; use windmill_queue::CanceledBy; use serde::Deserialize; -use crate::common::{build_http_client, OccupancyMetrics}; -use crate::handle_child::run_future_with_polling_update_job_poller; -use crate::{ - common::{build_args_values, resolve_job_timeout}, - AuthedClientBackgroundTask, +use crate::common::{build_args_values, resolve_job_timeout}; +use crate::common::{ + build_http_client, s3_mode_args_to_worker_data, OccupancyMetrics, S3ModeWorkerData, }; +use crate::handle_child::run_future_with_polling_update_job_poller; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; use gcp_auth::{AuthenticationManager, CustomServiceAccount}; @@ -30,6 +33,16 @@ struct BigqueryResponse { totalRows: Option, schema: Option, jobComplete: bool, + pageToken: Option, + jobReference: Option, +} + +#[allow(non_snake_case)] +#[derive(Deserialize, Clone)] +struct BigQueryResponseJobReference { + jobId: String, + projectId: String, + location: Option, } #[derive(Deserialize)] @@ -73,6 +86,7 @@ fn do_bigquery_inner<'a>( column_order: Option<&'a mut Option>>, skip_collect: bool, http_client: &'a Client, + s3: Option, ) -> windmill_common::error::Result>>> { let param_names = parse_sql_statement_named_params(query, '@'); @@ -119,69 +133,80 @@ fn do_bigquery_inner<'a>( e.to_string() )) })?; + let rows = handle_bigquery_response(&result, &s3, column_order).await?; - if !result.jobComplete { - return Err(Error::ExecutionErr( - "BigQuery API did not answer query in time".to_string(), - )); + if let Some(s3) = s3 { + let cloned_s3 = s3.clone(); + let cloned_http_client = http_client.clone(); + let cloned_token = token.to_string(); + let rows_stream = async_stream::stream! { + for row in rows.iter() { + yield Ok::<_, windmill_common::error::Error>(row.clone()); + } + let mut next_page_token = result.pageToken; + let Some(job_reference) = result.jobReference.clone() else { + return; + }; + while let Some(ref next_page_token_value) = next_page_token { + let response2 = cloned_http_client + .get( + format!("https://bigquery.googleapis.com/bigquery/v2/projects/{}/queries/{}", job_reference.projectId, job_reference.jobId), + ) + .bearer_auth(cloned_token.as_str()) + .query(&[ + ("pageToken", next_page_token_value.as_str()), + ("maxResults", "10000"), + ("timeoutMs", timeout_ms.to_string().as_str()), + ("location", job_reference.location.as_ref().unwrap_or(&"US".to_string()).as_str()), + ]) + .send() + .await + .map_err(|e| { + Error::ExecutionErr(format!("Could not send query to BigQuery API: {}", e)) + })?; + + if let Err(e) = response2.error_for_status_ref() { + match response2.json::().await { + Ok(bq_err) => { + yield Err(Error::ExecutionErr(format!( + "Error from BigQuery API: {}", + bq_err.error.message + ))) + .map_err(to_anyhow)?; + return; + }, + Err(_) => { + yield Err(Error::ExecutionErr(format!( + "Error from BigQuery API could not be parsed: {}", + e.to_string() + ))) + .map_err(to_anyhow)?; + return; + }, + } + } + + let result2 = response2.json::().await.map_err(|e| { + Error::ExecutionErr(format!( + "BigQuery API response could not be parsed: {}", + e.to_string() + )) + })?; + let rows = handle_bigquery_response(&result2, &Some(cloned_s3.clone()), None).await?; + for row in rows.into_iter() { + yield Ok::<_, windmill_common::error::Error>(row); + } + next_page_token = result2.pageToken; + } + }; + + let stream = + convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + + return Ok(to_raw_value(&s3.to_return_s3_obj())); } - if result.rows.is_none() || result.rows.as_ref().unwrap().len() == 0 { - return Ok(serde_json::from_str("[]").unwrap()); - } - - if result.schema.is_none() { - return Err(Error::ExecutionErr( - "Incomplete response from BigQuery API".to_string(), - )); - } - - if result - .totalRows - .unwrap_or(json!("")) - .as_str() - .unwrap_or("") - .parse::() - .unwrap_or(0) - > 10000 - { - return Err(Error::ExecutionErr( - "More than 10000 rows were requested, use LIMIT 10000 to limit the number of rows".to_string(), - )); - } - - if let Some(column_order) = column_order { - *column_order = Some( - result - .schema - .as_ref() - .unwrap() - .fields - .iter() - .map(|x| x.name.clone()) - .collect::>(), - ); - } - - let rows = result - .rows - .unwrap() - .iter() - .map(|row| { - let mut row_map = serde_json::Map::new(); - row.f - .iter() - .zip(result.schema.as_ref().unwrap().fields.iter()) - .for_each(|(field, schema)| { - row_map.insert( - schema.name.clone(), - parse_val(&field.v, &schema.r#type, &schema), - ); - }); - Value::from(row_map) - }) - .collect::>(); - Ok(to_raw_value(&rows)) } } @@ -203,26 +228,100 @@ fn do_bigquery_inner<'a>( Ok(result_f.boxed()) } +async fn handle_bigquery_response<'a>( + result: &BigqueryResponse, + s3: &Option, + column_order: Option<&'a mut Option>>, +) -> windmill_common::error::Result> { + if !result.jobComplete { + return Err(Error::ExecutionErr( + "BigQuery API did not answer query in time".to_string(), + )); + } + + if result.rows.is_none() || result.rows.as_ref().unwrap().len() == 0 { + return Ok(serde_json::from_str("[]").unwrap()); + } + + if result.schema.is_none() { + return Err(Error::ExecutionErr( + "Incomplete response from BigQuery API".to_string(), + )); + } + + if s3.is_none() + && result + .totalRows + .as_ref() + .unwrap_or(&json!("")) + .as_str() + .unwrap_or("") + .parse::() + .unwrap_or(0) + > 10000 + { + return Err(Error::ExecutionErr( + "More than 10000 rows were requested, use LIMIT 10000 to limit the number of rows" + .to_string(), + )); + } + + if let Some(column_order) = column_order { + *column_order = Some( + result + .schema + .as_ref() + .unwrap() + .fields + .iter() + .map(|x| x.name.clone()) + .collect::>(), + ); + } + + let rows = result + .rows + .as_ref() + .unwrap() + .iter() + .map(|row| { + let mut row_map = serde_json::Map::new(); + row.f + .iter() + .zip(result.schema.as_ref().unwrap().fields.iter()) + .for_each(|(field, schema)| { + row_map.insert( + schema.name.clone(), + parse_val(&field.v, &schema.r#type, &schema), + ); + }); + Value::from(row_map) + }) + .collect::>(); + Ok(rows) +} + +use windmill_queue::MiniPulledJob; + pub async fn do_bigquery( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, column_order: &mut Option>, occupancy_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let bigquery_args = build_args_values(job, client, db).await?; + let bigquery_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { Some( client - .get_authed() - .await .get_resource_value_interpolated::( &inline_db_res_path, Some(job.id.to_string()), @@ -252,7 +351,7 @@ pub async fn do_bigquery( .map_err(|e| Error::ExecutionErr(e.to_string()))?; let (timeout_duration, _, _) = - resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await; + resolve_job_timeout(&conn, &job.workspace_id, job.id, job.timeout).await; let timeout_ms = timeout_duration.as_millis() as u64; let http_client = build_http_client(timeout_duration)?; @@ -261,15 +360,21 @@ pub async fn do_bigquery( .await .map_err(|e| Error::ExecutionErr(e.to_string()))?; - let queries = parse_sql_blocks(query); - - let mut statement_values: HashMap = HashMap::new(); - let sig = parse_bigquery_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = + &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &bigquery_args)?; + + let queries = parse_sql_blocks(query); + + let mut statement_values: HashMap = HashMap::new(); + for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_n = arg.clone().name; let arg_v = bigquery_args.get(&arg.name).cloned().unwrap_or(json!("")); @@ -325,6 +430,7 @@ pub async fn do_bigquery( None, annotations.return_last_result && i < queries.len() - 1, &http_client, + s3.clone(), ) }) .collect::>>()?; @@ -354,16 +460,17 @@ pub async fn do_bigquery( Some(column_order), false, &http_client, + s3, )? }; let r = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, - result_f.map_err(to_anyhow), + result_f, worker_name, &job.workspace_id, &mut Some(occupancy_metrics), diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 29ffbc3044..cbe389403b 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -9,7 +9,7 @@ use serde_json::value::RawValue; use uuid::Uuid; use windmill_parser_ts::remove_pinned_imports; -use windmill_queue::{append_logs, CanceledBy}; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PrecomputedAgentInfo}; #[cfg(feature = "enterprise")] use crate::common::build_envs_map; @@ -20,10 +20,11 @@ use crate::{ read_file_content, read_result, start_child_process, write_file_binary, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, - BUN_PATH, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, - NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TZ_ENV, + BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_PATH, DISABLE_NSJAIL, + DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, + PATH_ENV, PROXY_ENVS, TZ_ENV, }; +use windmill_common::client::AuthedClient; #[cfg(windows)] use crate::SYSTEM_ROOT; @@ -41,9 +42,8 @@ use windmill_common::variables; use windmill_common::{ error::{self, Result}, get_latest_hash_for_path, - jobs::QueuedJob, scripts::ScriptLang, - worker::{exists_in_cache, save_cache, write_file, DISABLE_BUNDLING}, + worker::{exists_in_cache, save_cache, to_raw_value, write_file, Connection, DISABLE_BUNDLING}, DB, }; @@ -97,7 +97,7 @@ pub async fn gen_bun_lockfile( canceled_by: &mut Option, job_id: &Uuid, w_id: &str, - db: Option<&sqlx::Pool>, + db: Option<&Connection>, token: &str, script_path: &str, job_dir: &str, @@ -112,7 +112,7 @@ pub async fn gen_bun_lockfile( let mut empty_deps = false; - if let Some(raw_deps) = raw_deps { + if let Some(raw_deps) = raw_deps.as_ref() { gen_bunfig(job_dir).await?; write_file(job_dir, "package.json", raw_deps.as_str())?; } else { @@ -167,6 +167,7 @@ pub async fn gen_bun_lockfile( None, false, occupancy_metrics, + None, ) .await?; } else { @@ -201,10 +202,21 @@ pub async fn gen_bun_lockfile( } if export_pkg { - let mut content = "".to_string(); + let mut content; { let mut file = File::open(format!("{job_dir}/package.json")).await?; - file.read_to_string(&mut content).await?; + let mut buf = String::default(); + file.read_to_string(&mut buf).await?; + if raw_deps.is_some() { + let mut json_map: HashMap> = serde_json::from_str(&buf)?; + json_map.insert( + "generatedFromPackageJson".to_string(), + to_raw_value(&"true".to_string()), + ); + content = serde_json::to_string_pretty(&json_map)?; + } else { + content = buf; + } } if !npm_mode { #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -273,7 +285,7 @@ pub async fn install_bun_lockfile( canceled_by: &mut Option, job_id: &Uuid, w_id: &str, - db: Option<&sqlx::Pool>, + db: Option<&Connection>, job_dir: &str, worker_name: &str, common_bun_proc_envs: HashMap, @@ -350,6 +362,7 @@ pub async fn install_bun_lockfile( None, false, occupancy_metrics, + None, ) .await? } else { @@ -487,7 +500,7 @@ pub async fn generate_wrapper_mjs( w_id: &str, job_id: &Uuid, worker_name: &str, - db: &sqlx::Pool, + db: &Connection, timeout: Option, mem_peak: &mut i32, canceled_by: &mut Option, @@ -521,6 +534,7 @@ pub async fn generate_wrapper_mjs( timeout, false, occupancy_metrics, + None, ) .await?; fs::rename( @@ -536,7 +550,7 @@ pub async fn generate_bun_bundle( w_id: &str, job_id: &Uuid, worker_name: &str, - db: Option>, + db: Option<&Connection>, timeout: Option, mem_peak: &mut i32, canceled_by: &mut Option, @@ -571,6 +585,7 @@ pub async fn generate_bun_bundle( timeout, false, occupancy_metrics, + None, ) .await?; } else { @@ -595,14 +610,10 @@ pub async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<()> { if std::fs::metadata(&bun_cache_path).is_ok() { tracing::info!("loading {bun_cache_path} from cache"); - extract_saved_codebase(job_dir, &bun_cache_path, is_tar, &dst, false)?; } else { #[cfg(all(feature = "enterprise", feature = "parquet"))] - let object_store = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS - .read() - .await - .clone(); + let object_store = windmill_common::s3_helpers::get_object_store().await; #[cfg(not(all(feature = "enterprise", feature = "parquet")))] let object_store: Option<()> = None; @@ -677,21 +688,15 @@ pub async fn prebundle_bun_script( script_path: &str, job_id: &Uuid, w_id: &str, - db: Option, + db: Option<&DB>, job_dir: &str, base_internal_url: &str, worker_name: &str, token: &str, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, ) -> Result<()> { - let (local_path, remote_path) = compute_bundle_local_and_remote_path( - inner_content, - lockfile, - script_path, - db.clone(), - w_id, - ) - .await; + let (local_path, remote_path) = + compute_bundle_local_and_remote_path(inner_content, lockfile, script_path, db, w_id).await; if exists_in_cache(&local_path, &remote_path).await { return Ok(()); } @@ -725,7 +730,7 @@ pub async fn prebundle_bun_script( w_id, job_id, worker_name, - db.clone(), + db.map(|x| Connection::from(x.clone())).as_ref(), None, &mut 0, &mut None, @@ -734,7 +739,7 @@ pub async fn prebundle_bun_script( ) .await?; - save_cache(&local_path, &remote_path, &origin).await?; + save_cache(&local_path, &remote_path, &origin, false).await?; Ok(()) } @@ -753,11 +758,11 @@ async fn get_script_import_updated_at(db: &DB, w_id: &str, script_path: &str) -> Ok(last_updated_at.to_string()) } -async fn compute_bundle_local_and_remote_path( +pub async fn compute_bundle_local_and_remote_path( inner_content: &str, requirements_o: Option<&String>, script_path: &str, - db: Option, + db: Option<&DB>, w_id: &str, ) -> (String, String) { let mut input_src = format!( @@ -824,9 +829,10 @@ pub async fn handle_bun_job( codebase: Option<&String>, mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, job_dir: &str, inner_content: &String, base_internal_url: &str, @@ -835,6 +841,7 @@ pub async fn handle_bun_job( shared_mount: &str, new_args: &mut Option>>, occupancy_metrics: &mut OccupancyMetrics, + precomputed_agent_info: Option, ) -> error::Result> { let mut annotation = windmill_common::worker::TypeScriptAnnotations::parse(inner_content); @@ -843,16 +850,32 @@ pub async fn handle_bun_job( && !*DISABLE_BUNDLING && codebase.is_none() { - let (local_path, remote_path) = compute_bundle_local_and_remote_path( - inner_content, - requirements_o, - job.script_path(), - Some(db.clone()), - &job.workspace_id, - ) - .await; + let (local_path, remote_path) = match conn { + Connection::Sql(db) => { + compute_bundle_local_and_remote_path( + inner_content, + requirements_o, + job.runnable_path(), + Some(db), + &job.workspace_id, + ) + .await + } + Connection::Http(_) => { + let (local_path, remote_path) = match precomputed_agent_info { + Some(PrecomputedAgentInfo::Bun { local, remote }) => (local, remote), + _ => { + return Err(error::Error::ExecutionErr( + "bun bundle is missing the precomputed agent info".to_string(), + )) + } + }; + (local_path, remote_path) + } + }; - let (cache, logs) = windmill_common::worker::load_cache(&local_path, &remote_path).await; + let (cache, logs) = + windmill_common::worker::load_cache(&local_path, &remote_path, false).await; (cache, logs, local_path, remote_path) } else { (false, "".to_string(), "".to_string(), "".to_string()) @@ -871,7 +894,7 @@ pub async fn handle_bun_job( annotation.nodejs = true } let main_override = job.script_entrypoint_override.as_deref(); - let apply_preprocessor = !job.is_flow_step && job.preprocessed == Some(false); + let apply_preprocessor = !job.is_flow_step() && job.preprocessed == Some(false); if has_bundle_cache { let target; @@ -916,7 +939,7 @@ pub async fn handle_bun_job( canceled_by, &job.id, &job.workspace_id, - Some(db), + Some(conn), job_dir, worker_name, common_bun_proc_envs.clone(), @@ -928,15 +951,15 @@ pub async fn handle_bun_job( } else { // if !*DISABLE_NSJAIL || !empty_trusted_deps || has_custom_config_registry { let logs1 = "\n\n--- BUN INSTALL ---\n".to_string(); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; let _ = gen_bun_lockfile( mem_peak, canceled_by, &job.id, &job.workspace_id, - Some(db), - &client.get_token().await, - &job.script_path(), + Some(conn), + &client.token, + job.runnable_path(), job_dir, base_internal_url, worker_name, @@ -1104,13 +1127,13 @@ try {{ let reserved_variables_args_out_f = async { let args_and_out_f = async { if !annotation.native { - create_args_and_out_file(&client, job, job_dir, db).await?; + create_args_and_out_file(&client, job, job_dir, conn).await?; } Ok(()) as Result<()> }; let reserved_variables_f = async { - let client = client.get_authed().await; - let vars = get_reserved_variables(job, &client.token, db).await?; + let vars = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; Ok(vars) as Result> }; let (_, reserved_variables) = tokio::try_join!(args_and_out_f, reserved_variables_f)?; @@ -1128,9 +1151,9 @@ try {{ build_loader( job_dir, base_internal_url, - &client.get_token().await, + &client.token, &job.workspace_id, - &job.script_path(), + job.runnable_path(), if annotation.nodejs { LoaderMode::NodeBundle } else if annotation.native { @@ -1146,9 +1169,9 @@ try {{ build_loader( job_dir, base_internal_url, - &client.get_token().await, + &client.token, &job.workspace_id, - &job.script_path(), + job.runnable_path(), if annotation.nodejs { LoaderMode::Node } else { @@ -1173,7 +1196,7 @@ try {{ &job.workspace_id, &job.id, worker_name, - Some(db.clone()), + Some(conn), job.timeout, mem_peak, canceled_by, @@ -1182,7 +1205,14 @@ try {{ ) .await?; if !local_path.is_empty() { - match save_cache(&local_path, &remote_path, &format!("{job_dir}/main.js")).await { + match save_cache( + &local_path, + &remote_path, + &format!("{job_dir}/main.js"), + false, + ) + .await + { Err(e) => { let em = format!("could not save {local_path} to bundle cache: {e:?}"); tracing::error!(em) @@ -1215,7 +1245,7 @@ try {{ &job.workspace_id, &job.id, worker_name, - db, + conn, job.timeout, mem_peak, canceled_by, @@ -1245,7 +1275,7 @@ try {{ .join("\n")); let js_code = read_file_content(&format!("{job_dir}/main.js")).await?; let started_at = Instant::now(); - let args = crate::common::build_args_map(job, client, db) + let args = crate::common::build_args_map(job, client, conn) .await? .map(sqlx::types::Json); let job_args = if args.is_some() { @@ -1254,16 +1284,17 @@ try {{ job.args.as_ref() }; - append_logs(&job.id, &job.workspace_id, format!("{init_logs}\n"), db).await; + append_logs(&job.id, &job.workspace_id, format!("{init_logs}\n"), conn).await; let result = crate::js_eval::eval_fetch_timeout( env_code, inner_content.clone(), js_code, job_args, + job.script_entrypoint_override.clone(), job.id, job.timeout, - db, + conn, mem_peak, canceled_by, worker_name, @@ -1279,7 +1310,7 @@ try {{ return Ok(result); } } - append_logs(&job.id, &job.workspace_id, init_logs, db).await; + append_logs(&job.id, &job.workspace_id, init_logs, conn).await; //do not cache local dependencies let child = if !*DISABLE_NSJAIL { @@ -1411,7 +1442,7 @@ try {{ handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -1422,6 +1453,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -1500,7 +1532,7 @@ pub async fn start_worker( script_path: &str, token: &str, job_completed_tx: JobCompletedSender, - jobs_rx: Receiver>, + jobs_rx: Receiver>, killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> Result<()> { let mut logs = "".to_string(); @@ -1520,7 +1552,7 @@ pub async fn start_worker( annotation.nodejs = true; let context = variables::get_reserved_variables( - db, + &Connection::from(db.clone()), w_id, &token, "dedicated_worker@windmill.dev", @@ -1534,7 +1566,6 @@ pub async fn start_worker( None, None, None, - None, ) .await; let context_envs = build_envs_map(context.to_vec()).await; @@ -1571,7 +1602,7 @@ pub async fn start_worker( &mut canceled_by, &Uuid::nil(), &w_id, - Some(db), + Some(&Connection::from(db.clone())), job_dir, worker_name, common_bun_proc_envs.clone(), @@ -1588,7 +1619,7 @@ pub async fn start_worker( &mut canceled_by, &Uuid::nil(), &w_id, - Some(db), + Some(&Connection::from(db.clone())), token, &script_path, job_dir, @@ -1689,7 +1720,7 @@ for await (const line of Readline.createInterface({{ input: process.stdin }})) { w_id, &Uuid::nil(), worker_name, - db, + &Connection::from(db.clone()), None, &mut mem_peak, &mut canceled_by, diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 0ffdabb857..3b5312c14c 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -11,6 +11,7 @@ use sha2::Digest; use sqlx::types::Json; use sqlx::{Pool, Postgres}; use tokio::process::Command; +use tokio::sync::{RwLock, Semaphore}; use tokio::{fs::File, io::AsyncReadExt}; #[cfg(feature = "parquet")] @@ -19,18 +20,21 @@ use windmill_common::s3_helpers::{ }; use windmill_common::variables::{build_crypt_with_key_suffix, decrypt}; use windmill_common::worker::{ - to_raw_value, write_file, CLOUD_HOSTED, ROOT_CACHE_DIR, WORKER_CONFIG, + to_raw_value, update_ping_for_failed_init_script_query, write_file, Connection, Ping, PingType, + CLOUD_HOSTED, ROOT_CACHE_DIR, WORKER_CONFIG, }; use windmill_common::{ cache::{Cache, RawData}, error::{self, Error}, - jobs::QueuedJob, scripts::ScriptHash, variables::ContextualVariable, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; +use windmill_parser_sql::{s3_mode_extension, S3ModeArgs, S3ModeFormat}; +use windmill_queue::MiniPulledJob; +use std::ops::AsyncFn; use std::path::Path; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -39,18 +43,17 @@ use windmill_common::{variables, DB}; use tokio::{io::AsyncWriteExt, process::Child, time::Instant}; -use crate::{ - AuthedClient, AuthedClientBackgroundTask, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, - MAX_TIMEOUT_DURATION, PATH_ENV, -}; +use crate::agent_workers::UPDATE_PING_URL; +use crate::{DISABLE_NSJAIL, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, MAX_TIMEOUT_DURATION, PATH_ENV}; +use windmill_common::client::AuthedClient; pub async fn build_args_map<'a>( - job: &'a QueuedJob, - client: &AuthedClientBackgroundTask, - db: &Pool, + job: &'a MiniPulledJob, + client: &AuthedClient, + conn: &Connection, ) -> error::Result>>> { if let Some(args) = &job.args { - return transform_json(client, &job.workspace_id, &args.0, &job, db).await; + return transform_json(client, &job.workspace_id, &args.0, &job, conn).await; } return Ok(None); } @@ -73,12 +76,12 @@ pub fn check_executor_binary_exists( } pub async fn build_args_values( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, - db: &Pool, + job: &MiniPulledJob, + client: &AuthedClient, + conn: &Connection, ) -> error::Result> { if let Some(args) = &job.args { - transform_json_as_values(client, &job.workspace_id, &args.0, &job, db).await + transform_json_as_values(client, &job.workspace_id, &args.0, job, conn).await } else { Ok(HashMap::new()) } @@ -86,13 +89,13 @@ pub async fn build_args_values( #[tracing::instrument(level = "trace", skip_all)] pub async fn create_args_and_out_file( - client: &AuthedClientBackgroundTask, - job: &QueuedJob, + client: &AuthedClient, + job: &MiniPulledJob, job_dir: &str, - db: &Pool, + conn: &Connection, ) -> Result<(), Error> { if let Some(args) = job.args.as_ref() { - if let Some(x) = transform_json(client, &job.workspace_id, &args.0, job, db).await? { + if let Some(x) = transform_json(client, &job.workspace_id, &args.0, job, conn).await? { write_file( job_dir, "args.json", @@ -126,11 +129,11 @@ lazy_static::lazy_static! { } pub async fn transform_json<'a>( - client: &AuthedClientBackgroundTask, + client: &AuthedClient, workspace: &str, vs: &'a HashMap>, - job: &QueuedJob, - db: &Pool, + job: &MiniPulledJob, + db: &Connection, ) -> error::Result>>> { let mut has_match = false; for (_, v) in vs { @@ -150,9 +153,7 @@ pub async fn transform_json<'a>( let value = serde_json::from_str(inner_vs).map_err(|e| { error::Error::internal_err(format!("Error while parsing inner arg: {e:#}")) })?; - let transformed = - transform_json_value(&k, &client.get_authed().await, workspace, value, job, db) - .await?; + let transformed = transform_json_value(&k, &client, workspace, value, job, db).await?; let as_raw = serde_json::from_value(transformed).map_err(|e| { error::Error::internal_err(format!("Error while parsing inner arg: {e:#}")) })?; @@ -165,11 +166,11 @@ pub async fn transform_json<'a>( } pub async fn transform_json_as_values<'a>( - client: &AuthedClientBackgroundTask, + client: &AuthedClient, workspace: &str, vs: &'a HashMap>, - job: &QueuedJob, - db: &Pool, + job: &MiniPulledJob, + db: &Connection, ) -> error::Result> { let mut r: HashMap = HashMap::new(); for (k, v) in vs { @@ -178,9 +179,7 @@ pub async fn transform_json_as_values<'a>( let value = serde_json::from_str(inner_vs).map_err(|e| { error::Error::internal_err(format!("Error while parsing inner arg: {e:#}")) })?; - let transformed = - transform_json_value(&k, &client.get_authed().await, workspace, value, job, db) - .await?; + let transformed = transform_json_value(&k, &client, workspace, value, job, db).await?; let as_raw = serde_json::from_value(transformed).map_err(|e| { error::Error::internal_err(format!("Error while parsing inner arg: {e:#}")) })?; @@ -238,8 +237,8 @@ pub async fn transform_json_value( client: &AuthedClient, workspace: &str, v: Value, - job: &QueuedJob, - db: &Pool, + job: &MiniPulledJob, + conn: &Connection, ) -> error::Result { match v { Value::String(y) if y.starts_with("$var:") => { @@ -270,52 +269,39 @@ pub async fn transform_json_value( }) } Value::String(y) if y.starts_with("$encrypted:") => { - let encrypted = y.strip_prefix("$encrypted:").unwrap(); + match conn { + Connection::Sql(db) => { + let encrypted = y.strip_prefix("$encrypted:").unwrap(); - let root_job_id = get_root_job_id(&job.root_job.unwrap_or_else(|| job.id), db).await?; - let mc = build_crypt_with_key_suffix(&db, &job.workspace_id, &root_job_id.to_string()) - .await?; - decrypt(&mc, encrypted.to_string()).and_then(|x| { - serde_json::from_str(&x).map_err(|e| Error::internal_err(e.to_string())) - }) + let root_job_id = + get_root_job_id(&job.flow_innermost_root_job.unwrap_or_else(|| job.id), db) + .await?; + let mc = build_crypt_with_key_suffix( + &db, + &job.workspace_id, + &root_job_id.to_string(), + ) + .await?; + decrypt(&mc, encrypted.to_string()).and_then(|x| { + serde_json::from_str(&x).map_err(|e| Error::internal_err(e.to_string())) + }) + } + Connection::Http(_) => { + Err(Error::NotFound("Http connection not supported".to_string())) + } + } // let path = y.strip_prefix("$res:").unwrap(); } Value::String(y) if y.starts_with("$") => { - let flow_path = if let Some(uuid) = job.parent_job { - sqlx::query_scalar!("SELECT runnable_path FROM v2_job WHERE id = $1", uuid) - .fetch_optional(db) - .await? - .flatten() - } else { - None - }; - - let variables = variables::get_reserved_variables( - db, - &job.workspace_id, - &client.token, - &job.email, - &job.created_by, - &job.id.to_string(), - &job.permissioned_as, - job.script_path.clone(), - job.parent_job.map(|x| x.to_string()), - flow_path, - job.schedule_path.clone(), - job.flow_step_id.clone(), - job.root_job.clone().map(|x| x.to_string()), - None, - Some(job.scheduled_for.clone()), - ) - .await; + let variables = get_reserved_variables(job, &client.token, conn, None).await?; let name = y.strip_prefix("$").unwrap(); let value = variables .iter() - .find(|x| x.name == name) - .map(|x| x.value.clone()) + .find(|x| x.0 == name) + .map(|x| x.1.clone()) .unwrap_or_else(|| y); Ok(json!(value)) } @@ -323,7 +309,7 @@ pub async fn transform_json_value( for (a, b) in m.clone().into_iter() { m.insert( a.clone(), - transform_json_value(&a, client, workspace, b, job, &db).await?, + transform_json_value(&a, client, workspace, b, job, conn).await?, ); } Ok(Value::Object(m)) @@ -413,15 +399,23 @@ pub fn capitalize(s: &str) -> String { #[tracing::instrument(level = "trace", skip_all)] pub async fn get_reserved_variables( - job: &QueuedJob, + job: &MiniPulledJob, token: &str, - db: &sqlx::Pool, + db: &Connection, + parent_runnable_path: Option, ) -> Result, Error> { - let flow_path = if let Some(uuid) = job.parent_job { - sqlx::query_scalar!("SELECT runnable_path FROM v2_job WHERE id = $1", uuid) - .fetch_optional(db) - .await? - .flatten() + let flow_path = if parent_runnable_path.is_some() { + parent_runnable_path + } else if let Some(uuid) = job.parent_job { + match db { + Connection::Sql(db) => { + sqlx::query_scalar!("SELECT runnable_path FROM v2_job WHERE id = $1", uuid) + .fetch_optional(db) + .await? + .flatten() + } + Connection::Http(_) => None, + } } else { None }; @@ -430,17 +424,16 @@ pub async fn get_reserved_variables( db, &job.workspace_id, token, - &job.email, + &job.permissioned_as_email, &job.created_by, &job.id.to_string(), &job.permissioned_as, - job.script_path.clone(), + job.runnable_path.clone(), job.parent_job.map(|x| x.to_string()), flow_path, - job.schedule_path.clone(), + job.schedule_path(), job.flow_step_id.clone(), - job.root_job.clone().map(|x| x.to_string()), - None, + job.flow_innermost_root_job.clone().map(|x| x.to_string()), Some(job.scheduled_for.clone()), ) .await @@ -482,26 +475,58 @@ pub fn sizeof_val(v: &serde_json::Value) -> usize { } pub async fn update_worker_ping_for_failed_init_script( - db: &DB, + conn: &Connection, worker_name: &str, last_job_id: Uuid, ) { - if let Err(e) = sqlx::query!( - "UPDATE worker_ping SET - ping_at = now(), - jobs_executed = 1, - current_job_id = $1, - current_job_workspace_id = 'admins' - WHERE worker = $2", - last_job_id, - worker_name - ) - .execute(db) - .await - { - tracing::error!("Error updating worker ping for failed init script: {e:?}"); + match conn { + Connection::Sql(db) => { + if let Err(e) = + update_ping_for_failed_init_script_query(worker_name, last_job_id, db).await + { + tracing::error!("Error updating worker ping for failed init script: {e:?}"); + } + } + Connection::Http(client) => { + if let Err(e) = client + .post::<_, ()>( + UPDATE_PING_URL, + None, + &Ping { + last_job_executed: Some(last_job_id), + last_job_workspace_id: None, + worker_instance: None, + ip: None, + tags: None, + dw: None, + jobs_executed: None, + occupancy_rate: None, + occupancy_rate_15s: None, + occupancy_rate_5m: None, + occupancy_rate_30m: None, + version: None, + vcpus: None, + memory: None, + memory_usage: None, + wm_memory_usage: None, + ping_type: PingType::InitScript, + }, + ) + .await + { + tracing::error!("Error updating worker ping for failed init script: {e:?}"); + } + } } } + +pub fn error_to_value(err: Error) -> serde_json::Value { + match err { + Error::JsonErr(err) => err, + _ => json!({"message": err.to_string(), "name": "InternalErr"}), + } +} + pub struct OccupancyMetrics { pub running_job_started_at: Option, pub total_duration_of_running_jobs: f32, @@ -509,6 +534,13 @@ pub struct OccupancyMetrics { pub start_time: Instant, } +pub struct OccupancyResult { + pub occupancy_rate: f32, + pub occupancy_rate_15s: Option, + pub occupancy_rate_5m: Option, + pub occupancy_rate_30m: Option, +} + impl OccupancyMetrics { pub fn new(start_time: Instant) -> Self { OccupancyMetrics { @@ -519,7 +551,7 @@ impl OccupancyMetrics { } } - pub fn update_occupancy_metrics(&mut self) -> (f32, Option, Option, Option) { + pub fn update_occupancy_metrics(&mut self) -> OccupancyResult { let metrics = self; let current_occupied_duration = metrics .running_job_started_at @@ -570,12 +602,12 @@ impl OccupancyMetrics { .worker_occupancy_rate_history .push((total_occupation, elapsed)); - ( + OccupancyResult { occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, - ) + } } } @@ -586,7 +618,7 @@ pub async fn start_child_process(mut cmd: Command, executable: &str) -> Result, + _conn: &Connection, _w_id: &str, _job_id: Uuid, custom_timeout_secs: Option, @@ -594,13 +626,11 @@ pub async fn resolve_job_timeout( let mut warn_msg: Option = None; #[cfg(feature = "cloud")] let cloud_premium_workspace = *CLOUD_HOSTED - && sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", _w_id) - .fetch_one(_db) - .await - .map_err(|e| { - tracing::error!(%e, "error getting premium workspace for job {_job_id}: {e:#}"); - }) - .unwrap_or(false); + && windmill_common::workspaces::is_premium_workspace( + _conn.as_sql().expect("cloud cannot use http connection"), + _w_id, + ) + .await; #[cfg(not(feature = "cloud"))] let cloud_premium_workspace = false; @@ -669,15 +699,15 @@ async fn hash_args( pub async fn cached_result_path( db: &DB, client: &AuthedClient, - job: &QueuedJob, + job: &MiniPulledJob, raw_data: Option<&RawData>, ) -> String { let mut hasher = sha2::Sha256::new(); - hasher.update(&[job.job_kind as u8]); - if let Some(ScriptHash(hash)) = job.script_hash { + hasher.update(&[job.kind as u8]); + if let Some(ScriptHash(hash)) = job.runnable_id { hasher.update(&hash.to_le_bytes()) } else { - job.script_path + job.runnable_path .as_ref() .inspect(|x| hasher.update(x.as_bytes())); match raw_data { @@ -698,7 +728,7 @@ async fn get_workspace_s3_resource_path( storage: Option<&String>, ) -> windmill_common::error::Result> { use windmill_common::{ - job_s3_helpers_ee::get_s3_resource_internal, s3_helpers::StorageResourceType, + job_s3_helpers_oss::get_s3_resource_internal, s3_helpers::StorageResourceType, }; let raw_lfs_opt = if let Some(storage) = storage { @@ -750,19 +780,17 @@ async fn get_workspace_s3_resource_path( } }; - let client2 = client.clone(); - let token_fn = |audience: String| async move { - client2 - .get_id_token(&audience) - .await - .map_err(|e| windmill_common::error::Error::from(e)) - }; let s3_resource_value_raw = client .get_resource_value::(path.as_str()) .await?; - get_s3_resource_internal(rt, s3_resource_value_raw, token_fn) - .await - .map(Some) + get_s3_resource_internal( + rt, + s3_resource_value_raw, + windmill_common::job_s3_helpers_oss::TokenGenerator::AsClient(client), + db, + ) + .await + .map(Some) } #[cfg(feature = "parquet")] @@ -872,7 +900,7 @@ pub async fn get_cached_resource_value_if_valid( S3Object { s3: s3_file_key.clone(), storage: resource.storage.clone(), - filename: None, + ..Default::default() }, ) .await; @@ -890,7 +918,7 @@ pub async fn get_cached_resource_value_if_valid( pub async fn save_in_cache( db: &Pool, _client: &AuthedClient, - job: &QueuedJob, + job: &MiniPulledJob, cached_path: String, r: Arc>, ) { @@ -981,3 +1009,618 @@ pub fn build_http_client(timeout_duration: std::time::Duration) -> error::Result .build() .map_err(|e| Error::internal_err(format!("Error building http client: {e:#}"))) } + +#[derive(Clone)] +pub struct RequiredDependency { + /// Expected directory of dependency in cache + /// For example: + /// /tmp/windmill/cache/python_311/rich==0.0.0 + /// IMPORTANT!: path should not end with '/' + pub path: String, + /// Name to use for S3 tars + /// If not specified will use top level directory of path. + pub custom_name: Option, + /// Display name + /// Name that will be used for console output and logging + /// If not specified will either use custom_name or top level directory of path. + pub short_name: Option, +} + +pub enum InstallStrategy { + /// Will invoke callback to install single dependency + Single(Arc Result + Send + Sync>), + /// Will try to pull S3 first and will invoke closure to install the rest + AllAtOnce(Arc) -> Result + Send + Sync>), +} +/// # General +/// +/// Languages that compile usually include dependencies in final executable. +/// When dynamic languages do not and runtime dependencies provided separately. +/// +/// This helper implies that the language is dynamic. +/// Python, Ruby, Java are dynamic and they can use this helper. +/// +/// # Features +/// +/// This helper will install all specified dependencies in parallel and if it is EE, cache to S3 +/// It has atomic success file, allowing to distinguish failed installations from succesfull. +/// +/// Besides that it provides console output and does logging. +/// +/// # Usage +/// +/// Most important arguments in this helper are `deps` and `install_fn` +/// +/// In `deps` you specify all dependencies that are needed to be on worker in order to execute script. +/// You don't know which are actually installed and which are not. +/// +/// `deps` is a vector of RequiredDependency. Check [RequiredDependency] for more context. +/// +/// After `deps` are provided helper will check each dependency and check if it is in cache, if not it will try to pull from S3 +/// and if it does not work either, it will invoke `install_fn` closure. +/// Closure arguments has dependency name as well as it`s expected path in cache. +/// Closure should return Command that will install dependency to asked place. +pub async fn par_install_language_dependencies<'a>( + deps: Vec, + language_name: &'a str, + installer_executable_name: &'a str, + platform_agnostic: bool, + concurrent_downloads: usize, + stdout_on_err: bool, + install_fn: InstallStrategy, + postinstall_cb: impl AsyncFn(Vec) -> Result<(), error::Error>, + job_id: &'a Uuid, + w_id: &'a str, + worker_name: &'a str, + conn: &Connection, +) -> anyhow::Result<()> { + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + let _ = (platform_agnostic, language_name); + + let total_time = std::time::Instant::now(); + + // Total to install + let mut not_installed = vec![]; + let total_to_install; + // let mut not_installed = vec![]; + let counter_arc = Arc::new(tokio::sync::Mutex::new(0)); + // Append logs with line like this: + // [9/21] + requests==2.32.3 << (S3) | in 57ms + #[allow(unused_assignments)] + async fn print_success( + mut s3_pull: bool, + mut s3_push: bool, + job_id: &Uuid, + w_id: &str, + req: &str, + req_tl: usize, + counter_arc: Arc>, + total_to_install: usize, + instant: std::time::Instant, + conn: &Connection, + ) { + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + { + (s3_pull, s3_push) = (false, false); + } + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if windmill_common::s3_helpers::OBJECT_STORE_SETTINGS + .read() + .await + .is_none() + { + (s3_pull, s3_push) = (false, false); + } + + let mut counter = counter_arc.lock().await; + *counter += 1; + + windmill_queue::append_logs( + job_id, + w_id, + format!( + "\n{}+ {}{}{}| in {}ms", + windmill_common::worker::pad_string( + &format!("[{}/{total_to_install}]", counter), + 9 + ), + // Because we want to align to max len [999/999] we take 9 + // 123456789 + windmill_common::worker::pad_string(&req, req_tl + 1), + // Margin to the right ^ + if s3_pull { "<< (S3) " } else { "" }, + if s3_push { " > (S3) " } else { "" }, + instant.elapsed().as_millis(), + ), + conn, + ) + .await; + // Drop lock, so next print success can fire + } + + let mut name_tl = 0; + struct NotInstalledDependency { + path: String, + custom_name: Option, + short_name: Option, + display_name: String, + } + { + let mut to_be_installed_is_used = false; + for RequiredDependency { + path, // + custom_name, + short_name, + } in deps.into_iter() + { + if path.ends_with("/") { + anyhow::bail!("Internal error: path should not end with '/'") + } + let display_name = short_name + .as_ref() + .or(custom_name.as_ref()) + .or(path.split("/").last().map(|e| e.to_owned()).as_ref()) + .unwrap_or_else(|| { + tracing::warn!( + workspace_id = %w_id, + job_id = %job_id, + "failed to parse top level directory name for {path}, fallback to full path.", + ); + + &path + }) + .to_owned(); + { + // Later will help us align text in log console + if display_name.len() > name_tl { + name_tl = display_name.len(); + } + } + // Will look like: /tmp/windmill/cache/lang/dependency.valid.windmill + if tokio::fs::metadata(path.clone() + ".valid.windmill") + .await + .is_err() + { + if !to_be_installed_is_used { + windmill_queue::append_logs( + job_id, + w_id, + format!("\n--- INSTALLATION ---\n\nTo be installed:\n\n"), + conn, + ) + .await; + to_be_installed_is_used = true; + } + windmill_queue::append_logs(job_id, w_id, format!("- {display_name}\n"), conn) + .await; + not_installed.push(NotInstalledDependency { + path, + custom_name, + short_name, + display_name, + }); + } + } + } + total_to_install = not_installed.len(); + if total_to_install == 0 { + return Ok(()); + } + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let is_not_pro = !matches!( + windmill_common::ee_oss::get_license_plan().await, + windmill_common::ee_oss::LicensePlan::Pro + ); + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if is_not_pro && matches!(install_fn, InstallStrategy::AllAtOnce(_)) { + windmill_queue::append_logs( + job_id, + w_id, + format!("\nLooking for packages on S3:\n"), + conn, + ) + .await; + } + + // Parallelism level (N) + let parallel_limit = // Semaphore will panic if value less then 1 + concurrent_downloads.clamp(1, 30); + + tracing::info!( + workspace_id = %w_id, + "Install parallel limit: {}, job: {}", + parallel_limit, + job_id + ); + + let mut handles = vec![]; + let semaphore = Arc::new(Semaphore::new(parallel_limit)); + let not_pulled = Arc::new(RwLock::new(vec![])); + // let mut handles = Vec::with_capacity(total_to_install); + for NotInstalledDependency { + // + path, + custom_name, + short_name, + display_name, + } in not_installed + { + let permit = semaphore.clone().acquire_owned().await; // Acquire a permit + + if let Err(_) = permit { + tracing::error!( + workspace_id = %w_id, + "Cannot acquire permit on semaphore, that can only mean that semaphore has been closed." + ); + break; + } + + let permit = permit.unwrap(); + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let s3_pull_future = if is_not_pro { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + Some(crate::global_cache::pull_from_tar( + os, + path.clone(), + language_name.to_owned(), + custom_name.clone(), + platform_agnostic, + )) + } else { + None + } + } else { + None + }; + let child = { + if let InstallStrategy::Single(ref callback, ..) = install_fn { + let cmd = callback(RequiredDependency { + path: path.clone(), + custom_name: custom_name.clone(), + short_name: short_name.clone(), + })?; + tracing::debug!("{:?}", &cmd); + Some(start_child_process(cmd, &installer_executable_name).await?) + } else { + None + } + }; + + let ( + worker_name_2, + path_2, + display_name_2, + custom_name, + job_id_2, + w_id_2, + conn_2, + counter_arc, + language_name, + installer_executable_name, + not_pulled, + ) = ( + worker_name.to_owned(), + path.clone(), + display_name.clone(), + custom_name.clone(), + job_id.clone(), + w_id.to_owned(), + conn.clone(), + counter_arc.clone(), + language_name.to_owned(), + installer_executable_name.to_owned(), + not_pulled.clone(), + ); + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + let _ = language_name; + + let start = std::time::Instant::now(); + let handle = tokio::spawn(async move { + let _permit = permit; + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if let Some(s3_pull_future) = s3_pull_future { + if let Err(e) = s3_pull_future.await { + tracing::info!( + workspace_id = %w_id_2, + "No tarball was found for {:?} on S3 or different problem occured {job_id_2}:\n{e}", + &custom_name.clone().unwrap_or(path) + ); + } else { + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path_2.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id_2, + job_id = %job_id_2, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + print_success( + true, + false, + &job_id_2, + &w_id_2, + &display_name_2, + name_tl, + counter_arc, + total_to_install, + start, + &conn_2, + ) + .await; + return; + } + } + + let Some(child) = child else { + let mut lock = not_pulled.write().await; + lock.push(RequiredDependency { + path: path_2.clone(), + custom_name: custom_name.clone(), + short_name: short_name.clone(), + }); + return; + }; + if let Err(e) = crate::handle_child::handle_child( + &job_id_2, + &conn_2, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name_2, + &w_id_2, + &installer_executable_name, + None, + false, + &mut None, + None, + ) + .await + { + windmill_queue::append_logs( + &job_id_2, + &w_id_2, + format!("error while installing {}: {e:?}", &display_name_2), + &conn_2, + ) + .await; + } else { + // if let Some(cb) = postinstall_cb { + // if let Err(e) = cb(vec![RequiredDependency { + // path: path_2.clone(), + // custom_name: custom_name.clone(), + // short_name: short_name.clone(), + // }]) + // .await + // { + // tracing::error!( + // workspace_id = %w_id_2, + // job_id = %job_id_2, + // "Postinstall callback failed!\n{e}\n + // This might affect execution", + // ); + // } + // } + print_success( + false, + true, + &job_id_2, + &w_id_2, + &display_name_2, + name_tl, + counter_arc, + total_to_install, + start, + &conn_2, + ) + .await; + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path_2.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id_2, + job_id = %job_id_2, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path_2, + language_name, + custom_name, + platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + }); + handles.push(handle); + } + + for handle in handles { + if let Err(e) = handle.await { + tracing::error!("Error joining handles: {e:?}"); + } + } + if !not_pulled.read().await.is_empty() { + if let InstallStrategy::AllAtOnce(ref callback, ..) = install_fn { + let not_pulled_copy = not_pulled.read().await.clone(); + windmill_queue::append_logs( + job_id, + w_id, + format!("\n\nFetching {} packages...\n", not_pulled_copy.len()), + &conn, + ) + .await; + let cmd = callback(not_pulled_copy.clone())?; + tracing::debug!("{:?}", &cmd); + let child = start_child_process(cmd, &installer_executable_name).await?; + let mut buf = "".to_owned(); + let pipe_stdout = if stdout_on_err { Some(&mut buf) } else { None }; + if let Err(e) = crate::handle_child::handle_child( + // &job_id, + &Uuid::nil(), + &conn, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name, + &w_id, + &installer_executable_name, + None, + false, + &mut None, + pipe_stdout, + ) + .await + { + bail!(format!( + "error while installing dependencies: {e:?}\n{}", + buf + )); + } + { + postinstall_cb(not_pulled_copy.clone()).await?; + } + for RequiredDependency { path, custom_name: _custom_name, .. } in + not_pulled_copy.into_iter() + { + // TODO: Refactor + // Create a file to indicate that installation was successfull + let valid_path = path.clone() + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id, + job_id = %job_id, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + let language_name = language_name.to_owned(); + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path, + language_name, + _custom_name, + platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + } + } + { + let total_time = total_time.elapsed().as_millis(); + windmill_queue::append_logs( + &job_id, + w_id, + format!( + "\nDone. Time spent on installation phase: {}ms\n", + total_time + ), + conn, + ) + .await; + } + Ok(()) +} + +#[derive(Clone)] +pub struct S3ModeWorkerData { + pub client: AuthedClient, + pub object_key: String, + pub format: S3ModeFormat, + pub storage: Option, + pub workspace_id: String, +} + +impl S3ModeWorkerData { + pub async fn upload(&self, stream: S) -> anyhow::Result<()> + where + S: futures::stream::TryStream + Send + 'static, + S::Error: Into>, + bytes::Bytes: From, + { + self.client + .upload_s3_file( + self.workspace_id.as_str(), + self.object_key.clone(), + self.storage.clone(), + stream, + ) + .await + } + + pub fn to_return_s3_obj(&self) -> windmill_common::s3_helpers::S3Object { + windmill_common::s3_helpers::S3Object { + s3: self.object_key.clone(), + storage: self.storage.clone(), + ..Default::default() + } + } +} + +pub fn s3_mode_args_to_worker_data( + s3: S3ModeArgs, + client: AuthedClient, + job: &MiniPulledJob, +) -> S3ModeWorkerData { + S3ModeWorkerData { + client, + storage: s3.storage, + format: s3.format, + object_key: format!( + "{}/{}.{}", + s3.prefix.unwrap_or_else(|| format!( + "wmill_datalake/{}", + job.runnable_path + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("unknown_script") + )), + job.id, + s3_mode_extension(s3.format) + ), + workspace_id: job.workspace_id.clone(), + } +} diff --git a/backend/windmill-worker/src/csharp_executor.rs b/backend/windmill-worker/src/csharp_executor.rs index 5694920e88..a8ed06a2e2 100644 --- a/backend/windmill-worker/src/csharp_executor.rs +++ b/backend/windmill-worker/src/csharp_executor.rs @@ -19,7 +19,6 @@ use windmill_common::{ }; use windmill_common::error::{self, Error}; -use windmill_common::jobs::QueuedJob; #[cfg(feature = "csharp")] use windmill_queue::append_logs; @@ -37,7 +36,7 @@ use crate::{ }; use crate::common::OccupancyMetrics; -use crate::AuthedClientBackgroundTask; +use windmill_common::client::AuthedClient; #[cfg(windows)] use crate::SYSTEM_ROOT; @@ -68,7 +67,7 @@ pub async fn generate_nuget_lockfile( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + conn: &Connection, worker_name: &str, w_id: &str, occupancy_metrics: &mut OccupancyMetrics, @@ -116,7 +115,7 @@ pub async fn generate_nuget_lockfile( let gen_lockfile_process = start_child_process(gen_lockfile_cmd, DOTNET_PATH.as_str()).await?; handle_child( job_id, - db, + conn, mem_peak, canceled_by, gen_lockfile_process, @@ -127,6 +126,7 @@ pub async fn generate_nuget_lockfile( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -150,7 +150,7 @@ pub async fn generate_nuget_lockfile( _mem_peak: &mut i32, _canceled_by: &mut Option, _job_dir: &str, - _db: &sqlx::Pool, + _conn: &Connection, _worker_name: &str, _w_id: &str, _occupancy_metrics: &mut OccupancyMetrics, @@ -311,7 +311,7 @@ async fn build_cs_proj( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + conn: &Connection, worker_name: &str, w_id: &str, base_internal_url: &str, @@ -371,7 +371,7 @@ async fn build_cs_proj( let build_cs_process = start_child_process(build_cs_cmd, DOTNET_PATH.as_str()).await?; handle_child( job_id, - db, + conn, mem_peak, canceled_by, build_cs_process, @@ -382,9 +382,10 @@ async fn build_cs_proj( None, false, &mut Some(occupancy_metrics), + None, ) .await?; - append_logs(job_id, w_id, "\n\n", db).await; + append_logs(job_id, w_id, "\n\n", conn).await; if let Err(e) = std::fs::remove_file(Path::new(job_dir).join("nuget.config")) { if e.kind() != io::ErrorKind::NotFound { Err(anyhow!("Error erasing nuget.config: {}", e))?; @@ -401,6 +402,7 @@ async fn build_cs_proj( &bin_path, &format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}"), &target, + false, ) .await { @@ -426,13 +428,17 @@ fn remove_lines_from_text(contents: &str, indices_to_remove: Vec) -> Stri result.join("\n") } +use windmill_common::worker::Connection; +use windmill_queue::MiniPulledJob; + #[cfg(not(feature = "csharp"))] pub async fn handle_csharp_job( _mem_peak: &mut i32, _canceled_by: &mut Option, - _job: &QueuedJob, - _db: &sqlx::Pool, - _client: &AuthedClientBackgroundTask, + _job: &MiniPulledJob, + _conn: &Connection, + _client: &AuthedClient, + _parent_runnable_path: Option, _inner_content: &str, _job_dir: &str, _requirements_o: Option<&String>, @@ -444,14 +450,14 @@ pub async fn handle_csharp_job( ) -> Result, Error> { Err(anyhow!("C# is not available because the feature is not enabled").into()) } - #[cfg(feature = "csharp")] pub async fn handle_csharp_job( mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, inner_content: &str, job_dir: &str, requirements_o: Option<&String>, @@ -471,7 +477,8 @@ pub async fn handle_csharp_job( let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR); let remote_path = format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await; + let (cache, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { #[cfg(unix)] @@ -488,7 +495,7 @@ pub async fn handle_csharp_job( cache_logs } else { let logs1 = format!("{cache_logs}\n\n--- DOTNET BUILD ---\n"); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; let (reqs, lines_to_remove) = parse_csharp_reqs(inner_content); for req in &reqs { @@ -500,7 +507,7 @@ pub async fn handle_csharp_job( req.0, req.1.as_ref().unwrap_or(&"".to_string()) ), - db, + conn, ) .await; } @@ -518,7 +525,7 @@ pub async fn handle_csharp_job( mem_peak, canceled_by, job_dir, - db, + conn, worker_name, &job.workspace_id, base_internal_url, @@ -528,13 +535,13 @@ pub async fn handle_csharp_job( .await? }; - create_args_and_out_file(client, job, job_dir, db).await?; + create_args_and_out_file(client, job, job_dir, conn).await?; let logs2 = format!("{cache_logs}\n\n--- C# CODE EXECUTION ---\n"); - append_logs(&job.id, &job.workspace_id, format!("{}\n", logs2), db).await; + append_logs(&job.id, &job.workspace_id, format!("{}\n", logs2), conn).await; - let client = &client.get_authed().await; - let reserved_variables = get_reserved_variables(job, &client.token, db).await?; + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; let child = if !*DISABLE_NSJAIL { write_file( @@ -620,7 +627,7 @@ pub async fn handle_csharp_job( handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -631,6 +638,7 @@ pub async fn handle_csharp_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/dedicated_worker.rs b/backend/windmill-worker/src/dedicated_worker.rs index fb3a30176c..2762887213 100644 --- a/backend/windmill-worker/src/dedicated_worker.rs +++ b/backend/windmill-worker/src/dedicated_worker.rs @@ -15,22 +15,21 @@ use tokio::{ use windmill_common::error::Error; use windmill_common::flows::FlowValue; use windmill_common::worker::WORKER_CONFIG; +use windmill_common::KillpillSender; use windmill_common::{ cache, error, flows::{FlowModule, FlowModuleValue}, - jobs::QueuedJob, scripts::{ScriptHash, ScriptLang}, variables, worker::to_raw_value, DB, }; use windmill_queue::append_logs; +use windmill_queue::MiniPulledJob; use anyhow::Context; -use crate::{ - common::start_child_process, JobCompleted, JobCompletedSender, MAX_BUFFERED_DEDICATED_JOBS, -}; +use crate::{common::start_child_process, JobCompletedSender, MAX_BUFFERED_DEDICATED_JOBS}; use futures::{future, Future}; use std::{collections::HashMap, task::Poll}; @@ -69,7 +68,7 @@ pub async fn handle_dedicated_process( mut killpill_rx: tokio::sync::broadcast::Receiver<()>, job_completed_tx: JobCompletedSender, token: &str, - mut jobs_rx: Receiver>, + mut jobs_rx: Receiver>, worker_name: &str, db: &DB, script_path: &str, @@ -77,6 +76,8 @@ pub async fn handle_dedicated_process( ) -> std::result::Result<(), error::Error> { //do not cache local dependencies + use windmill_queue::{JobCompleted, MiniPulledJob}; + use crate::{handle_child::process_status, PROXY_ENVS}; let cmd_name = format!("dedicated {command_path}"); let mut child = { @@ -133,7 +134,8 @@ pub async fn handle_dedicated_process( } }); - let mut jobs: VecDeque> = VecDeque::with_capacity(MAX_BUFFERED_DEDICATED_JOBS); + let mut jobs: VecDeque> = + VecDeque::with_capacity(MAX_BUFFERED_DEDICATED_JOBS); // let mut i = 0; // let mut j = 0; let mut alive = true; @@ -178,21 +180,21 @@ pub async fn handle_dedicated_process( } tracing::debug!("processed job: |{line}|"); if line.starts_with("wm_res[") { - let job: Arc = jobs.pop_front().expect("pop"); + let job: Arc = jobs.pop_front().expect("pop"); tracing::info!("job completed on dedicated worker {script_path}: {}", job.id); match serde_json::from_str::>(&line.replace("wm_res[success]:", "").replace("wm_res[error]:", "")) { Ok(result) => { let result = Arc::new(result); - append_logs(&job.id, &job.workspace_id, logs.clone(), db).await; + append_logs(&job.id, &job.workspace_id, logs.clone(), &db.into()).await; if line.starts_with("wm_res[success]:") { - job_completed_tx.send(JobCompleted { job , result, result_columns: None, mem_peak: 0, canceled_by: None, success: true, cached_res_path: None, token: token.to_string(), duration: None }).await.unwrap() + job_completed_tx.send_job(JobCompleted { job , result, result_columns: None, mem_peak: 0, canceled_by: None, success: true, cached_res_path: None, token: token.to_string(), duration: None, preprocessed_args: None }, true).await.unwrap() } else { - job_completed_tx.send(JobCompleted { job , result, result_columns: None, mem_peak: 0, canceled_by: None, success: false, cached_res_path: None, token: token.to_string(), duration: None }).await.unwrap() + job_completed_tx.send_job(JobCompleted { job , result, result_columns: None, mem_peak: 0, canceled_by: None, success: false, cached_res_path: None, token: token.to_string(), duration: None, preprocessed_args: None }, true).await.unwrap() } }, Err(e) => { tracing::error!("Could not deserialize job result `{line}`: {e:?}"); - job_completed_tx.send(JobCompleted { job , result: Arc::new(to_raw_value(&serde_json::json!({"error": format!("Could not deserialize job result `{line}`: {e:?}")}))), result_columns: None, mem_peak: 0, canceled_by: None, success: false, cached_res_path: None, token: token.to_string(), duration: None }).await.unwrap(); + job_completed_tx.send_job(JobCompleted { job , result: Arc::new(to_raw_value(&serde_json::json!({"error": format!("Could not deserialize job result `{line}`: {e:?}")}))), result_columns: None, mem_peak: 0, canceled_by: None, success: false, cached_res_path: None, token: token.to_string(), duration: None, preprocessed_args: None }, true).await.unwrap(); }, }; logs = init_log.clone(); @@ -241,7 +243,7 @@ pub async fn handle_dedicated_process( type DedicatedWorker = ( String, - Sender>, + Sender>, Option>, ); @@ -252,7 +254,7 @@ async fn spawn_dedicated_workers_for_flow( modules: &Vec, w_id: &str, path: &str, - killpill_tx: tokio::sync::broadcast::Sender<()>, + killpill_tx: KillpillSender, killpill_rx: &tokio::sync::broadcast::Receiver<()>, db: &DB, worker_dir: &str, @@ -261,7 +263,7 @@ async fn spawn_dedicated_workers_for_flow( job_completed_tx: &JobCompletedSender, ) -> Vec { let mut workers = vec![]; - let mut script_path_to_worker: HashMap>> = + let mut script_path_to_worker: HashMap>> = HashMap::new(); for module in modules.iter() { let value = module.get_value(); @@ -393,13 +395,16 @@ async fn spawn_dedicated_workers_for_flow( } } FlowModuleValue::FlowScript { id, language, .. } => { - let spawn = cache::flow::fetch_script(db, *id).await.map(|data| { - SpawnWorker::RawScript { - path: "".to_string(), - content: data.code.clone(), - lock: data.lock.clone(), - lang: *language, - } + let spawn = cache::flow::fetch_script( + &windmill_common::worker::Connection::Sql(db.clone()), + *id, + ) + .await + .map(|data| SpawnWorker::RawScript { + path: "".to_string(), + content: data.code.clone(), + lock: data.lock.clone(), + lang: *language, }); match spawn { Ok(spawn) => { @@ -438,7 +443,7 @@ async fn spawn_dedicated_workers_for_flow( } pub async fn create_dedicated_worker_map( - killpill_tx: &tokio::sync::broadcast::Sender<()>, + killpill_tx: &KillpillSender, killpill_rx: &tokio::sync::broadcast::Receiver<()>, db: &DB, worker_dir: &str, @@ -446,7 +451,7 @@ pub async fn create_dedicated_worker_map( worker_name: &str, job_completed_tx: &JobCompletedSender, ) -> ( - HashMap>>, + HashMap>>, bool, Vec>, ) { @@ -551,7 +556,7 @@ pub enum SpawnWorker { async fn spawn_dedicated_worker( sw: SpawnWorker, w_id: &str, - killpill_tx: tokio::sync::broadcast::Sender<()>, + killpill_tx: KillpillSender, killpill_rx: &tokio::sync::broadcast::Receiver<()>, db: &DB, worker_dir: &str, @@ -565,8 +570,9 @@ async fn spawn_dedicated_worker( scripts::{ScriptHash, ScriptLang}, utils::rd_string, }; + use windmill_queue::MiniPulledJob; - use crate::{build_envs, get_script_content_by_hash, ContentReqLangEnvs, JOB_TOKEN}; + use crate::{build_envs, get_script_content_by_hash, ContentReqLangEnvs}; #[cfg(not(feature = "enterprise"))] { @@ -577,10 +583,11 @@ async fn spawn_dedicated_worker( #[cfg(feature = "enterprise")] { - let (dedicated_worker_tx, dedicated_worker_rx) = - tokio::sync::mpsc::channel::>(MAX_BUFFERED_DEDICATED_JOBS); + let (dedicated_worker_tx, dedicated_worker_rx) = tokio::sync::mpsc::channel::< + std::sync::Arc, + >(MAX_BUFFERED_DEDICATED_JOBS); let killpill_rx = killpill_rx.resubscribe(); - let db = db.clone(); + let db2 = db.clone(); let base_internal_url = base_internal_url.to_string(); let worker_name = worker_name.to_string(); let job_completed_tx = job_completed_tx.clone(); @@ -607,11 +614,11 @@ async fn spawn_dedicated_worker( let (content, lock, language, envs, codebase) = match sw.clone() { SpawnWorker::Script { path, hash } => { let q = if let Some(hash) = hash { - get_script_content_by_hash(&hash, &w_id, &db).await.map( - |r: ContentReqLangEnvs| { + get_script_content_by_hash(&hash, &w_id, &db2.into()) + .await + .map(|r: ContentReqLangEnvs| { Some((r.content, r.lockfile, r.language, r.envs, r.codebase)) - }, - ) + }) } else { sqlx::query_as::<_, (String, Option, Option, Option>, bool, Option)>( "SELECT content, lock, language, envs, codebase IS NOT NULL, hash FROM script WHERE path = $1 AND workspace_id = $2 AND @@ -620,7 +627,7 @@ async fn spawn_dedicated_worker( ) .bind(&path) .bind(&w_id) - .fetch_optional(&db) + .fetch_optional(&db2) .await .map_err(|e| Error::internal_err(format!("expected content and lock: {e:#}"))) .map(|x| x.map(|y| (y.0, y.1, y.2, y.3, if y.4 { y.5.map(|z| z.to_string()) } else { None }))) @@ -638,7 +645,7 @@ async fn spawn_dedicated_worker( } } else { tracing::error!("Failed to fetch script for dedicated worker"); - killpill_tx.send(()).expect("send"); + killpill_tx.send(); return None; } } @@ -652,10 +659,9 @@ async fn spawn_dedicated_worker( _ => return None, } + let db = db.clone(); let handle = tokio::spawn(async move { - let token = if let Some(token) = JOB_TOKEN.as_ref() { - token.clone() - } else { + let token = { let token = rd_string(32); if let Err(e) = sqlx::query_scalar!( "INSERT INTO token @@ -670,7 +676,7 @@ async fn spawn_dedicated_worker( .await { tracing::error!("failed to create token for dedicated worker: {:?}", e); - killpill_tx.clone().send(()).expect("send"); + killpill_tx.clone().send(); }; token }; @@ -682,7 +688,7 @@ async fn spawn_dedicated_worker( #[cfg(not(feature = "python"))] { tracing::error!("Python requires the python feature to be enabled"); - killpill_tx.send(()).expect("send"); + killpill_tx.send(); return; } @@ -744,9 +750,7 @@ async fn spawn_dedicated_worker( } { tracing::error!("error in dedicated worker for {sw:#?}: {:?}", e); }; - if let Err(e) = killpill_tx.clone().send(()) { - tracing::error!("failed to send final killpill to dedicated worker: {:?}", e); - } + killpill_tx.clone().send(); }); return Some((node_id.unwrap_or(path2), dedicated_worker_tx, Some(handle))); // (Some(dedi_path), Some(dedicated_worker_tx), Some(handle)) diff --git a/backend/windmill-worker/src/deno_executor.rs b/backend/windmill-worker/src/deno_executor.rs index 40350fdeee..e9db387b03 100644 --- a/backend/windmill-worker/src/deno_executor.rs +++ b/backend/windmill-worker/src/deno_executor.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, process::Stdio}; use itertools::Itertools; use serde_json::value::RawValue; use uuid::Uuid; -use windmill_queue::{append_logs, CanceledBy}; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; use crate::{ common::{ @@ -11,14 +11,15 @@ use crate::{ start_child_process, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, DENO_CACHE_DIR, DENO_PATH, DISABLE_NSJAIL, HOME_ENV, - NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV, + DENO_CACHE_DIR, DENO_PATH, DISABLE_NSJAIL, HOME_ENV, NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV, }; +use windmill_common::client::AuthedClient; + use tokio::{fs::File, io::AsyncReadExt, process::Command}; use windmill_common::{error::Result, worker::write_file, BASE_URL}; use windmill_common::{ error::{self}, - jobs::QueuedJob, + worker::Connection, }; use windmill_parser::Typ; @@ -100,7 +101,7 @@ pub async fn generate_deno_lock( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: Option<&sqlx::Pool>, + db: Option<&Connection>, w_id: &str, worker_name: &str, base_internal_url: &str, @@ -159,6 +160,7 @@ pub async fn generate_deno_lock( None, false, occupancy_metrics, + None, ) .await?; } else { @@ -180,9 +182,10 @@ pub async fn handle_deno_job( requirements_o: Option<&String>, mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, job_dir: &str, inner_content: &String, base_internal_url: &str, @@ -193,10 +196,10 @@ pub async fn handle_deno_job( ) -> error::Result> { // let mut start = Instant::now(); let logs1 = "\n\n--- DENO CODE EXECUTION ---\n".to_string(); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; let main_override = job.script_entrypoint_override.as_deref(); - let apply_preprocessor = !job.is_flow_step && job.preprocessed == Some(false); + let apply_preprocessor = !job.is_flow_step() && job.preprocessed == Some(false); write_file(job_dir, "main.ts", inner_content)?; @@ -310,32 +313,33 @@ try {{ let write_import_map_f = build_import_map( &job.workspace_id, - job.script_path(), + job.runnable_path(), base_internal_url, job_dir, ); let reserved_variables_args_out_f = async { let args_and_out_f = async { - create_args_and_out_file(&client, job, job_dir, db).await?; + create_args_and_out_file(&client, job, job_dir, conn).await?; Ok(()) as Result<()> }; let reserved_variables_f = async { - let client = client.get_authed().await; - let vars = get_reserved_variables(job, &client.token, db).await?; - Ok((vars, client.token)) as Result<(HashMap, String)> + let vars = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; + Ok(vars) as Result> }; let (_, reserved_variables) = tokio::try_join!(args_and_out_f, reserved_variables_f)?; - Ok(reserved_variables) as error::Result<(HashMap, String)> + Ok(reserved_variables) as error::Result> }; - let ((reserved_variables, token), _, _) = tokio::try_join!( + let (reserved_variables, _, _) = tokio::try_join!( reserved_variables_args_out_f, write_wrapper_f, write_import_map_f )?; - let mut common_deno_proc_envs = get_common_deno_proc_envs(&token, base_internal_url).await; + let mut common_deno_proc_envs = + get_common_deno_proc_envs(&client.token, base_internal_url).await; if !*DISABLE_NSJAIL { common_deno_proc_envs.insert("HOME".to_string(), job_dir.to_string()); } @@ -405,7 +409,7 @@ try {{ // start = Instant::now(); handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -416,6 +420,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; // logs.push_str(format!("execute: {:?}\n", start.elapsed().as_millis()).as_str()); @@ -502,7 +507,7 @@ pub async fn start_worker( script_path: &str, token: &str, job_completed_tx: JobCompletedSender, - jobs_rx: Receiver>, + jobs_rx: Receiver>, killpill_rx: tokio::sync::broadcast::Receiver<()>, db: &sqlx::Pool, ) -> Result<()> { @@ -514,7 +519,7 @@ pub async fn start_worker( let common_deno_proc_envs = get_common_deno_proc_envs(&token, base_internal_url).await; let context = variables::get_reserved_variables( - db, + &db.into(), w_id, &token, "dedicated_worker@windmill.dev", @@ -528,7 +533,6 @@ pub async fn start_worker( None, None, None, - None, ) .await; let context_envs = build_envs_map(context.to_vec()).await; diff --git a/backend/windmill-worker/src/duckdb_executor.rs b/backend/windmill-worker/src/duckdb_executor.rs new file mode 100644 index 0000000000..8ee78292d3 --- /dev/null +++ b/backend/windmill-worker/src/duckdb_executor.rs @@ -0,0 +1,664 @@ +use std::collections::HashMap; +use std::env; + +use duckdb::types::TimeUnit; +use duckdb::{params_from_iter, Row}; +use rust_decimal::prelude::FromPrimitive; +use rust_decimal::Decimal; +use serde_json::value::RawValue; +use serde_json::{json, Value}; +use tokio::fs::remove_file; +use tokio::task; +use uuid::Uuid; +use windmill_common::error::{to_anyhow, Error, Result}; +use windmill_common::s3_helpers::{ + DuckdbConnectionSettingsQueryV2, DuckdbConnectionSettingsResponse, S3Object, +}; +use windmill_common::worker::{to_raw_value, Connection}; +use windmill_parser_sql::{parse_duckdb_sig, parse_sql_blocks}; +use windmill_queue::{CanceledBy, MiniPulledJob}; + +use crate::common::{build_args_values, OccupancyMetrics}; +use crate::handle_child::run_future_with_polling_update_job_poller; +#[cfg(feature = "mysql")] +use crate::mysql_executor::MysqlDatabase; +use crate::pg_executor::PgDatabase; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; +use windmill_common::client::AuthedClient; + +fn do_duckdb_inner( + conn: &duckdb::Connection, + query: &str, + job_args: &HashMap, + skip_collect: bool, + column_order: &mut Option>, +) -> Result> { + let mut rows_vec = vec![]; + + let (query, job_args) = interpolate_named_args(query, &job_args); + + let mut stmt = conn + .prepare(&query) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + + let mut rows = stmt + .query(params_from_iter(job_args)) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + + if skip_collect { + return Ok(to_raw_value(&json!([]))); + } + + // Statement needs to be stepped at least once or stmt.column_names() will panic + let mut column_names = None; + loop { + let row = rows.next(); + match row { + Ok(Some(row)) => { + // Set column names if not already set + let stmt = row.as_ref(); + let column_names = match column_names.as_ref() { + Some(column_names) => column_names, + None => { + column_names = Some(stmt.column_names()); + column_names.as_ref().unwrap() + } + }; + + let row = row_to_value(row, &column_names.as_slice()) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + rows_vec.push(row); + } + Ok(None) => break, + Err(e) => { + return Err(Error::ExecutionErr(e.to_string())); + } + } + } + + if let (Some(column_order), Some(column_names)) = (column_order.as_mut(), column_names) { + *column_order = column_names.clone(); + } + + return Ok(to_raw_value(&rows_vec)); +} + +pub async fn do_duckdb( + job: &MiniPulledJob, + client: &AuthedClient, + query: &str, + conn: &Connection, + mem_peak: &mut i32, + canceled_by: &mut Option, + worker_name: &str, + column_order_ref: &mut Option>, + occupancy_metrics: &mut OccupancyMetrics, +) -> Result> { + let result_f = async { + let sig = parse_duckdb_sig(query)?.args; + let mut job_args = build_args_values(job, client, conn).await?; + + let (query, _) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + // Prevent interpolate_named_args from detecting argument identifiers in the signature for + // the first query block + let query = trunc_sig(query); + + let (_query_with_transformed_s3_uris, mut used_storages) = + transform_s3_uris(query, client).await?; + let query = _query_with_transformed_s3_uris.as_deref().unwrap_or(query); + + let job_args = { + let mut m: HashMap = HashMap::new(); + for sig_arg in sig.into_iter() { + let json_value = job_args + .remove(&sig_arg.name) + .or_else(|| sig_arg.default) + .unwrap_or_else(|| json!(null)); + + if matches!(&sig_arg.otyp.as_ref().map(String::as_str), Some("s3object")) { + let s3_obj = serde_json::from_value::(json_value).map_err(|e| { + Error::ExecutionErr(format!("Failed to deserialize S3Object: {}", e)) + })?; + let duckdb_conn_settings: windmill_common::s3_helpers::DuckdbConnectionSettingsResponse = client + .get_duckdb_connection_settings(&DuckdbConnectionSettingsQueryV2 { + s3_resource_path: None, + storage: s3_obj.storage.clone(), + }) + .await?; + + let uri = match ( + &duckdb_conn_settings.s3_bucket, + &duckdb_conn_settings.azure_container_path, + ) { + (Some(s3_bucket), None) => format!("s3://{}/{}", s3_bucket, &s3_obj.s3), + (None, Some(az_container)) => format!("{}/{}", az_container, &s3_obj.s3), + _ => { + return Err(Error::ExecutionErr( + "S3Object must have either s3_bucket or azure_container_path" + .to_string(), + )); + } + }; + m.insert(sig_arg.name, duckdb::types::Value::Text(uri)); + used_storages.insert(s3_obj.storage, duckdb_conn_settings); + } else { + let duckdb_value = json_value_to_duckdb_value( + &json_value, + sig_arg + .otyp + .clone() + .unwrap_or_else(|| "text".to_string()) + .as_str(), + client, + )?; + m.insert(sig_arg.name, duckdb_value); + } + } + m + }; + + let query_block_list = parse_sql_blocks(query); + + // Replace windmill resource ATTACH statements with the real instructions + let query_block_list = { + let mut v = vec![]; + for query_block in query_block_list.iter() { + match parse_attach_db_resource(query_block) { + Some(parsed) => v.extend( + transform_attach_db_resource_query(&parsed, &job.id, client).await?, + ), + None => v.push(query_block.to_string()), + }; + } + v + }; + + // duckdb::Connection is not Send so we do it in a single blocking task + let (result, column_order) = task::spawn_blocking(move || { + let conn = duckdb::Connection::open_in_memory() + .map_err(|e| Error::ConnectingToDatabase(e.to_string()))?; + + for (_, DuckdbConnectionSettingsResponse { connection_settings_str, .. }) in + used_storages.into_iter() + { + conn.execute_batch(&connection_settings_str) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + } + + let mut result: Option> = None; + let mut column_order = None; + for (query_block_index, query_block) in query_block_list.iter().enumerate() { + result = Some( + do_duckdb_inner( + &conn, + query_block.as_str(), + &job_args, + query_block_index != query_block_list.len() - 1, + &mut column_order, + ) + .map_err(|e| Error::ExecutionErr(e.to_string()))?, + ); + } + let result = result.unwrap_or_else(|| to_raw_value(&json!([]))); + Ok::<_, Error>((result, column_order)) + }) + .await + .map_err(to_anyhow)??; + + *column_order_ref = column_order; + + // BigQuery cleanup + let bq_credentials_path = make_bq_credentials_path(&job.id); + env::remove_var("GOOGLE_APPLICATION_CREDENTIALS"); + if matches!(tokio::fs::try_exists(&bq_credentials_path).await, Ok(true)) { + remove_file(&bq_credentials_path).await.map_err(to_anyhow)?; + } + Ok(result) + }; + + let result = run_future_with_polling_update_job_poller( + job.id, + job.timeout, + conn, + mem_peak, + canceled_by, + result_f, + worker_name, + &job.workspace_id, + &mut Some(occupancy_metrics), + Box::pin(futures::stream::once(async { 0 })), + ) + .await?; + + Ok(result) +} + +fn row_to_value(row: &Row<'_>, column_names: &[String]) -> Result> { + let mut obj = serde_json::Map::new(); + for (i, key) in column_names.iter().enumerate() { + let value: duckdb::types::Value = + row.get(i).map_err(|e| Error::ExecutionErr(e.to_string()))?; + let json_value = match value { + duckdb::types::Value::Null => serde_json::Value::Null, + duckdb::types::Value::Boolean(b) => serde_json::Value::Bool(b), + duckdb::types::Value::TinyInt(i) => serde_json::Value::Number(i.into()), + duckdb::types::Value::SmallInt(i) => serde_json::Value::Number(i.into()), + duckdb::types::Value::Int(i) => serde_json::Value::Number(i.into()), + duckdb::types::Value::BigInt(i) => serde_json::Value::Number(i.into()), + duckdb::types::Value::HugeInt(i) => serde_json::Value::String(i.to_string()), + duckdb::types::Value::UTinyInt(u) => serde_json::Value::Number(u.into()), + duckdb::types::Value::USmallInt(u) => serde_json::Value::Number(u.into()), + duckdb::types::Value::UInt(u) => serde_json::Value::Number(u.into()), + duckdb::types::Value::UBigInt(u) => serde_json::Value::Number(u.into()), + duckdb::types::Value::Float(f) => serde_json::Value::Number( + serde_json::Number::from_f64(f as f64) + .ok_or_else(|| Error::ExecutionErr("Could not convert to f64".to_string()))?, + ), + duckdb::types::Value::Double(f) => serde_json::Value::Number( + serde_json::Number::from_f64(f) + .ok_or_else(|| Error::ExecutionErr("Could not convert to f64".to_string()))?, + ), + duckdb::types::Value::Decimal(d) => serde_json::Value::String(d.to_string()), + duckdb::types::Value::Timestamp(_, ts) => serde_json::Value::String(ts.to_string()), + duckdb::types::Value::Text(s) => serde_json::Value::String(s), + duckdb::types::Value::Blob(b) => serde_json::Value::Array( + b.into_iter() + .map(|byte| serde_json::Value::Number(byte.into())) + .collect(), + ), + duckdb::types::Value::Date32(d) => serde_json::Value::Number(d.into()), + duckdb::types::Value::Time64(_, t) => serde_json::Value::String(t.to_string()), + duckdb::types::Value::Interval { months, days, nanos } => serde_json::json!({ + "months": months, + "days": days, + "nanos": nanos + }), + duckdb::types::Value::List(values) => serde_json::Value::Array( + values + .into_iter() + .map(|v| serde_json::Value::String(format!("{:?}", v))) + .collect(), + ), + duckdb::types::Value::Enum(e) => serde_json::Value::String(e), + duckdb::types::Value::Struct(fields) => serde_json::Value::Object( + fields + .iter() + .map(|(k, v)| (k.clone(), serde_json::Value::String(format!("{:?}", v)))) + .collect(), + ), + duckdb::types::Value::Array(values) => serde_json::Value::Array( + values + .into_iter() + .map(|v| serde_json::Value::String(format!("{:?}", v))) + .collect(), + ), + duckdb::types::Value::Map(map) => serde_json::Value::Object( + map.iter() + .map(|(k, v)| { + ( + format!("{:?}", k), + serde_json::Value::String(format!("{:?}", v)), + ) + }) + .collect(), + ), + duckdb::types::Value::Union(value) => { + serde_json::Value::String(format!("{:?}", *value)) + } + }; + obj.insert(key.clone(), json_value); + } + serde_json::value::to_raw_value(&obj).map_err(|e| e.into()) +} + +fn json_value_to_duckdb_value( + json_value: &serde_json::Value, + arg_type: &str, + client: &AuthedClient, +) -> Result { + let arg_type = arg_type.to_lowercase(); + let duckdb_value = match json_value { + serde_json::Value::Null => duckdb::types::Value::Null, + serde_json::Value::Bool(b) => duckdb::types::Value::Boolean(*b), + + serde_json::Value::String(s) + if matches!( + arg_type.as_str(), + "timestamp" | "timestamptz" | "timestamp with time zone" | "datetime" + ) => + { + string_to_duckdb_timestamp(&s)? + } + serde_json::Value::String(s) if arg_type.as_str() == "date" => string_to_duckdb_date(&s)?, + serde_json::Value::String(s) if arg_type.as_str() == "time" => string_to_duckdb_time(&s)?, + serde_json::Value::String(s) => duckdb::types::Value::Text(s.clone()), + + serde_json::Value::Number(n) if n.is_i64() => { + let v = n.as_i64().unwrap(); + match arg_type.as_str() { + "tinyint" | "int1" => duckdb::types::Value::TinyInt(v as i8), + "smallint" | "int2" | "short" => duckdb::types::Value::SmallInt(v as i16), + "integer" | "int4" | "int" | "signed" => duckdb::types::Value::Int(v as i32), + "bigint" | "int8" | "long" => duckdb::types::Value::BigInt(v), + "hugeint" => duckdb::types::Value::HugeInt(v as i128), + "float" | "float4" | "real" => duckdb::types::Value::Float(v as f32), + "double" | "float8" => duckdb::types::Value::Double(v as f64), + _ => duckdb::types::Value::BigInt(v), // default fallback + } + } + + serde_json::Value::Number(n) if n.is_u64() => { + let v = n.as_u64().unwrap(); + match arg_type.as_str() { + "utinyint" => duckdb::types::Value::UTinyInt(v as u8), + "usmallint" => duckdb::types::Value::USmallInt(v as u16), + "uinteger" => duckdb::types::Value::UInt(v as u32), + "ubigint" | "uhugeint" => duckdb::types::Value::UBigInt(v), + _ => duckdb::types::Value::UBigInt(v), // default fallback + } + } + + serde_json::Value::Number(n) if n.is_f64() => { + let v = n.as_f64().unwrap(); + match arg_type.as_str() { + "float" | "float4" | "real" => duckdb::types::Value::Float(v as f32), + "double" | "float8" => duckdb::types::Value::Double(v), + "decimal" | "numeric" => { + duckdb::types::Value::Decimal(Decimal::from_f64(v).ok_or_else(|| { + Error::ExecutionErr("Could not convert f64 to Decimal".to_string()) + })?) + } + _ => duckdb::types::Value::Double(v), // default fallback + } + } + + serde_json::Value::Array(arr) => duckdb::types::Value::Array( + arr.iter() + .map(|val| json_value_to_duckdb_value(val, arg_type.as_str(), client)) + .collect::>>()?, + ), + serde_json::Value::Object(map) => duckdb::types::Value::Struct( + map.iter() + .map(|(k, v)| { + Ok::<_, Error>(( + k.clone(), + json_value_to_duckdb_value(v, arg_type.as_str(), client)?, + )) + }) + .collect::>>()? + .into(), + ), + + value @ _ => { + return Err(Error::ExecutionErr(format!( + "Unsupported type in query: {:?} and signature {arg_type:?}", + value + ))) + } + }; + Ok(duckdb_value) +} + +fn string_to_duckdb_timestamp(s: &str) -> Result { + let ts = chrono::DateTime::parse_from_rfc3339(s) + .map_err(|e: chrono::ParseError| Error::ExecutionErr(e.to_string()))?; + Ok(duckdb::types::Value::Timestamp( + TimeUnit::Millisecond, + ts.timestamp_millis(), + )) +} + +fn string_to_duckdb_date(s: &str) -> Result { + use chrono::Datelike; + let date = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").unwrap(); + Ok(duckdb::types::Value::Date32(date.num_days_from_ce())) +} + +fn string_to_duckdb_time(s: &str) -> Result { + use chrono::Timelike; + let time = chrono::NaiveTime::parse_from_str(s, "%H:%M:%S").unwrap(); + Ok(duckdb::types::Value::Time64( + TimeUnit::Microsecond, + time.num_seconds_from_midnight() as i64, + )) +} + +struct ParsedAttachDbResource<'a> { + resource_path: &'a str, + name: &'a str, + db_type: &'a str, + extra_args: Option<&'a str>, +} +fn parse_attach_db_resource<'a>(query: &'a str) -> Option> { + lazy_static::lazy_static! { + static ref RE: regex::Regex = regex::Regex::new(r"ATTACH '\$res:([^']+)' AS (\S+) \(TYPE (\w+)(.*)\)").unwrap(); + } + + for cap in RE.captures_iter(query) { + if let (Some(resource_path), Some(name), Some(db_type)) = + (cap.get(1), cap.get(2), cap.get(3)) + { + let extra_args = cap.get(4).map(|m| query[m.start()..m.end()].trim()); + return Some(ParsedAttachDbResource { + resource_path: query[resource_path.start()..resource_path.end()].trim(), + name: query[name.start()..name.end()].trim(), + db_type: query[db_type.start()..db_type.end()].trim(), + extra_args, + }); + } + } + None +} + +async fn transform_attach_db_resource_query( + parsed: &ParsedAttachDbResource<'_>, + job_id: &Uuid, + client: &AuthedClient, +) -> Result> { + match parsed.db_type.to_lowercase().as_str() { + "postgres" => { + let resource: PgDatabase = client + .get_resource_value_interpolated(parsed.resource_path, Some(job_id.to_string())) + .await?; + + let attach_str = format!( + "ATTACH 'dbname={} {} host={} {} {}' AS {} (TYPE postgres{});", + resource.dbname, + resource + .user + .map(|u| format!("user={}", u)) + .unwrap_or_default(), + resource.host, + resource + .password + .map(|p| format!("password={}", p)) + .unwrap_or_default(), + resource + .port + .map(|p| format!("port={}", p)) + .unwrap_or_default(), + parsed.name, + parsed.extra_args.unwrap_or("") + ); + + Ok(vec![ + "INSTALL postgres;".to_string(), + "LOAD postgres;".to_string(), + attach_str, + ]) + } + "mysql" => { + #[cfg(not(feature = "mysql"))] + return Err(Error::ExecutionErr( + "MySQL feature is not enabled".to_string(), + )); + + #[cfg(feature = "mysql")] + { + let resource: MysqlDatabase = client + .get_resource_value_interpolated(parsed.resource_path, Some(job_id.to_string())) + .await?; + + let attach_str = format!( + "ATTACH 'database={} host={} ssl_mode={} {} {} {}' AS {} (TYPE mysql{});", + resource.database, + resource.host, + resource + .ssl + .map(|ssl| if ssl { "required" } else { "disabled" }) + .unwrap_or("preferred"), + resource + .password + .map(|p| format!("password={}", p)) + .unwrap_or_default(), + resource + .port + .map(|p| format!("port={}", p)) + .unwrap_or_default(), + resource + .user + .map(|u| format!("user={}", u)) + .unwrap_or_default(), + parsed.name, + parsed.extra_args.unwrap_or("") + ); + + Ok(vec![ + "INSTALL mysql;".to_string(), + "LOAD mysql;".to_string(), + attach_str, + ]) + } + } + "bigquery" => { + let resource: Value = client + .get_resource_value_interpolated(parsed.resource_path, Some(job_id.to_string())) + .await?; + // duckdb's bigquery extension requires a json file as credentials + let bq_credentials_path = make_bq_credentials_path(job_id); + env::set_var("GOOGLE_APPLICATION_CREDENTIALS", &bq_credentials_path); + tokio::fs::write(&bq_credentials_path, resource.to_string()) + .await + .map_err(|e| { + Error::ExecutionErr(format!( + "Failed to write BigQuery credentials to {}: {}", + &bq_credentials_path, e + )) + })?; + let project_id: String = serde_json::from_value( + resource + .get("project_id") + .ok_or_else(|| { + Error::ExecutionErr("BigQuery resource must contain project_id".to_string()) + })? + .to_owned(), + ) + .map_err(|_e| Error::ExecutionErr("failed project_id deserialize".to_string()))?; + let attach_str = format!( + "ATTACH 'project={}' as {} (TYPE bigquery{});", + project_id, + parsed.name, + parsed.extra_args.unwrap_or("") + ) + .to_string(); + Ok(vec![ + "INSTALL bigquery FROM community;".to_string(), + "LOAD bigquery;".to_string(), + attach_str, + ]) + } + _ => Err(Error::ExecutionErr(format!( + "Unsupported db type in DuckDB ATTACH: {}", + parsed.db_type + ))), + } +} + +// Returns the transformed query and the set of storages used +async fn transform_s3_uris( + query: &str, + client: &AuthedClient, +) -> Result<( + Option, + HashMap, DuckdbConnectionSettingsResponse>, +)> { + let mut transformed_query = None; + lazy_static::lazy_static! { + static ref RE: regex::Regex = regex::Regex::new(r"'s3://([^'/]*)/([^']+)'").unwrap(); + } + let mut used_storages = HashMap::new(); + for cap in RE.captures_iter(query) { + if let (storage, Some(s3_path)) = (cap.get(1), cap.get(2)) { + let s3_path = s3_path.as_str(); + let storage = match storage.map(|m| m.as_str()) { + Some("") | None => None, + Some(s) => Some(s.to_string()), + }; + let original_str_lit = + format!("'s3://{}/{}'", storage.as_deref().unwrap_or(""), s3_path); + let duckdb_conn_settings = client + .get_duckdb_connection_settings(&DuckdbConnectionSettingsQueryV2 { + s3_resource_path: None, + storage: storage.clone(), + }) + .await?; + let url = match &duckdb_conn_settings { + DuckdbConnectionSettingsResponse { s3_bucket: Some(bucket), .. } => { + format!("'s3://{bucket}/{s3_path}'") + } + DuckdbConnectionSettingsResponse { azure_container_path: Some(base), .. } => { + format!("'{base}/{s3_path}'") + } + _ => { + return Err(Error::ExecutionErr( + "DuckDB connection settings response must have either s3_bucket or azure_container_path".to_string(), + ))?; + } + }; + transformed_query = Some( + transformed_query + .unwrap_or(query.to_string()) + .replace(&original_str_lit, &url), + ); + used_storages.insert(storage, duckdb_conn_settings); + } + } + Ok((transformed_query, used_storages)) +} + +// BigQuery extension requires a json file as credentials +// The file path is set as an env var by do_duckdb +// It is created by transform_attach_db_resource_query (when bigquery is detected) +// and deleted by do_duckdb after the query is executed +fn make_bq_credentials_path(job_id: &Uuid) -> String { + format!("/tmp/service-account-credentials-{}.json", job_id) +} + +// duckdb-rs does not support named parameters, +// and it raises an error when passing unused arguments. We cannot prepare batch statements +// but only single SQL statements so it doesn't work when all arguments are not used by +// every single statement. +fn interpolate_named_args<'a>( + query: &str, + args: &'a HashMap, +) -> (String, Vec<&'a duckdb::types::Value>) { + let mut query = query.to_string(); + + let mut values = vec![]; + for (arg_name, arg_value) in args { + let pat = format!("${}", arg_name); + if !query.contains(&pat) { + continue; + } + values.push(arg_value); + query = query.replace(&pat, &format!("${}", values.len())); + } + (query, values) +} + +fn trunc_sig(query: &str) -> &str { + let idx = query.rfind("-- $").unwrap_or(query.len()); + // find next \n starting from idx and return everything after it + let idx = query[idx..].find('\n').map(|i| i + idx).unwrap_or(0); + &query[idx..] +} diff --git a/backend/windmill-worker/src/global_cache.rs b/backend/windmill-worker/src/global_cache.rs index 23ce255615..6ab2acb441 100644 --- a/backend/windmill-worker/src/global_cache.rs +++ b/backend/windmill-worker/src/global_cache.rs @@ -4,23 +4,25 @@ use tokio::time::Instant; use windmill_common::error; -#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +#[cfg(all(feature = "enterprise", feature = "parquet"))] use object_store::ObjectStore; -#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +#[cfg(all(feature = "enterprise", feature = "parquet"))] use std::sync::Arc; #[cfg(all(feature = "enterprise", feature = "parquet"))] pub const TARGET: &str = const_format::concatcp!(std::env::consts::OS, "_", std::env::consts::ARCH); -#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +#[cfg(all(feature = "enterprise", feature = "parquet"))] pub async fn build_tar_and_push( s3_client: Arc, folder: String, - // python_311 - python_xyz: String, + lang: String, + custom_folder_name: Option, + platform_agnostic: bool, ) -> error::Result<()> { use object_store::path::Path; + use tokio::fs::create_dir_all; use crate::TAR_PYBASE_CACHE_DIR; @@ -28,10 +30,16 @@ pub async fn build_tar_and_push( let start = Instant::now(); // e.g. tiny==1.0.0 - let folder_name = folder.split("/").last().unwrap(); + let folder_name = if let Some(name) = custom_folder_name { + name + } else { + folder.split("/").last().unwrap().to_owned() + }; - let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", python_xyz); - let tar_path = format!("{prefix}/{folder_name}_tar.tar",); + let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", lang); + let tar_path = format!("{prefix}/{folder_name}_tar.tar"); + + create_dir_all(prefix).await?; let tar_file = std::fs::File::create(&tar_path)?; let mut tar = tar::Builder::new(tar_file); @@ -51,7 +59,10 @@ pub async fn build_tar_and_push( // })?; if let Err(e) = s3_client .put( - &Path::from(format!("/tar/{TARGET}/{python_xyz}/{folder_name}.tar")), + &Path::from(format!( + "/tar/{}/{lang}/{folder_name}.tar", + if platform_agnostic { "" } else { TARGET } + )), std::fs::read(&tar_path)?.into(), ) .await @@ -75,22 +86,30 @@ pub async fn build_tar_and_push( Ok(()) } -#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +#[cfg(all(feature = "enterprise", feature = "parquet"))] pub async fn pull_from_tar( client: Arc, folder: String, - // python_311 - python_xyz: String, + lang: String, + custom_folder_name: Option, + platform_agnostic: bool, ) -> error::Result<()> { use windmill_common::s3_helpers::attempt_fetch_bytes; - let folder_name = folder.split("/").last().unwrap(); + let folder_name = if let Some(name) = custom_folder_name { + name + } else { + folder.split("/").last().unwrap().to_owned() + }; - tracing::info!("Attempting to pull piptar {folder_name} from bucket"); + tracing::info!("Attempting to pull tar {folder_name} from bucket"); let start = Instant::now(); - let tar_path = format!("tar/{TARGET}/{python_xyz}/{folder_name}.tar"); + let tar_path = format!( + "tar/{}/{lang}/{folder_name}.tar", + if platform_agnostic { "" } else { TARGET } + ); let bytes = attempt_fetch_bytes(client, &tar_path).await?; extract_tar(bytes, &folder).map_err(|e| { diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 1a8156a1bb..c5fe55184a 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -3,16 +3,19 @@ use std::{collections::HashMap, fs::DirBuilder, process::Stdio}; use itertools::Itertools; use serde_json::value::RawValue; -use tokio::{fs::File, io::AsyncReadExt, process::Command}; +use tokio::{ + fs::{self, File}, + io::AsyncReadExt, + process::Command, +}; use uuid::Uuid; use windmill_common::{ error::{self, Error}, - jobs::QueuedJob, utils::calculate_hash, - worker::{save_cache, write_file}, + worker::{save_cache, write_file, Connection}, }; use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE}; -use windmill_queue::{append_logs, CanceledBy}; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; use crate::{ common::{ @@ -20,9 +23,10 @@ use crate::{ start_child_process, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, GOPRIVATE, GOPROXY, - GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV, NSJAIL_PATH, PATH_ENV, TZ_ENV, + DISABLE_NSJAIL, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV, + NSJAIL_PATH, PATH_ENV, TZ_ENV, }; +use windmill_common::client::AuthedClient; const GO_REQ_SPLITTER: &str = "//go.sum\n"; const NSJAIL_CONFIG_RUN_GO_CONTENT: &str = include_str!("../nsjail/run.go.config.proto"); @@ -36,9 +40,10 @@ pub const GO_OBJECT_STORE_PREFIX: &str = "gobin/"; pub async fn handle_go_job( mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, inner_content: &str, job_dir: &str, requirements_o: Option<&String>, @@ -65,7 +70,8 @@ pub async fn handle_go_job( )); let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR); let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await; + let (cache, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let (skip_go_mod, skip_tidy) = if cache { (true, true) @@ -77,7 +83,7 @@ pub async fn handle_go_job( let cache_logs = if !cache { let logs1 = format!("{cache_logs}\n\n--- GO DEPENDENCIES SETUP ---\n"); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; install_go_dependencies( &job.id, @@ -85,17 +91,18 @@ pub async fn handle_go_job( mem_peak, canceled_by, job_dir, - db, + conn, true, skip_go_mod, skip_tidy, + false, worker_name, &job.workspace_id, occupation_metrics, ) .await?; - create_args_and_out_file(client, job, job_dir, db).await?; + create_args_and_out_file(client, job, job_dir, conn).await?; { let sig = windmill_parser_go::parse_go_sig(&inner_content)?; @@ -201,7 +208,7 @@ func Run(req Req) (interface{{}}, error){{ let build_go_process = start_child_process(build_go_cmd, GO_PATH.as_str()).await?; handle_child( &job.id, - db, + conn, mem_peak, canceled_by, build_go_process, @@ -212,6 +219,7 @@ func Run(req Req) (interface{{}}, error){{ None, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -219,6 +227,7 @@ func Run(req Req) (interface{{}}, error){{ &bin_path, &format!("{GO_OBJECT_STORE_PREFIX}{hash}"), &format!("{job_dir}/main"), + false, ) .await { @@ -242,16 +251,15 @@ func Run(req Req) (interface{{}}, error){{ )) })?; - create_args_and_out_file(client, job, job_dir, db).await?; + create_args_and_out_file(client, job, job_dir, conn).await?; cache_logs }; let logs2 = format!("{cache_logs}\n\n--- GO CODE EXECUTION ---\n"); - append_logs(&job.id, &job.workspace_id, logs2, db).await; + append_logs(&job.id, &job.workspace_id, logs2, conn).await; - let client = &client.get_authed().await; - - let reserved_variables = get_reserved_variables(job, &client.token, db).await?; + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; let child = if !*DISABLE_NSJAIL { let _ = write_file( @@ -304,7 +312,7 @@ func Run(req Req) (interface{{}}, error){{ }; handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -315,6 +323,7 @@ func Run(req Req) (interface{{}}, error){{ job.timeout, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -348,15 +357,25 @@ pub async fn install_go_dependencies( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + conn: &Connection, non_dep_job: bool, skip_go_mod: bool, has_sum: bool, + raw_deps: bool, worker_name: &str, w_id: &str, occupation_metrics: &mut OccupancyMetrics, ) -> error::Result { - if !skip_go_mod { + if raw_deps { + let go_mod = + if let Some(module) = code.lines().find(|l| l.trim_start().starts_with("module ")) { + code.replace(module, "module mymod") + } else { + format!("module mymod\n{code}") + }; + fs::write(format!("{job_dir}/go.mod"), go_mod).await?; + } + if !raw_deps && !skip_go_mod { gen_go_mymod(code, job_dir).await?; let mut child_cmd = Command::new(GO_PATH.as_str()); child_cmd @@ -368,7 +387,7 @@ pub async fn install_go_dependencies( handle_child( job_id, - db, + conn, mem_peak, canceled_by, child_process, @@ -379,6 +398,7 @@ pub async fn install_go_dependencies( None, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -395,7 +415,9 @@ pub async fn install_go_dependencies( let mut new_lockfile = false; - let hash = if !has_sum { + let hash = if raw_deps { + calculate_hash(code) + } else if !has_sum { calculate_hash(parse_go_imports(&code)?.iter().join("\n").as_str()) } else { "".to_string() @@ -405,24 +427,34 @@ pub async fn install_go_dependencies( let mut skip_tidy = has_sum; if !has_sum { - if let Some(cached) = sqlx::query_scalar!( - "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", - hash - ) - .fetch_optional(db) - .await? - { - let logs1 = format!("\nfound cached resolution: {}", hash); - append_logs(&job_id, w_id, logs1, db).await; - gen_go_mod(code, job_dir, &cached).await?; - skip_tidy = true; - new_lockfile = false; - } else { - new_lockfile = true; + if let Some(db) = conn.as_sql() { + if let Some(cached) = sqlx::query_scalar!( + "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", + hash + ) + .fetch_optional(db) + .await? + { + let logs1 = format!("\nfound cached resolution: {}", hash); + append_logs(&job_id, w_id, logs1, conn).await; + gen_go_mod(code, job_dir, &cached).await?; + skip_tidy = true; + new_lockfile = false; + } else { + new_lockfile = true; + } } } - let mod_command = if skip_tidy { "download" } else { "tidy" }; + let mod_command = if skip_tidy || + // If there is go.mod provided we want to use `download` only. + // Unlike `tidy` it does not modify local go.mod + raw_deps + { + "download" + } else { + "tidy" + }; let mut child_cmd = Command::new(GO_PATH.as_str()); child_cmd .current_dir(job_dir) @@ -434,7 +466,7 @@ pub async fn install_go_dependencies( handle_child( job_id, - db, + conn, mem_peak, canceled_by, child_process, @@ -445,6 +477,7 @@ pub async fn install_go_dependencies( None, false, &mut Some(occupation_metrics), + None, ) .await?; @@ -464,11 +497,15 @@ pub async fn install_go_dependencies( } if non_dep_job { - sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", - hash, - req_content - ).fetch_optional(db).await?; + if let Some(db) = conn.as_sql() { + sqlx::query!( + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + hash, + req_content + ) + .fetch_optional(db) + .await?; + } return Ok(String::new()); } else { diff --git a/backend/windmill-worker/src/graphql_executor.rs b/backend/windmill-worker/src/graphql_executor.rs index 929235cccf..d9117adb53 100644 --- a/backend/windmill-worker/src/graphql_executor.rs +++ b/backend/windmill-worker/src/graphql_executor.rs @@ -1,20 +1,19 @@ use std::collections::HashMap; -use anyhow::anyhow; use futures::{stream, TryStreamExt}; use serde_json::{json, value::RawValue}; use sqlx::types::Json; -use windmill_common::jobs::QueuedJob; -use windmill_common::worker::to_raw_value; +use windmill_common::worker::{to_raw_value, Connection}; use windmill_common::{error::Error, worker::CLOUD_HOSTED}; use windmill_parser_graphql::parse_graphql_sig; -use windmill_queue::CanceledBy; +use windmill_queue::{CanceledBy, MiniPulledJob}; use serde::Deserialize; use crate::common::{build_http_client, resolve_job_timeout, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; -use crate::{common::build_args_map, AuthedClientBackgroundTask}; +use crate::common::build_args_map; +use windmill_common::client::AuthedClient; #[derive(Deserialize)] struct GraphqlApi { @@ -35,16 +34,16 @@ struct GraphqlError { } pub async fn do_graphql( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, occupation_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let args = build_args_map(job, client, db).await?.map(Json); + let args = build_args_map(job, client, conn).await?.map(Json); let job_args = if args.is_some() { args.as_ref() } else { @@ -82,7 +81,7 @@ pub async fn do_graphql( } } let (timeout_duration, _, _) = - resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await; + resolve_job_timeout(&conn, &job.workspace_id, job.id, job.timeout).await; let http_client = build_http_client(timeout_duration)?; @@ -135,11 +134,13 @@ pub async fn do_graphql( .map_err(|e| Error::ExecutionErr(e.to_string()))?; if let Some(errors) = result.errors { - return Err(anyhow!(errors - .into_iter() - .map(|x| x.message) - .collect::>() - .join("\n"),)); + return Err(Error::ExecutionErr( + errors + .into_iter() + .map(|x| x.message) + .collect::>() + .join("\n"), + )); } // And then check that we got back the same string we sent over. @@ -151,7 +152,7 @@ pub async fn do_graphql( let r = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, result_f, diff --git a/backend/windmill-worker/src/handle_child.rs b/backend/windmill-worker/src/handle_child.rs index 7294950363..467fac7c60 100644 --- a/backend/windmill-worker/src/handle_child.rs +++ b/backend/windmill-worker/src/handle_child.rs @@ -4,8 +4,9 @@ use futures::Future; use nix::sys::signal::{self, Signal}; #[cfg(any(target_os = "linux", target_os = "macos"))] use nix::unistd::Pid; +use windmill_common::agent_workers::PingJobStatusResponse; +use windmill_common::jobs::LARGE_LOG_THRESHOLD_SIZE; -use sqlx::{Pool, Postgres}; #[cfg(windows)] use std::process::Stdio; use tokio::fs::File; @@ -15,7 +16,10 @@ use windmill_common::error::to_anyhow; use windmill_common::error::{self, Error}; -use windmill_common::worker::{get_windmill_memory_usage, get_worker_memory_usage, CLOUD_HOSTED}; +use windmill_common::worker::{ + get_windmill_memory_usage, get_worker_memory_usage, set_job_cancelled_query, Connection, + JobCancelled, CLOUD_HOSTED, +}; use windmill_queue::{append_logs, CanceledBy}; @@ -29,7 +33,6 @@ use std::{io, panic, time::Duration}; use tracing::{trace_span, Instrument}; use uuid::Uuid; -use windmill_common::DB; #[cfg(feature = "enterprise")] use windmill_common::job_metrics; @@ -49,8 +52,9 @@ use futures::{ }; use crate::common::{resolve_job_timeout, OccupancyMetrics}; -use crate::job_logger::{append_job_logs, append_with_limit, LARGE_LOG_THRESHOLD_SIZE}; -use crate::job_logger_ee::process_streaming_log_lines; +use crate::job_logger::{append_job_logs, append_with_limit}; +use crate::job_logger_oss::process_streaming_log_lines; +use crate::worker_utils::{ping_job_status, update_worker_ping_from_job}; use crate::{MAX_RESULT_SIZE, MAX_WAIT_FOR_SIGINT, MAX_WAIT_FOR_SIGTERM}; lazy_static::lazy_static! { @@ -92,7 +96,7 @@ async fn kill_process_tree(pid: Option) -> Result<(), String> { #[tracing::instrument(name="run_subprocess", level = "info", skip_all, fields(otel.name = %child_name))] pub async fn handle_child( job_id: &Uuid, - db: &Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by_ref: &mut Option, mut child: Child, @@ -103,6 +107,8 @@ pub async fn handle_child( custom_timeout: Option, sigterm: bool, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + // Do not print logs to output, but instead save to string. + pipe_stdout: Option<&mut String>, ) -> error::Result<()> { let start = Instant::now(); @@ -124,19 +130,19 @@ pub async fn handle_child( } else { tracing::info!("could not get child pid"); } - let (set_too_many_logs, mut too_many_logs) = watch::channel::(false); + let (mut set_too_many_logs, mut too_many_logs) = watch::channel::(false); let (tx, rx) = broadcast::channel::<()>(3); - let mut rx2 = tx.subscribe(); + let mut rx2: broadcast::Receiver<()> = tx.subscribe(); - let output = child_joined_output_stream(&mut child, job_id.clone()); + let output = child_joined_output_stream(&mut child, job_id.clone(), w_id.to_string()); - let job_id = job_id.clone(); + let job_id: Uuid = job_id.clone(); /* the cancellation future is polled on by `wait_on_child` while * waiting for the child to exit normally */ let update_job = update_job_poller( job_id, - db, + conn, mem_peak, canceled_by_ref, Box::pin(stream::unfold((), move |_| async move { @@ -182,15 +188,13 @@ pub async fn handle_child( } let (timeout_duration, timeout_warn_msg, is_job_specific) = - resolve_job_timeout(&db, w_id, job_id, custom_timeout).await; + resolve_job_timeout(&conn, w_id, job_id, custom_timeout).await; if let Some(msg) = timeout_warn_msg { - append_logs(&job_id, w_id, msg.as_str(), db).await; + append_logs(&job_id, w_id, msg.as_str(), conn).await; } /* a future that completes when the child process exits */ let wait_on_child = async { - let db = db.clone(); - let kill_reason = tokio::select! { biased; result = child.wait() => return result.map(Ok), @@ -206,18 +210,34 @@ pub async fn handle_child( let set_reason = async { if matches!(kill_reason, KillReason::Timeout { .. }) { - if let Err(err) = sqlx::query!( - "UPDATE v2_job_queue - SET canceled_by = 'timeout' - , canceled_reason = $1 - WHERE id = $2", - format!("duration > {}", timeout_duration.as_secs()), - job_id - ) - .execute(&db) - .await - { - tracing::error!(%job_id, %err, "error setting cancelation reason for job {job_id}: {err}"); + match conn { + Connection::Sql(db) => { + if let Err(err) = set_job_cancelled_query( + job_id, + db, + "timeout", + &format!("duration > {}", timeout_duration.as_secs()), + ) + .await + { + tracing::error!(%job_id, %err, "error setting cancelation reason for job {job_id}: {err}"); + } + } + Connection::Http(client) => { + if let Err(err) = client + .post::<_, ()>( + &format!("/api/agent_workers/set_job_cancelled/{}", job_id), + None, + &JobCancelled { + canceled_by: "timeout".to_string(), + reason: format!("duration > {}", timeout_duration.as_secs()), + }, + ) + .await + { + tracing::error!(%job_id, %err, "error setting cancelation reason for job using http {job_id}: {err}"); + } + } } } }; @@ -277,140 +297,19 @@ pub async fn handle_child( }; /* a future that reads output from the child and appends to the database */ - let lines = async move { - - let max_log_size = if *CLOUD_HOSTED { - MAX_RESULT_SIZE - } else { - usize::MAX - }; - - /* log_remaining is zero when output limit was reached */ - let mut log_remaining = if *CLOUD_HOSTED { - max_log_size - } else { - usize::MAX - }; - let mut result = io::Result::Ok(()); - let mut output = output.take_until(async { - let _ = rx2.recv().await; - //wait at most 50ms after end of a script for output stream to end - tokio::time::sleep(Duration::from_millis(50)).await; - }).boxed(); - /* `do_write` resolves the task, but does not contain the Result. - * It's useful to know if the task completed. */ - let (mut do_write, mut write_result) = tokio::spawn(ready(())).remote_handle(); - - let mut log_total_size: u64 = 0; - let pg_log_total_size = Arc::new(AtomicU32::new(0)); - - while let Some(line) = output.by_ref().next().await { - - let do_write_ = do_write.shared(); - - let delay = if start.elapsed() < Duration::from_secs(10) { - Duration::from_millis(500) - } else if start.elapsed() < Duration::from_secs(60){ - Duration::from_millis(2500) - } else { - Duration::from_millis(5000) - }; - - let delay = if *SLOW_LOGS { - delay * 10 - } else { - delay - }; - - let mut read_lines = stream::once(async { line }) - .chain(output.by_ref()) - /* after receiving a line, continue until some delay has passed - * _and_ the previous database write is complete */ - .take_until(future::join(sleep(delay), do_write_.clone())) - .boxed(); - - /* Read up until an error is encountered, - * handle log lines first and then the error... */ - let mut joined = String::new(); - - while let Some(line) = read_lines.next().await { - - match line { - Ok(line) => { - if line.is_empty() { - continue; - } - append_with_limit(&mut joined, &line, &mut log_remaining); - if log_remaining == 0 { - tracing::info!(%job_id, "Too many logs lines for job {job_id}"); - let _ = set_too_many_logs.send(true); - joined.push_str(&format!( - "Job logs or result reached character limit of {MAX_RESULT_SIZE}; killing job." - )); - /* stop reading and drop our streams fairly quickly */ - break; - } - } - Err(err) => { - result = Err(err); - break; - } - } - } - - - /* Ensure the last flush completed before starting a new one. - * - * This shouldn't pause since `take_until()` reads lines until `do_write` - * resolves. We only stop reading lines before `take_until()` resolves if we reach - * EOF or a read error. In those cases, waiting on a database query to complete is - * fine because we're done. */ - - if let Some(Ok(p)) = do_write_ - .then(|()| write_result) - .await - .err() - .map(|err| err.try_into_panic()) - { - panic::resume_unwind(p); - } - - - let joined_len = joined.len() as u64; - log_total_size += joined_len; - let compact_logs = log_total_size > LARGE_LOG_THRESHOLD_SIZE as u64; - if compact_logs { - log_total_size = 0; - } - - let worker_name = worker.to_string(); - let w_id2 = w_id.to_string(); - (do_write, write_result) = tokio::spawn(append_job_logs(job_id, w_id2, joined, db.clone(), compact_logs, pg_log_total_size.clone(), worker_name)).remote_handle(); - - - - if let Err(err) = result { - tracing::error!(%job_id, %err, "error reading output for job {job_id} '{child_name}': {err}"); - break; - } - - if *set_too_many_logs.borrow() { - break; - } - } - - /* drop our end of the pipe */ - drop(output); - - if let Some(Ok(p)) = do_write - .then(|()| write_result) - .await - .err() - .map(|err| err.try_into_panic()) - { - panic::resume_unwind(p); - } - }.instrument(trace_span!("child_lines")); + let lines = write_lines( + output, + &job_id, + w_id, + worker, + conn, + &mut set_too_many_logs, + start, + pipe_stdout, + &mut rx2, + child_name, + ) + .instrument(trace_span!("child_lines")); let (wait_result, _) = tokio::join!(wait_on_child, lines); @@ -436,6 +335,169 @@ pub async fn handle_child( } } +pub async fn write_lines( + output: impl stream::Stream> + Send, + job_id: &Uuid, + w_id: &str, + worker: &str, + conn: &Connection, + set_too_many_logs: &mut watch::Sender, + start: Instant, + pipe_stdout: Option<&mut String>, + rx2: &mut broadcast::Receiver<()>, + child_name: &str, +) { + let max_log_size = if *CLOUD_HOSTED { + MAX_RESULT_SIZE + } else { + usize::MAX + }; + + /* log_remaining is zero when output limit was reached */ + let mut log_remaining = if *CLOUD_HOSTED { + max_log_size + } else { + usize::MAX + }; + let mut result = io::Result::Ok(()); + let mut output = output + .take_until(async { + let _ = rx2.recv().await; + //wait at most 50ms after end of a script for output stream to end + tokio::time::sleep(Duration::from_millis(50)).await; + }) + .boxed(); + /* `do_write` resolves the task, but does not contain the Result. + * It's useful to know if the task completed. */ + let (mut do_write, mut write_result) = tokio::spawn(ready(())).remote_handle(); + + let mut log_total_size: u64 = 0; + let pg_log_total_size = Arc::new(AtomicU32::new(0)); + + let mut pipe_stdout = pipe_stdout; + + while let Some(line) = output.by_ref().next().await { + let do_write_ = do_write.shared(); + + let delay = if start.elapsed() < Duration::from_secs(10) { + Duration::from_millis(500) + } else if start.elapsed() < Duration::from_secs(60) { + Duration::from_millis(2500) + } else { + Duration::from_millis(5000) + }; + + let delay = if *SLOW_LOGS { delay * 10 } else { delay }; + + let mut read_lines = stream::once(async { line }) + .chain(output.by_ref()) + /* after receiving a line, continue until some delay has passed + * _and_ the previous database write is complete */ + .take_until(future::join(sleep(delay), do_write_.clone())) + .boxed(); + + /* Read up until an error is encountered, + * handle log lines first and then the error... */ + let mut joined = String::new(); + + let job_id = job_id.clone(); + while let Some(line) = read_lines.next().await { + match line { + Ok(line) => { + if line.is_empty() { + continue; + } + append_with_limit(&mut joined, &line, &mut log_remaining); + if log_remaining == 0 { + tracing::info!(%job_id, "Too many logs lines for job {job_id}"); + let _ = set_too_many_logs.send(true); + joined.push_str(&format!( + "Job logs or result reached character limit of {MAX_RESULT_SIZE}; killing job." + )); + /* stop reading and drop our streams fairly quickly */ + break; + } + } + Err(err) => { + result = Err(err); + break; + } + } + } + + /* Ensure the last flush completed before starting a new one. + * + * This shouldn't pause since `take_until()` reads lines until `do_write` + * resolves. We only stop reading lines before `take_until()` resolves if we reach + * EOF or a read error. In those cases, waiting on a database query to complete is + * fine because we're done. */ + + if let Some(Ok(p)) = do_write_ + .then(|()| write_result) + .await + .err() + .map(|err| err.try_into_panic()) + { + panic::resume_unwind(p); + } + + let joined_len = joined.len() as u64; + log_total_size += joined_len; + let compact_logs = log_total_size > LARGE_LOG_THRESHOLD_SIZE as u64; + if compact_logs { + log_total_size = 0; + } + + let worker_name = worker.to_string(); + + if let Some(buf) = &mut pipe_stdout { + buf.push_str(&joined); + (do_write, write_result) = tokio::spawn(async {}).remote_handle(); + } else { + let conn = conn.clone(); + let worker_name = worker_name.to_string(); + let w_id = w_id.to_string(); + let job_id = job_id.clone(); + let pg_log_total_size = pg_log_total_size.clone(); + + (do_write, write_result) = tokio::spawn(async move { + append_job_logs( + &job_id, + &w_id, + &joined, + &conn, + compact_logs, + pg_log_total_size, + &worker_name, + ) + .await; + }) + .remote_handle(); + } + + if let Err(err) = result { + tracing::error!(%job_id, %err, "error reading output for job {job_id} '{child_name}': {err}"); + break; + } + + if *set_too_many_logs.borrow() { + break; + } + } + + /* drop our end of the pipe */ + drop(output); + + if let Some(Ok(p)) = do_write + .then(|()| write_result) + .await + .err() + .map(|err| err.try_into_panic()) + { + panic::resume_unwind(p); + } +} + pub(crate) async fn get_mem_peak(pid: Option, nsjail: bool) -> i32 { if pid.is_none() { return -1; @@ -490,7 +552,7 @@ pub(crate) async fn get_mem_peak(pid: Option, nsjail: bool) -> i32 { pub async fn run_future_with_polling_update_job_poller( job_id: Uuid, timeout: Option, - db: &DB, + conn: &Connection, mem_peak: &mut i32, canceled_by_ref: &mut Option, result_f: Fut, @@ -500,14 +562,14 @@ pub async fn run_future_with_polling_update_job_poller( get_mem: S, ) -> error::Result where - Fut: Future>, + Fut: Future>, S: stream::Stream + Unpin, { let (tx, rx) = broadcast::channel::<()>(3); let update_job = update_job_poller( job_id, - db, + conn, mem_peak, canceled_by_ref, get_mem, @@ -518,7 +580,7 @@ where ); let timeout_ms = u64::try_from( - resolve_job_timeout(&db, &w_id, job_id, timeout) + resolve_job_timeout(&conn, &w_id, job_id, timeout) .await .0 .as_millis(), @@ -553,7 +615,7 @@ pub enum UpdateJobPollingExit { pub async fn update_job_poller( job_id: Uuid, - db: &DB, + conn: &Connection, mem_peak: &mut i32, canceled_by_ref: &mut Option, mut get_mem: S, @@ -567,8 +629,7 @@ where { let update_job_interval = Duration::from_millis(500); - let db = db.clone(); - + let conn = conn.clone(); let mut interval = interval(update_job_interval); interval.set_missed_tick_behavior(MissedTickBehavior::Skip); @@ -590,22 +651,9 @@ where tracing::info!("job {job_id} on {worker_name} in {w_id} worker memory snapshot {}kB/{}kB", memory_usage.unwrap_or_default()/1024, wm_memory_usage.unwrap_or_default()/1024); let occupancy = occupancy_metrics.as_mut().map(|x| x.update_occupancy_metrics()); if job_id != Uuid::nil() { - sqlx::query!( - "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4, - occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5", - &job_id, - &w_id, - memory_usage, - wm_memory_usage, - &worker_name, - occupancy.map(|x| x.0), - occupancy.and_then(|x| x.1), - occupancy.and_then(|x| x.2), - occupancy.and_then(|x| x.3), - ) - .execute(&db) - .await - .expect("update worker ping"); + if let Err(err) = update_worker_ping_from_job(&conn, &job_id, w_id, worker_name, memory_usage, wm_memory_usage, occupancy).await { + tracing::error!("Unable to update worker ping for job {} in workspace {}. Error was: {:?}", job_id, w_id, err); + } } } let current_mem = get_mem.next().await.unwrap_or(0); @@ -620,55 +668,49 @@ where #[cfg(feature = "enterprise")] { if job_id != Uuid::nil() { - - // tracking metric starting at i >= 2 b/c first point it useless and we don't want to track metric for super fast jobs - if i == 2 { - memory_metric_id = job_metrics::register_metric_for_job( - &db, - w_id.to_string(), - job_id, - "memory_kb".to_string(), - job_metrics::MetricKind::TimeseriesInt, - Some("Job Memory Footprint (kB)".to_string()), - ) - .await; - } - if let Ok(ref metric_id) = memory_metric_id { - if let Err(err) = job_metrics::record_metric(&db, w_id.to_string(), job_id, metric_id.to_owned(), job_metrics::MetricNumericValue::Integer(current_mem)).await { - tracing::error!("Unable to save memory stat for job {} in workspace {}. Error was: {:?}", job_id, w_id, err); + if let Connection::Sql(ref db) = conn { + // tracking metric starting at i >= 2 b/c first point it useless and we don't want to track metric for super fast jobs + if i == 2 { + memory_metric_id = job_metrics::register_metric_for_job( + &db, + w_id.to_string(), + job_id, + "memory_kb".to_string(), + job_metrics::MetricKind::TimeseriesInt, + Some("Job Memory Footprint (kB)".to_string()), + ) + .await; + } + if let Ok(ref metric_id) = memory_metric_id { + if let Err(err) = job_metrics::record_metric(&db, w_id.to_string(), job_id, metric_id.to_owned(), job_metrics::MetricNumericValue::Integer(current_mem)).await { + tracing::error!("Unable to save memory stat for job {} in workspace {}. Error was: {:?}", job_id, w_id, err); + } } } } } if job_id != Uuid::nil() { - let (canceled_by, canceled_reason, already_completed) = sqlx::query!( - "UPDATE v2_job_runtime r SET - memory_peak = $1, - ping = now() - FROM v2_job_queue q - WHERE r.id = $2 AND q.id = r.id - RETURNING canceled_by, canceled_reason", - *mem_peak, - job_id - ) - .map(|x| (x.canceled_by, x.canceled_reason, false)) - .fetch_optional(&db) - .await - .unwrap_or_else(|e| { - tracing::error!(%e, "error updating job {job_id}: {e:#}"); - Some((None, None, false)) - }) - .unwrap_or_else(|| { - // if the job is not in queue, it can only be in the completed_job so it is already complete - (None, None, true) - }); - if already_completed { + if matches!(conn, Connection::Http(_)) { + if i % 4 != 0 { + // only ping every 4th time (2s) on http agent mode + continue; + } + } + let ping_job_status = ping_job_status(&conn, &job_id, Some(*mem_peak), if current_mem > 0 { Some(current_mem) } else { None }).await.unwrap_or_else(|e| { + tracing::error!("Unable to ping job status for job {job_id}. Error was: {:?}", e); + PingJobStatusResponse { + canceled_by: None, + canceled_reason: None, + already_completed: false, + } + }); + if ping_job_status.already_completed { return UpdateJobPollingExit::AlreadyCompleted } - if canceled_by.is_some() { + if ping_job_status.canceled_by.is_some() { canceled_by_ref.replace(CanceledBy { - username: canceled_by.clone(), - reason: canceled_reason.clone(), + username: ping_job_status.canceled_by.clone(), + reason: ping_job_status.canceled_reason.clone(), }); break } @@ -688,6 +730,7 @@ where fn child_joined_output_stream( child: &mut Child, job_id: Uuid, + w_id: String, ) -> impl stream::FusedStream> { let stderr = child .stderr @@ -702,8 +745,8 @@ fn child_joined_output_stream( let stdout = BufReader::new(stdout).lines(); let stderr = BufReader::new(stderr).lines(); stream::select( - lines_to_stream(stderr, true, job_id.clone()), - lines_to_stream(stdout, false, job_id), + lines_to_stream(stderr, true, job_id.clone(), w_id.clone()), + lines_to_stream(stdout, false, job_id, w_id), ) } @@ -711,11 +754,12 @@ pub fn lines_to_stream( mut lines: tokio::io::Lines, stderr: bool, job_id: Uuid, + w_id: String, ) -> impl futures::Stream> { stream::poll_fn(move |cx| { std::pin::Pin::new(&mut lines) .poll_next_line(cx) - .map(|result| process_streaming_log_lines(result, stderr, &job_id)) + .map(|result| process_streaming_log_lines(result, stderr, &job_id, &w_id)) }) } diff --git a/backend/windmill-worker/src/java_executor.rs b/backend/windmill-worker/src/java_executor.rs new file mode 100644 index 0000000000..f62de150a9 --- /dev/null +++ b/backend/windmill-worker/src/java_executor.rs @@ -0,0 +1,923 @@ +use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; + +use anyhow::{anyhow, bail}; +use async_recursion::async_recursion; +use itertools::Itertools; +use serde_json::value::RawValue; +use tokio::{ + fs::{create_dir_all, metadata, remove_dir_all, File}, + io::AsyncWriteExt, + process::Command, +}; +use uuid::Uuid; +use windmill_common::{ + error::{self, Error}, + utils::calculate_hash, + worker::{copy_dir_recursively, save_cache, write_file, Connection}, +}; +use windmill_parser::Arg; +use windmill_parser_java::parse_java_sig_meta; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; + +use crate::{ + common::{ + create_args_and_out_file, get_reserved_variables, par_install_language_dependencies, + read_result, start_child_process, OccupancyMetrics, RequiredDependency, + }, + handle_child, COURSIER_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, JAVA_CACHE_DIR, + JAVA_REPOSITORY_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, +}; +use windmill_common::client::AuthedClient; + +lazy_static::lazy_static! { + static ref JAVA_CONCURRENT_DOWNLOADS: usize = std::env::var("JAVA_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + static ref JAVA_PATH: String = std::env::var("JAVA_PATH").unwrap_or_else(|_| "/usr/bin/java".to_string()); + static ref JAVAC_PATH: String = std::env::var("JAVAC_PATH").unwrap_or_else(|_| "/usr/bin/javac".to_string()); + static ref CS_PATH: String = std::env::var("COURSIER_PATH").unwrap_or_else(|_| "/usr/bin/coursier".to_string()); + static ref STOREPASS: String = std::env::var("JAVA_STOREPASS").unwrap_or("123456".into()); + static ref TRUST_STORE_PATH: String = std::env::var("JAVA_TRUST_STORE_PATH").unwrap_or("/usr/local/share/ca-certificates/truststore.jks".into()); +} + +const NSJAIL_CONFIG_RUN_JAVA_CONTENT: &str = include_str!("../nsjail/run.java.config.proto"); + +#[allow(dead_code)] +pub(crate) struct JobHandlerInput<'a> { + pub base_internal_url: &'a str, + pub canceled_by: &'a mut Option, + pub client: &'a AuthedClient, + pub parent_runnable_path: Option, + pub conn: &'a Connection, + pub envs: HashMap, + pub inner_content: &'a str, + pub job: &'a MiniPulledJob, + pub job_dir: &'a str, + pub mem_peak: &'a mut i32, + pub occupancy_metrics: &'a mut OccupancyMetrics, + pub requirements_o: Option<&'a String>, + pub shared_mount: &'a str, + pub worker_name: &'a str, +} + +pub async fn handle_java_job<'a>(mut args: JobHandlerInput<'a>) -> Result, Error> { + // --- Prepare --- + { + prepare(&mut args).await?; + } + // --- Generate Lockfile --- + + let deps = resolve( + &args.job.id, + &args.inner_content, + &args.job_dir, + &args.conn, + &args.job.workspace_id, + ) + .await?; + + // --- Install --- + + let classpath = install(&mut args, deps).await?; + + // --- Build .java files --- + { + compile(&mut args, &classpath).await?; + } + // --- Run --- + { + run(&mut args, &classpath).await?; + } + // --- Retrieve results --- + { + read_result(&args.job_dir).await + } +} + +async fn prepare<'a>( + JobHandlerInput { job, conn, job_dir, client, inner_content, .. }: &mut JobHandlerInput<'a>, +) -> Result<(), Error> { + // Create needed files + { + create_args_and_out_file(&client, job, job_dir, conn).await?; + let app_path = format!("{}/src/main/java/net/script/", job_dir); + create_dir_all(&app_path).await?; + File::create(format!("{app_path}/App.java")) + .await? + .write_all(&wrap(inner_content)?.into_bytes()) + .await?; + File::create(format!("{app_path}/Main.java")) + .await? + .write_all( + &format!( + "package net.script;\n{MINI_CLIENT_IMPORTS}\n{}\n{MINI_CLIENT}", + inner_content + ) + .into_bytes(), + ) + .await?; + } + Ok(()) +} + +pub async fn resolve<'a>( + job_id: &Uuid, + code: &str, + job_dir: &str, + conn: &Connection, + w_id: &str, +) -> Result { + let deps = { + let find_requirements = code.lines().find_position(|x| { + x.starts_with("//requirements:") || x.starts_with("// requirements:") + }); + + let specified_deps = if let Some((pos, _)) = find_requirements { + code.lines() + .skip(pos + 1) + .map_while(|x| { + if x.starts_with("//") { + Some(x.replace("//", "").trim().to_owned()) + } else { + None + } + }) + .collect::>() + } else { + Default::default() + }; + + let mut deps = vec![ + // Default requirements + "com.fasterxml.jackson.core:jackson-databind:2.9.8".to_owned(), + ]; + deps.extend(specified_deps); + deps.join("\n") + }; + + let req_hash = format!("java-{}", calculate_hash(&deps)); + if let Connection::Sql(db) = conn { + if let Some(cached) = sqlx::query_scalar!( + "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", + req_hash + ) + .fetch_optional(db) + .await? + { + return Ok(cached); + } + } + let lock = { + append_logs( + job_id, + w_id, + format!("\n--- RESOLVING LOCKFILE ---\n"), + &conn, + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .envs(PROXY_ENVS.clone()); + + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + cmd.args(&[ + "-jar", + &CS_PATH, + "resolve", + &get_no_default(), + "--parallel", + &format!("{}", *JAVA_CONCURRENT_DOWNLOADS), + "--cache", + COURSIER_CACHE_DIR, + ]) + .args(&get_repos().await) + .args(&deps.split("\n").collect_vec()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + let output = cmd.output().await?; + // Check if the command was successful + if output.status.success() { + String::from_utf8(output.stdout).expect("Failed to convert output to String") + } else { + let stderr = + String::from_utf8(output.stderr).expect("Failed to convert error output to String"); + return Err(error::Error::internal_err(stderr)); + } + }; + + if let Connection::Sql(db) = conn { + sqlx::query!( + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + req_hash, + lock.clone(), + ) + .fetch_optional(db) + .await?; + } + + append_logs(job_id, w_id, format!("\n{}", &lock), &conn).await; + Ok(lock) +} + +async fn install<'a>( + JobHandlerInput { worker_name, job, conn, job_dir, .. }: &mut JobHandlerInput<'a>, + deps: String, +) -> Result { + let deps = deps + .lines() + .map(|line| { + let unparsed_dep = line.replace(":jar", "").replace(":lib", ""); + let mut it = unparsed_dep.split(":"); + + match (it.next(), it.next(), it.next()) { + (Some(group_id), Some(artifact_id), Some(version)) => { + let path = format!( + "{JAVA_REPOSITORY_DIR}/{}/{artifact_id}/{version}", + group_id.replace(".", "/") + ); + Ok(RequiredDependency { + path, + custom_name: Some(format!("{group_id}:{artifact_id}:{version}")), + short_name: Some(format!("{artifact_id}:{version}")), + }) + } + _ => anyhow::bail!("{line} is not parsable"), + } + }) + .collect::>>()?; + + let classpath = deps + .clone() + .into_iter() + .map(|RequiredDependency { path, .. }| path + "/*") + .collect_vec() + .join(":") + + ":target"; + + #[cfg(windows)] + let classpath = classpath.replace(":", ";"); + + tracing::debug!( + workspace_id = %job.workspace_id, + "JAVA classpath: {}", &classpath + ); + let (repos, no_default, trust_store_metadata) = ( + get_repos().await, + get_no_default(), + metadata(TRUST_STORE_PATH.clone()).await, + ); + let job_dir = job_dir.to_owned(); + let fetch_dir = format!("{JAVA_CACHE_DIR}/tmp-fetch-{}", Uuid::new_v4()); + let fetch_dir2 = fetch_dir.clone(); + par_install_language_dependencies( + deps, + "java", + "java", + true, + *JAVA_CONCURRENT_DOWNLOADS, + true, + crate::common::InstallStrategy::AllAtOnce(Arc::new(move |dependencies| { + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + let artifacts = dependencies + .into_iter() + .map(|e| { + e.custom_name.ok_or(anyhow::anyhow!( + "Internal Error: Artifact name should be Some!" + )) + }) + .collect::>>()?; + cmd.env_clear() + .current_dir(&job_dir) + .env("PATH", PATH_ENV.as_str()) + .envs(PROXY_ENVS.clone()); + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + + if trust_store_metadata.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + cmd.args(&[ + "-jar", + &CS_PATH, + "fetch", + &no_default, + "--quiet", + "--parallel", + &format!("{}", *JAVA_CONCURRENT_DOWNLOADS), + "--cache", + &fetch_dir, + ]) + .args(&repos) + .arg("--intransitive") + .args(artifacts) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + + Ok(cmd) + })), + async move |_| { + move_to_repository(&fetch_dir2, 0).await?; + remove_dir_all(&fetch_dir2).await?; + #[async_recursion] + async fn move_to_repository(path: &str, depth: u8) -> anyhow::Result<()> { + if depth == 3 { + copy_dir_recursively( + &PathBuf::from(path), + &PathBuf::from(JAVA_REPOSITORY_DIR), + )?; + + return Ok(()); + } + let mut entries = tokio::fs::read_dir(path).await?; + loop { + let Some(entry) = entries.next_entry().await? else { + break Ok(()); + }; + + let path = entry + .path() + .to_str() + .ok_or(anyhow!("Internal Error: Cannot convert Path to Str"))? + .to_owned(); + + move_to_repository(&path, depth + 1).await?; + } + } + Ok(()) + }, + &job.id, + &job.workspace_id, + worker_name, + conn, + ) + .await?; + Ok(classpath) +} + +async fn compile<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + conn, + job_dir, + client, + envs, + base_internal_url, + inner_content, + requirements_o, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + classpath: &'a str, + // plugins: Vec<&'a str>, +) -> Result<(), Error> { + fn compute_hash(code: &str, requirements_o: Option<&String>) -> String { + calculate_hash(&format!( + "{}{}", + code, + requirements_o + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_default() + )) + } + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?; + let hash = compute_hash(inner_content, *requirements_o); + let bin_path = format!("{}/{hash}", JAVA_CACHE_DIR); + let remote_path = format!("java_jar/{hash}"); + let (cache, ..) = windmill_common::worker::load_cache(&bin_path, &remote_path, true).await; + + if cache { + let target = format!("{job_dir}/target"); + + #[cfg(unix)] + let symlink = std::os::unix::fs::symlink(&bin_path, &target); + #[cfg(windows)] + let symlink = copy_dir_recursively(&PathBuf::from(&bin_path), &PathBuf::from(&target)); + + symlink.map_err(|e| { + Error::ExecutionErr(format!( + "could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}" + )) + })?; + } else { + // let plugin_registry = format!("{job_dir}/plugin-registry"); + let child = { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- COMPILING .JAVA FILES\n"), + &conn, + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "javac" + } else { + JAVAC_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(&[ + "-classpath", + &classpath, + "src/main/java/net/script/Main.java", + "src/main/java/net/script/App.java", + "-d", + "./target", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "javac").await? + }; + handle_child::handle_child( + &job.id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "javac", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + + match save_cache( + &bin_path, + &format!("java_jar/{hash}"), + &format!("{job_dir}/target"), + true, + ) + .await + { + Err(e) => { + let em = format!( + "could not save {bin_path} to {} to java cache: {e:?}", + format!("{job_dir}/main"), + ); + tracing::error!(em); + } + Ok(logs) => { + tracing::trace!(logs); + } + } + }; + + Ok(()) +} +async fn run<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + conn, + job_dir, + shared_mount, + client, + envs, + base_internal_url, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + classpath: &'a str, +) -> Result<(), Error> { + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?; + + let child = if !cfg!(windows) && !*DISABLE_NSJAIL { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- ISOLATED JAVA CODE EXECUTION ---\n"), + &conn, + ) + .await; + + write_file( + job_dir, + "run.config.proto", + &NSJAIL_CONFIG_RUN_JAVA_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{CACHE_DIR}", JAVA_CACHE_DIR) + .replace("{SHARED_MOUNT}", &shared_mount) + // .replace("{CACHED_TARGET}", &shared_mount) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), + )?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.env_clear() + .current_dir(job_dir) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(vec![ + "--config", + "run.config.proto", + "--", + JAVA_PATH.as_str(), + ]); + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + cmd.args(vec!["-classpath", &classpath, "net.script.App"]); + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + + start_child_process(cmd, NSJAIL_PATH.as_str()).await? + } else { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- JAVA CODE EXECUTION ---\n"), + &conn, + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "java" + } else { + JAVA_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables); + if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { + cmd.args(&[ + &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), + &format!("-Djavax.net.ssl.trustStorePassword={}", *STOREPASS), + ]); + } + // Configure proxies + { + let jps = parse_proxy()?; + if let Some(val) = jps.https_host { + cmd.arg(&format!("-Dhttps.proxyHost={}", val)); + } + if let Some(val) = jps.https_port { + cmd.arg(&format!("-Dhttps.proxyPort={}", val)); + } + if let Some(val) = jps.http_host { + cmd.arg(&format!("-Dhttp.proxyHost={}", val)); + } + if let Some(val) = jps.http_port { + cmd.arg(&format!("-Dhttp.proxyPort={}", val)); + } + if let Some(val) = jps.no_proxy { + cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); + } + } + cmd.args(&["-classpath", &classpath, "net.script.App"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "java").await? + }; + handle_child::handle_child( + &job.id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "java", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await +} + +#[derive(Default, Debug)] +struct JavaProxySettings { + http_host: Option, + http_port: Option, + https_host: Option, + https_port: Option, + no_proxy: Option, +} +fn parse_proxy() -> anyhow::Result { + let mut jps = JavaProxySettings::default(); + for (ident, mut val) in PROXY_ENVS.clone() { + match ident { + "HTTPS_PROXY" => { + if val.contains("http://") { + bail!("HTTPS_PROXY url cannot contain http scheme."); + } + if !val.contains("https://") { + val = format!("https://{val}"); + } + let mut url = url::Url::parse(&val)?; + let port = url.port(); + // Make sure port and schema is not included in final url + { + url.set_port(None).unwrap_or_default(); + jps.https_host = Some(url.as_str().replace("https://", "")); + if let Some(port) = port { + jps.https_port = Some(format!("{}", port)); + } + } + } + "HTTP_PROXY" => { + if val.contains("https://") { + bail!("HTTP_PROXY url cannot contain https scheme."); + } + if !val.contains("http://") { + val = format!("http://{val}"); + } + let mut url = url::Url::parse(&val)?; + let port = url.port(); + // Make sure port and schema is not included in final url + { + url.set_port(None).unwrap_or_default(); + jps.http_host = Some(url.as_str().replace("http://", "")); + if let Some(port) = port { + jps.https_port = Some(format!("{}", port)); + } + } + } + // Java uses | instead of , + "NO_PROXY" => jps.no_proxy = Some(val.replace(",", "|")), + _ => {} + } + } + + Ok(jps) +} +async fn get_repos() -> Vec { + MAVEN_REPOS + .read() + .await + .as_ref() + .map(|repos| { + repos + .trim() + .split_whitespace() + .into_iter() + .map(|el| vec!["--repository".to_owned(), el.to_owned()]) + .collect_vec() + }) + .unwrap_or_default() + .concat() +} + +fn get_no_default() -> String { + if NO_DEFAULT_MAVEN.load(std::sync::atomic::Ordering::Relaxed) { + "--no-default" + } else { + // Command does not take empty arguments + "-q" + } + .into() +} + +/// Wraps content script +/// that upon execution reads args.json (which are piped and transformed from previous flow step or top level inputs) +/// Also wrapper takes output of program and serializes to result.json (Which windmill will know how to use later) +fn wrap(inner_content: &str) -> Result { + let sig = parse_java_sig_meta(inner_content)?; + let ret_void = sig.returns_void; + let spread = sig + .main_sig + .args + .clone() + .into_iter() + .map(|Arg { name, .. }| { + // Apply additional input transformation + format!(" parsedArgs.{name}") + }) + .collect_vec() + .join(","); + let args = sig + .main_sig + .args + .clone() + .into_iter() + .map(|Arg { name, otyp, .. }| { + // Apply additional input transformation + format!("public {} {name};\n", otyp.unwrap()) + }) + .collect_vec() + .join(" "); + Ok(r#" +package net.script; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.FileOutputStream; +import net.script.Main; + +public class App{ + + public static class Args {ARGS} + + public static void main(String[] args) { + try { + InputStream fileInputStream = new FileInputStream("args.json"); + ObjectMapper mapper = new ObjectMapper(); + Args parsedArgs = mapper.readValue(fileInputStream, Args.class); + fileInputStream.close(); + {MAIN_HANDLER} + FileOutputStream fileOutputStream = new FileOutputStream("result.json"); + mapper.writeValue(fileOutputStream, res); + fileOutputStream.close(); + + } catch (Exception e) { // Catching general Exception + e.printStackTrace(); // Handle the exception + } + } +} + "# + .replace( + "{MAIN_HANDLER}", + if ret_void { + " + Main.main(SPREAD); + Object res = null; + " + } else { + " + Object res = Main.main(SPREAD); + " + }, + ) + .replace("SPREAD", &spread) + .replace("ARGS", &args)) +} +const MINI_CLIENT_IMPORTS: &str = r#" +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +"#; +const MINI_CLIENT: &str = r#" +class Wmill { + public static String getVariable(String path) { + var baseUrl = System.getenv("BASE_INTERNAL_URL"); + var workspace = System.getenv("WM_WORKSPACE"); + var uri = java.text.MessageFormat.format("{0}/api/w/{1}/variables/get_value/{2}", baseUrl, workspace, path); + + // Create an HttpRequest + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(uri)) + .header("Authorization", "Bearer " + System.getenv("WM_TOKEN")) // Add the Authorization header + .GET() // Set the request method to GET + .build(); + + // Send the request and get the response + return HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .join(); // Wait for the completion + } + public static String getResource(String path) { + var baseUrl = System.getenv("BASE_INTERNAL_URL"); + var workspace = System.getenv("WM_WORKSPACE"); + var uri = java.text.MessageFormat.format("{0}/api/w/{1}/resources/get_value_interpolated/{2}", baseUrl, workspace, path); + + // Create an HttpRequest + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(uri)) + .header("Authorization", "Bearer " + System.getenv("WM_TOKEN")) // Add the Authorization header + .GET() // Set the request method to GET + .build(); + + // Send the request and get the response + return HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .join(); // Wait for the completion + } +} +"#; diff --git a/backend/windmill-worker/src/job_logger.rs b/backend/windmill-worker/src/job_logger.rs index fa4ac15383..626dfbce44 100644 --- a/backend/windmill-worker/src/job_logger.rs +++ b/backend/windmill-worker/src/job_logger.rs @@ -1,20 +1,22 @@ use regex::Regex; -use windmill_common::worker::CLOUD_HOSTED; +pub use windmill_common::jobs::LARGE_LOG_THRESHOLD_SIZE; +use windmill_common::utils::WarnAfterExt; +use windmill_common::worker::{Connection, CLOUD_HOSTED}; +use windmill_common::DB; use windmill_queue::append_logs; use std::sync::atomic::AtomicU32; use std::sync::Arc; use uuid::Uuid; -use windmill_common::DB; #[cfg(not(all(feature = "enterprise", feature = "parquet")))] -use crate::job_logger_ee::default_disk_log_storage; +use crate::job_logger_oss::default_disk_log_storage; #[cfg(all(feature = "enterprise", feature = "parquet"))] -use crate::job_logger_ee::s3_storage; +use crate::job_logger_oss::s3_storage; pub enum CompactLogs { #[cfg(not(all(feature = "enterprise", feature = "parquet")))] @@ -25,38 +27,78 @@ pub enum CompactLogs { S3, } -pub(crate) async fn append_job_logs( - job_id: Uuid, - w_id: String, - logs: String, - db: DB, +pub async fn append_job_logs( + job_id: &Uuid, + w_id: &str, + logs: &str, + conn: &Connection, must_compact_logs: bool, total_size: Arc, - worker_name: String, + worker_name: &str, ) -> () { - if must_compact_logs { - #[cfg(all(feature = "enterprise", feature = "parquet"))] - s3_storage(job_id, &w_id, &db, logs, total_size, &worker_name).await; + match conn { + Connection::Sql(db) if must_compact_logs => { + #[cfg(all(feature = "enterprise", feature = "parquet"))] + s3_storage(&job_id, &w_id, &db, logs, total_size, worker_name).await; - #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - { - default_disk_log_storage( - job_id, - &w_id, - &db, - logs, - total_size, - CompactLogs::NotEE, - &worker_name, - ) - .await; + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + { + default_disk_log_storage( + &job_id, + &w_id, + &db, + logs, + total_size, + CompactLogs::NotEE, + &worker_name, + ) + .await; + } + } + _ => { + append_logs(&job_id, w_id, logs, &conn).await; } - } else { - append_logs(&job_id, w_id, logs, db).await; } } -pub const LARGE_LOG_THRESHOLD_SIZE: usize = 9000; +pub async fn append_logs_with_compaction( + job_id: &Uuid, + w_id: &str, + logs: &str, + db: &DB, + worker_name: &str, +) { + let log_length = sqlx::query_scalar!( + "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text) RETURNING length(logs)", + logs, + job_id, + &w_id, + ) + .fetch_one(db) + .warn_after_seconds(1) + .await; + match log_length { + Ok(length) => { + let len = length.unwrap_or(0); + let conn: Connection = db.into(); + if len > LARGE_LOG_THRESHOLD_SIZE as i32 { + append_job_logs( + &job_id, + w_id, + "", + &conn, + true, + Arc::new(AtomicU32::new(len as u32)), + worker_name, + ) + .await; + } + } + Err(err) => { + tracing::error!(%job_id, %err, "error updating logs for job {job_id}: {err}"); + } + } +} lazy_static::lazy_static! { static ref RE_00: Regex = Regex::new('\u{00}'.to_string().as_str()).unwrap(); diff --git a/backend/windmill-worker/src/job_logger_ee.rs b/backend/windmill-worker/src/job_logger_oss.rs similarity index 58% rename from backend/windmill-worker/src/job_logger_ee.rs rename to backend/windmill-worker/src/job_logger_oss.rs index 43cb43f673..3ee3d29a3a 100644 --- a/backend/windmill-worker/src/job_logger_ee.rs +++ b/backend/windmill-worker/src/job_logger_oss.rs @@ -1,30 +1,32 @@ -use std::io; -use std::sync::atomic::AtomicU32; -use std::sync::Arc; +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::job_logger_ee::*; -use uuid::Uuid; -use windmill_common::DB; +#[cfg(not(feature = "private"))] +use { + crate::job_logger::CompactLogs, std::io, std::sync::atomic::AtomicU32, std::sync::Arc, + uuid::Uuid, windmill_common::DB, +}; -use crate::job_logger::CompactLogs; - -#[cfg(all(feature = "enterprise", feature = "parquet"))] +#[cfg(all(feature = "enterprise", feature = "parquet", not(feature = "private")))] pub(crate) async fn s3_storage( - _job_id: Uuid, - _w_id: &String, + _job_id: &Uuid, + _w_id: &str, _db: &sqlx::Pool, - _logs: String, + _logs: &str, _total_size: Arc, - _worker_name: &String, + _worker_name: &str, ) { tracing::info!("Logs length of {_job_id} has exceeded a threshold. Implementation to store excess on s3 in not OSS"); } +#[cfg(not(feature = "private"))] #[allow(dead_code)] pub(crate) async fn default_disk_log_storage( - job_id: Uuid, + job_id: &Uuid, _w_id: &str, _db: &DB, - _nlogs: String, + _logs: &str, _total_size: Arc, _compact_kind: CompactLogs, _worker_name: &str, @@ -32,10 +34,12 @@ pub(crate) async fn default_disk_log_storage( tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on disk in not OSS"); } +#[cfg(not(feature = "private"))] pub(crate) fn process_streaming_log_lines( r: Result, io::Error>, _stderr: bool, _job_id: &Uuid, + _w_id: &str, ) -> Option> { r.transpose() } diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index ea6d3cf763..77ccd9b15f 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -45,10 +45,11 @@ use windmill_common::error::Error; #[cfg(feature = "deno_core")] use windmill_common::worker::{write_file, TMP_DIR}; -use windmill_common::{flow_status::JobResult, DB}; +use windmill_common::flow_status::JobResult; use windmill_queue::CanceledBy; -use crate::{common::OccupancyMetrics, AuthedClient}; +use crate::common::OccupancyMetrics; +use windmill_common::client::AuthedClient; #[cfg(feature = "deno_core")] use crate::{common::unsafe_raw, handle_child::run_future_with_polling_update_job_poller}; @@ -749,15 +750,19 @@ fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> { ) }) } + +use windmill_common::worker::Connection; + #[cfg(not(feature = "deno_core"))] pub async fn eval_fetch_timeout( _env_code: String, _ts_expr: String, _js_expr: String, _args: Option<&Json>>>, + _script_entrypoint_override: Option, _job_id: Uuid, _job_timeout: Option, - _db: &DB, + _conn: &Connection, _mem_peak: &mut i32, _canceled_by: &mut Option, _worker_name: &str, @@ -775,21 +780,28 @@ pub async fn eval_fetch_timeout( ts_expr: String, js_expr: String, args: Option<&Json>>>, + script_entrypoint_override: Option, job_id: Uuid, job_timeout: Option, - db: &DB, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, w_id: &str, load_client: bool, occupation_metrics: &mut OccupancyMetrics, -) -> anyhow::Result> { +) -> windmill_common::error::Result> { use windmill_queue::append_logs; let (sender, mut receiver) = oneshot::channel::(); - let parsed_args = windmill_parser_ts::parse_deno_signature(&ts_expr, true, false, None)?.args; + let parsed_args = windmill_parser_ts::parse_deno_signature( + &ts_expr, + true, + false, + script_entrypoint_override.clone(), + )? + .args; let spread = parsed_args .into_iter() .map(|x| { @@ -817,7 +829,7 @@ pub async fn eval_fetch_timeout( )); } - let db_ = db.clone(); + let conn_ = conn.clone(); let w_id_ = w_id.to_string(); let result_f = tokio::task::spawn_blocking(move || { let ops = vec![op_get_static_args(), op_log()]; @@ -902,7 +914,7 @@ pub async fn eval_fetch_timeout( let future = async { let r = tokio::select! { - r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), load_client, &job_id) => Ok(r), + r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), script_entrypoint_override, load_client, &job_id) => Ok(r), _ = memory_limit_rx.recv() => Err(Error::ExecutionErr("Memory limit reached, killing isolate".to_string())) }; @@ -913,7 +925,7 @@ pub async fn eval_fetch_timeout( "{extra_logs}{}", js_runtime.op_state().borrow().borrow::().s ), - db_, + &conn_, ) .await; @@ -922,16 +934,16 @@ pub async fn eval_fetch_timeout( let r = runtime.block_on(future)?; // tracing::info!("total: {:?}", instant.elapsed()); - r + r as windmill_common::error::Result> }); let res = run_future_with_polling_update_job_poller( job_id, job_timeout, - db, + conn, mem_peak, canceled_by, - async { result_f.await? }, + async { result_f.await.map_err(windmill_common::error::to_anyhow)? }, worker_name, w_id, &mut Some(occupation_metrics), @@ -990,24 +1002,29 @@ async fn eval_fetch( js_runtime: &mut JsRuntime, expr: &str, env_code: Option, + script_entrypoint_override: Option, load_client: bool, job_id: &Uuid, -) -> anyhow::Result> { +) -> windmill_common::error::Result> { if load_client { if let Some(env_code) = env_code.as_ref() { let _ = js_runtime .load_side_es_module_from_code( - &deno_core::resolve_url("file:///windmill.ts")?, + &deno_core::resolve_url("file:///windmill.ts").map_err(error::to_anyhow)?, format!("{env_code}\n{}", WINDMILL_CLIENT.to_string()), ) - .await?; + .await + .map_err(error::to_anyhow)?; } } use anyhow::Context; + use deno_core::error::CoreError; + use windmill_common::{error, worker::to_raw_value}; + let source = format!("{}\n{expr}", env_code.unwrap_or_default()); let _ = js_runtime .load_side_es_module_from_code( - &deno_core::resolve_url("file:///eval.ts")?, - format!("{}\n{expr}", env_code.unwrap_or_default()), + &deno_core::resolve_url("file:///eval.ts").map_err(error::to_anyhow)?, + source.to_string(), ) .await .map_err(|e| { @@ -1016,13 +1033,16 @@ async fn eval_fetch( }) .context("failed to load module")?; + let main_override = script_entrypoint_override.unwrap_or("main".to_string()); let script = js_runtime .execute_script( "", - r#" + format!( + r#" let args = Deno.core.ops.op_get_static_args().map(JSON.parse) -import("file:///eval.ts").then((module) => module.main(...args)).then(JSON.stringify) -"#, +import("file:///eval.ts").then((module) => module.{main_override}(...args)).then(JSON.stringify) +"# + ), ) .map_err(|e| { write_error_expr(expr, &job_id); @@ -1037,15 +1057,50 @@ import("file:///eval.ts").then((module) => module.main(...args)).then(JSON.strin .map_err(|e| { write_error_expr(expr, &job_id); e - }) - .context("native script event loop")?; + }); - let scope = &mut js_runtime.handle_scope(); - let local = v8::Local::new(scope, global); - // Deserialize a `v8` object into a Rust type using `serde_v8`, - // in this case deserialize to a JSON `Value`. - let r = serde_v8::from_v8::>(scope, local)?; - Ok(unsafe_raw(r.unwrap_or_else(|| "null".to_string()))) + match global { + Ok(global) => { + let scope = &mut js_runtime.handle_scope(); + let local = v8::Local::new(scope, global); + // Deserialize a `v8` object into a Rust type using `serde_v8`, + // in this case deserialize to a JSON `Value`. + let r = serde_v8::from_v8::>(scope, local).map_err(error::to_anyhow)?; + Ok(unsafe_raw(r.unwrap_or_else(|| "null".to_string()))) + } + Err(CoreError::Js(e)) => { + let stack_head = e.frames.first().and_then(|f| { + if f.file_name.as_ref().is_some_and(|x| x == "file:///eval.ts") { + Some(format!( + "{}\n", + source + .lines() + .nth((f.line_number.unwrap_or(1)) as usize - 1) + .unwrap_or("") + .to_string() + )) + } else { + None + } + }); + let stack_s = format!( + "{}{}", + stack_head.unwrap_or("".to_string()), + e.stack.unwrap_or("".to_string()) + ); + let stack = if stack_s.is_empty() { + None + } else { + Some(stack_s) + }; + Err(Error::ExecutionRawError(to_raw_value(&serde_json::json!({ + "message": e.message, + "stack": stack, + "name": e.name, + })))) + } + Err(e) => Err(Error::ExecutionErr(e.print_with_cause())), + } } #[cfg(feature = "deno_core")] diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index 9b77ca840f..3dfeba5c42 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -5,12 +5,14 @@ mod mssql_executor; #[cfg(feature = "enterprise")] mod snowflake_executor; +mod agent_workers; #[cfg(feature = "python")] mod ansible_executor; mod bash_executor; -#[cfg(feature = "benchmark")] -pub mod bench; +#[cfg(feature = "java")] +mod java_executor; + mod bun_executor; pub mod common; mod config; @@ -18,34 +20,53 @@ mod csharp_executor; #[cfg(feature = "enterprise")] mod dedicated_worker; mod deno_executor; +#[cfg(feature = "duckdb")] +mod duckdb_executor; mod global_cache; mod go_executor; mod graphql_executor; mod handle_child; -mod job_logger; -mod job_logger_ee; +pub mod job_logger; +#[cfg(feature = "private")] +pub mod job_logger_ee; +mod job_logger_oss; mod js_eval; #[cfg(feature = "mysql")] mod mysql_executor; +#[cfg(feature = "nu")] +mod nu_executor; #[cfg(feature = "oracledb")] mod oracledb_executor; +#[cfg(feature = "private")] +pub mod otel_ee; +mod otel_oss; mod pg_executor; #[cfg(feature = "php")] mod php_executor; #[cfg(feature = "python")] mod python_executor; -mod result_processor; +#[cfg(feature = "python")] +mod python_versions; +pub mod result_processor; #[cfg(feature = "rust")] mod rust_executor; +mod sanitized_sql_params; +mod schema; mod worker; mod worker_flow; mod worker_lockfiles; +mod worker_utils; pub use worker::*; +pub use worker_lockfiles::process_relative_imports; pub use result_processor::handle_job_error; pub use bun_executor::{ - get_common_bun_proc_envs, install_bun_lockfile, prebundle_bun_script, prepare_job_dir, + compute_bundle_local_and_remote_path, get_common_bun_proc_envs, install_bun_lockfile, + prebundle_bun_script, prepare_job_dir, }; pub use deno_executor::generate_deno_lock; + +#[cfg(feature = "python")] +pub use python_versions::{PyV, PyVAlias}; diff --git a/backend/windmill-worker/src/mssql_executor.rs b/backend/windmill-worker/src/mssql_executor.rs index 42d9c3aa8c..7f5ec7c116 100644 --- a/backend/windmill-worker/src/mssql_executor.rs +++ b/backend/windmill-worker/src/mssql_executor.rs @@ -1,5 +1,6 @@ use base64::{engine::general_purpose, Engine as _}; use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; +use futures::StreamExt; use regex::Regex; use serde::Deserialize; use serde_json::value::RawValue; @@ -8,24 +9,42 @@ use tiberius::{AuthMethod, Client, ColumnData, Config, FromSqlOwned, Query, Row, use tokio::net::TcpStream; use tokio_util::compat::TokioAsyncWriteCompatExt; use uuid::Uuid; -use windmill_common::error::{self, Error}; -use windmill_common::worker::to_raw_value; -use windmill_common::{error::to_anyhow, jobs::QueuedJob}; -use windmill_parser_sql::{parse_db_resource, parse_mssql_sig}; +use windmill_common::s3_helpers::convert_json_line_stream; +use windmill_common::{ + error::{self, to_anyhow, Error}, + utils::empty_as_none, + worker::{to_raw_value, Connection}, +}; +use windmill_parser_sql::{parse_db_resource, parse_mssql_sig, parse_s3_mode}; +use windmill_queue::MiniPulledJob; use windmill_queue::{append_logs, CanceledBy}; -use crate::common::{build_args_values, OccupancyMetrics}; +use crate::common::{build_args_values, s3_mode_args_to_worker_data, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; -use crate::AuthedClientBackgroundTask; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; +use windmill_common::client::AuthedClient; + +use serde::Deserializer; #[derive(Deserialize)] struct MssqlDatabase { host: String, - user: String, - password: String, + user: Option, + password: Option, port: Option, dbname: String, instance_name: Option, + #[serde(default, deserialize_with = "deserialize_aad_token")] + aad_token: Option, + trust_cert: Option, + #[serde(default, deserialize_with = "empty_as_none")] + ca_cert: Option, +} + +#[derive(Debug, Deserialize)] +struct AadToken { + #[serde(default, deserialize_with = "empty_as_none")] + token: Option, } lazy_static::lazy_static! { @@ -33,24 +52,24 @@ lazy_static::lazy_static! { } pub async fn do_mssql( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, occupancy_metrics: &mut OccupancyMetrics, + job_dir: &str, ) -> error::Result> { - let mssql_args = build_args_values(job, client, db).await?; + let mssql_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { Some( client - .get_authed() - .await .get_resource_value_interpolated::( &inline_db_res_path, Some(job.id.to_string()), @@ -90,12 +109,42 @@ pub async fn do_mssql( if readonly_intent { let logs = format!("\nSetting ApplicationIntent to ReadOnly"); - append_logs(&job.id, &job.workspace_id, logs, db).await; + append_logs(&job.id, &job.workspace_id, logs, conn).await; } - // Using SQL Server authentication. - config.authentication(AuthMethod::sql_server(database.user, database.password)); - config.trust_cert(); // on production, it is not a good idea to do this + // Handle authentication based on available credentials + if let Some(token_value) = &database.aad_token { + if let Some(token) = &token_value.token { + config.authentication(AuthMethod::aad_token(token)); + } else { + return Err(Error::BadRequest( + "Invalid AAD token format - expected { token: string }".to_string(), + )); + } + } else if let (Some(user), Some(password)) = (&database.user, &database.password) { + config.authentication(AuthMethod::sql_server(user.clone(), password.clone())); + } else { + return Err(Error::BadRequest( + "Neither AAD token nor username/password credentials are set".to_string(), + )); + } + + // Handle certificate trust configuration + if database.trust_cert.unwrap_or(true) { + // If trust_cert is true, ignore ca_cert and trust any certificate + config.trust_cert(); + tracing::info!("MSSQL: disabling certificate validation"); + } else if let Some(ca_cert) = &database.ca_cert { + // Only use ca_cert if trust_cert is false + let cert_path = format!("{}/ca_cert.pem", job_dir); + + std::fs::write(&cert_path, ca_cert) + .map_err(|e| Error::ExecutionErr(format!("Failed to write CA certificate: {}", e)))?; + + // Use the CA certificate for trust + config.trust_cert_ca(cert_path); + tracing::info!("MSSQL: using provided CA certificate for trust"); + } let tcp = if use_instance_name { TcpStream::connect_named(&config).await.map_err(to_anyhow)? // named instance @@ -131,8 +180,14 @@ pub async fn do_mssql( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = + &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &mssql_args)?; + let mut prepared_query = Query::new(query.to_owned()); for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_v = mssql_args .get(&arg.name) @@ -145,34 +200,49 @@ pub async fn do_mssql( // A response to a query is a stream of data, that must be // polled to the end before querying again. Using streams allows // fetching data in an asynchronous manner, if needed. - let stream = prepared_query.query(&mut client).await.map_err(to_anyhow)?; - let results = stream.into_results().await.map_err(to_anyhow)?; - let len = results.len(); - let mut json_results = vec![]; - for (i, statement_result) in results.into_iter().enumerate() { - if annotations.return_last_result && i < len - 1 { - continue; - } - let mut json_rows = vec![]; - for row in statement_result { - let row = row_to_json(row)?; - json_rows.push(row); - } - json_results.push(json_rows); - } + if let Some(s3) = s3 { + let rows_stream = async_stream::stream! { + let mut stream = prepared_query.query(&mut client).await.map_err(to_anyhow)?.into_row_stream().map(|row| { + row_to_json(row.map_err(to_anyhow)?).map_err(to_anyhow) + }); + while let Some(row) = stream.next().await { + yield row; + } + }; - if annotations.return_last_result && json_results.len() > 0 { - Ok(to_raw_value(&json_results.pop().unwrap())) + let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + + Ok(to_raw_value(&s3.to_return_s3_obj())) } else { - Ok(to_raw_value(&json_results)) + let stream = prepared_query.query(&mut client).await.map_err(to_anyhow)?; + let results = stream.into_results().await.map_err(to_anyhow)?; + let len = results.len(); + let mut json_results = vec![]; + for (i, statement_result) in results.into_iter().enumerate() { + if annotations.return_last_result && i < len - 1 { + continue; + } + let mut json_rows = vec![]; + for row in statement_result { + let row = row_to_json(row)?; + json_rows.push(row); + } + json_results.push(json_rows); + } + if annotations.return_last_result && json_results.len() > 0 { + Ok(to_raw_value(&json_results.pop().unwrap())) + } else { + Ok(to_raw_value(&json_results)) + } } }; let raw_result = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, result_f, @@ -328,3 +398,15 @@ fn sql_to_json_value(val: ColumnData) -> Result { ), } } + +fn deserialize_aad_token<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let result = AadToken::deserialize(deserializer); + + match result { + Ok(token) if token.token.is_some() => Ok(Some(token)), + _ => Ok(None), + } +} diff --git a/backend/windmill-worker/src/mysql_executor.rs b/backend/windmill-worker/src/mysql_executor.rs index d733901f93..04403e4e08 100644 --- a/backend/windmill-worker/src/mysql_executor.rs +++ b/backend/windmill-worker/src/mysql_executor.rs @@ -1,49 +1,54 @@ use std::{collections::HashMap, sync::Arc}; +use anyhow::anyhow; use base64::Engine; -use futures::{future::BoxFuture, FutureExt}; +use futures::{future::BoxFuture, FutureExt, StreamExt}; use itertools::Itertools; use mysql_async::{ consts::ColumnType, prelude::*, FromValueError, OptsBuilder, Params, Row, SslOpts, }; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; -use sqlx::types::Json; +use std::str::FromStr; use tokio::sync::Mutex; use windmill_common::{ + client::AuthedClient, error::{to_anyhow, Error}, - jobs::QueuedJob, - worker::to_raw_value, + s3_helpers::convert_json_line_stream, + worker::{to_raw_value, Connection}, }; use windmill_parser_sql::{ - parse_db_resource, parse_mysql_sig, parse_sql_blocks, parse_sql_statement_named_params, - RE_ARG_MYSQL_NAMED, + parse_db_resource, parse_mysql_sig, parse_s3_mode, parse_sql_blocks, + parse_sql_statement_named_params, RE_ARG_MYSQL_NAMED, }; use windmill_queue::CanceledBy; +use windmill_queue::MiniPulledJob; use crate::{ - common::{build_args_map, OccupancyMetrics}, + common::{build_args_values, s3_mode_args_to_worker_data, OccupancyMetrics, S3ModeWorkerData}, handle_child::run_future_with_polling_update_job_poller, - AuthedClientBackgroundTask, + sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args, }; #[derive(Deserialize)] -struct MysqlDatabase { - host: String, - user: Option, - password: Option, - port: Option, - database: String, - ssl: Option, +pub struct MysqlDatabase { + pub host: String, + pub user: Option, + pub password: Option, + pub port: Option, + pub database: String, + pub ssl: Option, } -pub fn do_mysql_inner<'a>( +fn do_mysql_inner<'a>( query: &'a str, all_statement_values: &Params, conn: Arc>, column_order: Option<&'a mut Option>>, skip_collect: bool, -) -> windmill_common::error::Result>>> { + s3: Option, +) -> windmill_common::error::Result>>> { let param_names = parse_sql_statement_named_params(query, ':') .into_iter() .map(|x| x.into_bytes()) @@ -69,6 +74,38 @@ pub fn do_mysql_inner<'a>( .map_err(to_anyhow)?; Ok(to_raw_value(&Value::Array(vec![]))) + } else if let Some(ref s3) = s3 { + let query = query.to_string(); + let rows_stream = async_stream::stream! { + let mut conn = conn.lock().await; + let mut result = match conn.exec_iter(query, statement_values).await.map_err(to_anyhow) { + Ok(result) => result, + Err(e) => { + yield Err(anyhow!("Error executing query: {:?}", e)); + return; + } + }; + loop { + let row = result.next().await; + match row { + Ok(Some(row)) => { + yield Ok(convert_row_to_value(row)); + } + Ok(None) => { + break; + } + Err(e) => { + yield Err(anyhow!("Error fetching row: {:?}", e)); + return; + } + } + } + }; + + let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + + Ok(to_raw_value(&s3.to_return_s3_obj())) } else { let rows: Vec = conn .lock() @@ -103,46 +140,36 @@ pub fn do_mysql_inner<'a>( } pub async fn do_mysql( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, column_order: &mut Option>, occupancy_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let args = build_args_map(job, client, db).await?.map(Json); - let job_args = if args.is_some() { - args.as_ref() - } else { - job.args.as_ref() - }; + let job_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { - let val = client - .get_authed() - .await - .get_resource_value_interpolated::( - &inline_db_res_path, - Some(job.id.to_string()), - ) - .await?; - - let as_raw = serde_json::from_value(val).map_err(|e| { - Error::internal_err(format!("Error while parsing inline resource: {e:#}")) - })?; - - Some(as_raw) + Some( + client + .get_resource_value_interpolated::( + &inline_db_res_path, + Some(job.id.to_string()), + ) + .await?, + ) } else { - job_args.and_then(|x| x.get("database").cloned()) + job_args.get("database").cloned() }; let database = if let Some(db) = db_arg { - serde_json::from_str::(db.get()) + serde_json::from_value::(db) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { return Err(Error::BadRequest("Missing database argument".to_string())); @@ -171,6 +198,8 @@ pub async fn do_mysql( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + let using_named_params = RE_ARG_MYSQL_NAMED.captures_iter(query).count() > 0; let mut statement_values: Params = match using_named_params { @@ -178,18 +207,17 @@ pub async fn do_mysql( false => Params::Positional(vec![]), }; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "text".to_string()); let arg_n = arg.name.clone(); let mysql_v = match job_args - .and_then(|x| { - x.get(arg.name.as_str()) - .map(|x| serde_json::from_str::(x.get()).ok()) - }) - .flatten() - .unwrap_or_else(|| json!(null)) + .get(arg.name.as_str()) + .unwrap_or_else(|| &json!(null)) { Value::Null => mysql_async::Value::NULL, - Value::Bool(b) => mysql_async::Value::Int(if b { 1 } else { 0 }), + Value::Bool(b) => mysql_async::Value::Int(if *b { 1 } else { 0 }), Value::String(s) if arg_t == "timestamp" || arg_t == "datetime" @@ -244,8 +272,8 @@ pub async fn do_mysql( } let pool = mysql_async::Pool::new(opts); - let conn = pool.get_conn().await.map_err(to_anyhow)?; - let conn_a = Arc::new(Mutex::new(conn)); + let mysql_conn = pool.get_conn().await.map_err(to_anyhow)?; + let conn_a = Arc::new(Mutex::new(mysql_conn)); let queries = parse_sql_blocks(query); @@ -260,6 +288,7 @@ pub async fn do_mysql( conn_a.clone(), None, annotations.return_last_result && i < queries.len() - 1, + s3.clone(), ) }) .collect::>>()?; @@ -285,13 +314,14 @@ pub async fn do_mysql( conn_a.clone(), Some(column_order), false, + s3, )? }; let result = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, result_f, @@ -313,26 +343,38 @@ pub async fn do_mysql( return Ok(raw_result); } +// 2023-12-01T16:18:00.000Z +static DATE_REGEX_TZ: Lazy = Lazy::new(|| { + regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d+)Z").unwrap() +}); +// 2025-04-21 10:08:00 +static DATE_REGEX: Lazy = + Lazy::new(|| regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})").unwrap()); + fn string_date_to_mysql_date(s: &str) -> mysql_async::Value { - // 2023-12-01T16:18:00.000Z - let re = regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d+)Z").unwrap(); - let caps = re.captures(s); + let caps = DATE_REGEX_TZ.captures(s).or_else(|| DATE_REGEX.captures(s)); if let Some(caps) = caps { mysql_async::Value::Date( - caps.get(1).unwrap().as_str().parse().unwrap_or_default(), - caps.get(2).unwrap().as_str().parse().unwrap_or_default(), - caps.get(3).unwrap().as_str().parse().unwrap_or_default(), - caps.get(4).unwrap().as_str().parse().unwrap_or_default(), - caps.get(5).unwrap().as_str().parse().unwrap_or_default(), - caps.get(6).unwrap().as_str().parse().unwrap_or_default(), - caps.get(7).unwrap().as_str().parse().unwrap_or_default(), + get_capture_by_index(&caps, 1), + get_capture_by_index(&caps, 2), + get_capture_by_index(&caps, 3), + get_capture_by_index(&caps, 4), + get_capture_by_index(&caps, 5), + get_capture_by_index(&caps, 6), + get_capture_by_index(&caps, 7), ) } else { mysql_async::Value::Date(0, 0, 0, 0, 0, 0, 0) } } +fn get_capture_by_index(caps: ®ex::Captures, n: usize) -> T { + caps.get(n) + .and_then(|s| s.as_str().parse::().ok()) + .unwrap_or_default() +} + fn convert_row_to_value(row: Row) -> serde_json::Value { let mut map = serde_json::Map::new(); diff --git a/backend/windmill-worker/src/nu_executor.rs b/backend/windmill-worker/src/nu_executor.rs new file mode 100644 index 0000000000..c31d979c27 --- /dev/null +++ b/backend/windmill-worker/src/nu_executor.rs @@ -0,0 +1,363 @@ +use std::{collections::HashMap, process::Stdio}; + +use itertools::Itertools; +use serde_json::value::RawValue; +use tokio::{fs::File, io::AsyncWriteExt, process::Command}; +use windmill_common::{ + error::Error, + worker::{write_file, Connection}, +}; +use windmill_parser::Arg; +use windmill_parser_nu::parse_nu_signature; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; + +use crate::{ + common::{ + create_args_and_out_file, get_reserved_variables, read_result, start_child_process, + OccupancyMetrics, + }, + handle_child, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, +}; +use windmill_common::client::AuthedClient; + + +const NSJAIL_CONFIG_RUN_NU_CONTENT: &str = include_str!("../nsjail/run.nu.config.proto"); +lazy_static::lazy_static! { + static ref NU_PATH: String = std::env::var("NU_PATH").unwrap_or_else(|_| "/usr/bin/nu".to_string()); + // TODO(v1): + // static ref PLUGIN_USE_RE: Regex = Regex::new(r#"(?:plugin use )(?.*)"#).unwrap(); +} + +// TODO: Can be generalized and used for other handlers +#[allow(dead_code)] +pub(crate) struct JobHandlerInput<'a> { + pub base_internal_url: &'a str, + pub canceled_by: &'a mut Option, + pub client: &'a AuthedClient, + pub parent_runnable_path: Option, + pub conn: &'a Connection, + pub envs: HashMap, + pub inner_content: &'a str, + pub job: &'a MiniPulledJob, + pub job_dir: &'a str, + pub mem_peak: &'a mut i32, + pub occupancy_metrics: &'a mut OccupancyMetrics, + pub requirements_o: Option<&'a String>, + pub shared_mount: &'a str, + pub worker_name: &'a str, +} + +pub async fn handle_nu_job<'a>(mut args: JobHandlerInput<'a>) -> Result, Error> { + // TODO(v1): + // --- Handle plugins --- + // let plugins = get_plugins(&mut args).await?; + // TODO(v1): + // --- Handle imports --- + // TODO(v1): + // --- Handle relative --- + // --- Wrap and write to fs --- + { + create_args_and_out_file(&args.client, args.job, args.job_dir, args.conn).await?; + File::create(format!("{}/main.nu", args.job_dir)) + .await? + .write_all(&wrap(args.inner_content)?.into_bytes()) + .await?; + } + // --- Execute --- + { + run(&mut args).await?; + } + // --- Retrieve results --- + { + read_result(&args.job_dir).await + } +} + +// async fn get_plugins<'a>( +// JobHandlerInput { +// occupancy_metrics, +// mem_peak, +// canceled_by, +// worker_name, +// job, +// db, +// inner_content, +// .. +// }: &mut JobHandlerInput<'a>, +// ) -> Result, Error> { +// let plugins_dir = concatcp!(NU_CACHE_DIR, "/plugins"); +// let nu_version = from_utf8_mut( +// Command::new(NU_PATH.as_str()) +// .arg("--version") +// .output() +// .await? +// .stdout +// .as_mut_slice(), +// ) +// .map_err(|e| windmill_common::error::Error::ExecutionErr(e.to_string()))? +// .to_owned(); + +// let plugins = parse_plugin_use(inner_content); + +// for plugin in &plugins { +// let mut run_cmd = Command::new(CARGO_PATH.as_str()); +// // cargo install nu_plugin_query --version (nu --version); plugin add ~/.cargo/bin/nu_plugin_query +// run_cmd +// // TODO: make it work with env_clear +// // .env_clear() +// .args(&[ +// "install", +// "--root", +// plugins_dir, +// "--locked", +// &format!("nu_plugin_{plugin}"), +// "--version", +// &nu_version, +// ]) +// .stdout(Stdio::piped()) +// .stderr(Stdio::piped()); + +// #[cfg(windows)] +// nsjail_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); +// let child = start_child_process(run_cmd, "cargo").await?; +// // handle_child::handle_child( +// // &job.id, +// // db, +// // mem_peak, +// // canceled_by, +// // child, +// // !*DISABLE_NSJAIL, +// // worker_name, +// // &job.workspace_id, +// // "cargo", +// // job.timeout, +// // false, +// // &mut Some(occupancy_metrics), +// // ) +// // .await?; +// } +// Ok(plugins) +// } + +// fn parse_plugin_use(inner_content: &str) -> Vec<&str> { +// let mut plugins = vec![]; +// // TODO: Ignore plugins with # in the beginning +// for cap in PLUGIN_USE_RE.captures_iter(inner_content).into_iter() { +// if let Some(mat) = cap.name("plugin") { +// plugins.push(mat.as_str()); +// } +// } +// plugins +// } + +/// Wraps content script +/// that upon execution reads args.json (which are piped and transformed from previous flow step or top level inputs) +/// Also wrapper takes output of program and serializes to result.json (Which windmill will know how to use later) +fn wrap(inner_content: &str) -> Result { + let sig = parse_nu_signature(inner_content)?; + let spread = sig + .args + .clone() + .into_iter() + .map(|Arg { name, typ, has_default, .. }| { + // Apply additional input transformation + let transformation = format!( + "| if $in != null {{ {} }} else {{ $in }}", + match typ { + // JSON converts X.0 to X and nu can't coerce type automatically + windmill_parser::Typ::Datetime => "into datetime", + windmill_parser::Typ::Bytes => "into binary", + windmill_parser::Typ::Float => "into float", + // Ident + _ => "$in", + } + ); + let nullguard = if has_default || matches!(typ, windmill_parser::Typ::Unknown) { + "".to_owned() + } else { + format!("| nullguard {name}") + }; + format!("\n\t\t\t($parsed_args.{name}? {nullguard} {transformation}) ",) + }) + .collect_vec() + .join(" "); + Ok( + r#" +$env.config.table.mode = 'basic' + +def nullguard [ name: string ] { + if ($in == null) { + panic $"argument `($name)` of main function can't be null" + } + $in +} + +# TODO: Probably needs rework in order for LSP to work +def get_variable [ pat ] { + let addr = $"($env.BASE_INTERNAL_URL)/api/w/($env.WM_WORKSPACE)/variables/get_value/($pat)" ; + http get -H ["Authorization", $"Bearer ($env.WM_TOKEN)"] $addr | return $in +} +def get_resource [ pat ] { + let addr = $"($env.BASE_INTERNAL_URL)/api/w/($env.WM_WORKSPACE)/resources/get_value_interpolated/($pat)" ; + http get -H ["Authorization", $"Bearer ($env.WM_TOKEN)"] $addr | return $in +} + +def 'main --wrapped' [] { + let parsed_args = open args.json + (main SPREAD + ) | to json | save -f result.json +} + +INNER_CONTENT + "# + .replace("INNER_CONTENT", inner_content) + .replace("SPREAD", &spread), // .replace("TRANSFORM", transform) + ) +} + +async fn run<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + conn, + job_dir, + shared_mount, + client, + parent_runnable_path, + envs, + base_internal_url, + .. + }: &mut JobHandlerInput<'a>, + // plugins: Vec<&'a str>, +) -> Result<(), Error> { + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?; + let child = if !cfg!(windows) && !*DISABLE_NSJAIL { + append_logs( + &job.id, + &job.workspace_id, + format!("\n\n--- ISOLATED NU CODE EXECUTION ---\n"), + conn, + ) + .await; + + write_file( + job_dir, + "run.config.proto", + &NSJAIL_CONFIG_RUN_NU_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{NU_PATH}", &NU_PATH) + .replace("{SHARED_MOUNT}", &shared_mount) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), + )?; + let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); + nsjail_cmd + .env_clear() + .current_dir(job_dir) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(vec![ + "--config", + "run.config.proto", + "--", + NU_PATH.as_str(), + "/tmp/main.nu", + "--wrapped", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + } else { + append_logs( + &job.id, + &job.workspace_id, + format!("\n\n--- NU CODE EXECUTION ---\n"), + &conn, + ) + .await; + + // let plugin_registry = format!("{job_dir}/plugin-registry"); + // File::create(&plugin_registry).await?; + // + let mut cmd = Command::new(if cfg!(windows) { + "nu" + } else { + NU_PATH.as_str() + }); + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(envs) + .envs(reserved_variables) + .envs(PROXY_ENVS.clone()) + .args(&[ + "main.nu", + "--wrapped", + // TODO(v1): + // "--plugins", + // &format!( + // "[{}]", + // plugins + // .into_iter() + // .map(|pl| format!("{NU_CACHE_DIR}/plugins/bin/nu_plugin_{pl}")) + // .collect_vec() + // .join(",") + // ), + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "nu").await? + }; + handle_child::handle_child( + &job.id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "nu", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await +} +// #[cfg(test)] +// mod test { +// use super::parse_plugin_use; + +// #[test] +// fn test_nu_plugin_use() { +// let content = r#" +// plugin use foo +// plugin use bar +// plugin use baz +// plugin use meh +// "#; +// assert_eq!( +// vec!["foo", "bar", "baz", "meh"], // +// parse_plugin_use(content) +// ); +// } +// } diff --git a/backend/windmill-worker/src/oracledb_executor.rs b/backend/windmill-worker/src/oracledb_executor.rs index e6292181b2..244cbb1958 100644 --- a/backend/windmill-worker/src/oracledb_executor.rs +++ b/backend/windmill-worker/src/oracledb_executor.rs @@ -1,29 +1,35 @@ use anyhow::anyhow; use chrono::Utc; -use std::{collections::HashMap, str::FromStr, sync::Arc}; +use std::{collections::HashMap, str::FromStr, sync::Arc, vec}; use windmill_parser::Arg; -use futures::{future::BoxFuture, FutureExt}; +use futures::{future::BoxFuture, FutureExt, StreamExt}; use itertools::Itertools; use oracle::sql_type::{InnerValue, OracleType, ToSql}; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; -use sqlx::types::Json; use windmill_common::{ error::{to_anyhow, Error}, - jobs::QueuedJob, - worker::to_raw_value, + s3_helpers::convert_json_line_stream, + worker::{to_raw_value, Connection}, }; +use windmill_queue::MiniPulledJob; + use windmill_parser_sql::{ - parse_db_resource, parse_oracledb_sig, parse_sql_blocks, parse_sql_statement_named_params, + parse_db_resource, parse_oracledb_sig, parse_s3_mode, parse_sql_blocks, + parse_sql_statement_named_params, }; use windmill_queue::CanceledBy; use crate::{ - common::{build_args_map, check_executor_binary_exists, OccupancyMetrics}, + common::{ + build_args_values, check_executor_binary_exists, s3_mode_args_to_worker_data, + OccupancyMetrics, S3ModeWorkerData, + }, handle_child::run_future_with_polling_update_job_poller, - AuthedClientBackgroundTask, + sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args }; +use windmill_common::client::AuthedClient; #[derive(Deserialize)] struct OracleDatabase { @@ -42,7 +48,8 @@ pub fn do_oracledb_inner<'a>( conn: Arc>, column_order: Option<&'a mut Option>>, skip_collect: bool, -) -> windmill_common::error::Result>>> { + s3: Option, +) -> windmill_common::error::Result>>> { let qw = query.trim_end_matches(';').to_string(); let result_f = async move { @@ -80,55 +87,90 @@ pub fn do_oracledb_inner<'a>( Ok(to_raw_value(&Value::Array(vec![]))) } else { - let rows = tokio::task::spawn_blocking(move || { - let params2: Vec<(&str, &dyn ToSql)> = params - .iter() - .filter(|(k, _)| param_names.contains(&k.clone().into_bytes())) - .map(|(key, val)| (key.as_str(), &**val as &dyn ToSql)) - .collect(); + // We use an mpsc because we need an async stream for s3 mode. However since everything is sync + // in rust-oracle, I assumed that calling ResultSet::next() is blocking when it has to refetch. + let (tx, rx) = tokio::sync::mpsc::channel::>(1000); + let (column_order_oneshot_tx, column_order_oneshot_rx) = + tokio::sync::oneshot::channel::>>(); + let mut column_order_oneshot_tx = Some(column_order_oneshot_tx); + let rows_stream = tokio_stream::wrappers::ReceiverStream::new(rx); + tokio::task::spawn_blocking(move || { + let result = (|| { + let tx = tx.clone(); + let params2: Vec<(&str, &dyn ToSql)> = params + .iter() + .filter(|(k, _)| param_names.contains(&k.clone().into_bytes())) + .map(|(key, val)| (key.as_str(), &**val as &dyn ToSql)) + .collect(); - let c = conn.lock()?; - let mut stmt = c.statement(&qw).build()?; + let c = conn.lock()?; + let mut stmt = c.statement(&qw).build()?; - let rows = match stmt.statement_type() { - oracle::StatementType::Select => { - let result_rows = stmt.query_named(¶ms2)?; - let rows: Vec = - result_rows.into_iter().filter_map(Result::ok).collect_vec(); - rows - } - _ => { - stmt.execute_named(¶ms2)?; - c.commit()?; - vec![] - } - }; + match stmt.statement_type() { + oracle::StatementType::Select => { + let mut result_rows = stmt.query_named(¶ms2)?.enumerate(); + while let Some((i, row)) = result_rows.next() { + match row { + Ok(row) => { + // If first row, infer column order and send it to the channel + if i == 0 { + let col_order: Vec = row + .column_info() + .iter() + .map(|x| x.name().to_string()) + .collect::>(); + let _ = column_order_oneshot_tx + .take() + .unwrap() + .send(Some(col_order)); + } - oracle::Result::Ok(rows) - }) - .await - .map_err(to_anyhow)? - .map_err(to_anyhow)?; + // called in a spawn_blocking synchronous context, unwrap won't panic + tx.blocking_send(Ok(convert_row_to_value(row))).unwrap() + } + Err(e) => { + tx.blocking_send(Err(e)).unwrap(); + break; + } + } + } + } + _ => { + stmt.execute_named(¶ms2)?; + c.commit()?; + } + }; + drop(column_order_oneshot_tx); + Ok::<_, oracle::Error>(()) + })(); + match result { + Ok(_) => {} + Err(e) => tx.blocking_send(Err(e)).unwrap(), + } + // all instances of tx should be dropped here + }); - if let Some(column_order) = column_order { - *column_order = Some( - rows.first() - .map(|x| { - x.column_info() - .iter() - .map(|x| x.name().to_string()) - .collect::>() - }) - .unwrap_or_default(), - ); + if let Ok(Some(col_order)) = column_order_oneshot_rx.await { + if let Some(column_order) = column_order { + *column_order = Some(col_order); + } } - Ok(to_raw_value( - &rows - .into_iter() - .map(|x| convert_row_to_value(x)) - .collect::>(), - )) + if let Some(s3) = s3 { + let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + return Ok(to_raw_value(&s3.to_return_s3_obj())); + } else { + let rows: Vec<_> = rows_stream.collect().await; + Ok(to_raw_value( + &rows + .into_iter() + .collect::, _>>() + .map_err(to_anyhow)? + .into_iter() + .collect::>(), + )) + } } }; @@ -220,24 +262,24 @@ fn convert_oracledb_value_to_json(v: &oracle::SqlValue, c: &OracleType) -> serde fn get_statement_values( sig: Vec, - job_args: Option<&Json>>>, + job_args: &HashMap, + args_to_skip: &Vec, ) -> (Vec<(String, Box)>, Vec) { let mut statement_values = vec![]; let mut errors = vec![]; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "text".to_string()); let arg_n = arg.name.clone(); let oracle_v: Box = match job_args - .and_then(|x| { - x.get(arg.name.as_str()) - .map(|x| serde_json::from_str::(x.get()).ok()) - }) - .flatten() - .unwrap_or_else(|| json!(null)) + .get(arg.name.as_str()) + .unwrap_or_else(|| &json!(null)) { // Value::Null => todo!(), - Value::Bool(b) => Box::new(b), + Value::Bool(b) => Box::new(*b), Value::String(s) if arg_t == "timestamp" || arg_t == "datetime" @@ -247,10 +289,10 @@ fn get_statement_values( if let Ok(d) = chrono::DateTime::::from_str(s.as_str()) { Box::new(d) } else { - Box::new(s) + Box::new(s.clone()) } } - Value::String(s) => Box::new(s), + Value::String(s) => Box::new(s.clone()), Value::Number(n) if n.is_i64() && (arg_t == "int" @@ -292,10 +334,10 @@ fn get_statement_values( } pub async fn do_oracledb( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, @@ -308,36 +350,26 @@ pub async fn do_oracledb( "Oracle Database", )?; - let args = build_args_map(job, client, db).await?.map(Json); - let job_args = if args.is_some() { - args.as_ref() - } else { - job.args.as_ref() - }; + let job_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { - let val = client - .get_authed() - .await - .get_resource_value_interpolated::( - &inline_db_res_path, - Some(job.id.to_string()), - ) - .await?; - - let as_raw = serde_json::from_value(val).map_err(|e| { - Error::internal_err(format!("Error while parsing inline resource: {e:#}")) - })?; - - Some(as_raw) + Some( + client + .get_resource_value_interpolated::( + &inline_db_res_path, + Some(job.id.to_string()), + ) + .await?, + ) } else { - job_args.and_then(|x| x.get("database").cloned()) + job_args.get("database").cloned() }; let database = if let Some(db) = db_arg { - serde_json::from_str::(db.get()) + serde_json::from_value::(db) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { return Err(Error::BadRequest("Missing database argument".to_string())); @@ -349,7 +381,9 @@ pub async fn do_oracledb( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; - let (statement_values, errors) = get_statement_values(sig.clone(), job_args); + let (query, args_to_skip) = sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + + let (statement_values, errors) = get_statement_values(sig.clone(), &job_args, &args_to_skip); if !errors.is_empty() { return Err(Error::ExecutionErr(errors.join("\n"))); @@ -362,28 +396,29 @@ pub async fn do_oracledb( .init(); } - let conn = tokio::task::spawn_blocking(|| { + let oracle_conn = tokio::task::spawn_blocking(|| { oracle::Connection::connect(database.user, database.password, database.database) .map_err(|e| Error::ExecutionErr(e.to_string())) }) .await .map_err(to_anyhow)??; - let conn_a = Arc::new(std::sync::Mutex::new(conn)); + let conn_a = Arc::new(std::sync::Mutex::new(oracle_conn)); - let queries = parse_sql_blocks(query); + let queries = parse_sql_blocks(&query); let result_f = if queries.len() > 1 { let f = async { let mut res: Vec> = vec![]; for (i, q) in queries.iter().enumerate() { - let (vals, _) = get_statement_values(sig.clone(), job_args); + let (vals, _) = get_statement_values(sig.clone(), &job_args, &args_to_skip); let r = do_oracledb_inner( q, vals, conn_a.clone(), None, annotations.return_last_result && i < queries.len() - 1, + s3.clone(), )? .await?; res.push(r); @@ -398,13 +433,20 @@ pub async fn do_oracledb( f.boxed() } else { - do_oracledb_inner(query, statement_values, conn_a, Some(column_order), false)? + do_oracledb_inner( + &query, + statement_values, + conn_a, + Some(column_order), + false, + s3, + )? }; let result = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, result_f, diff --git a/backend/windmill-worker/src/otel_oss.rs b/backend/windmill-worker/src/otel_oss.rs new file mode 100644 index 0000000000..2f65c534b4 --- /dev/null +++ b/backend/windmill-worker/src/otel_oss.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::otel_ee::*; + +#[cfg(not(feature = "private"))] +use windmill_queue::MiniPulledJob; + +#[cfg(not(feature = "private"))] +pub fn add_root_flow_job_to_otlp(_queued_job: &MiniPulledJob, _success: bool) {} diff --git a/backend/windmill-worker/src/pg_executor.rs b/backend/windmill-worker/src/pg_executor.rs index 2ad6eb4038..2c7fe123dc 100644 --- a/backend/windmill-worker/src/pg_executor.rs +++ b/backend/windmill-worker/src/pg_executor.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::net::IpAddr; -use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -8,7 +8,7 @@ use anyhow::Context; use base64::{engine, Engine as _}; use chrono::Utc; use futures::future::BoxFuture; -use futures::{FutureExt, TryStreamExt}; +use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use itertools::Itertools; use native_tls::{Certificate, TlsConnector}; use postgres_native_tls::MakeTlsConnector; @@ -17,7 +17,7 @@ use serde::Deserialize; use serde_json::value::RawValue; use serde_json::Map; use serde_json::Value; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, RwLock}; use tokio_postgres::Client; use tokio_postgres::{types::ToSql, NoTls, Row}; use tokio_postgres::{ @@ -25,38 +25,44 @@ use tokio_postgres::{ Column, }; use uuid::Uuid; +use windmill_common::error::to_anyhow; use windmill_common::error::{self, Error}; -use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; -use windmill_common::{error::to_anyhow, jobs::QueuedJob}; +use windmill_common::s3_helpers::convert_json_line_stream; +use windmill_common::worker::{to_raw_value, Connection, CLOUD_HOSTED}; use windmill_parser::{Arg, Typ}; use windmill_parser_sql::{ - parse_db_resource, parse_pg_statement_arg_indices, parse_pgsql_sig, parse_sql_blocks, + parse_db_resource, parse_pg_statement_arg_indices, parse_pgsql_sig, parse_s3_mode, + parse_sql_blocks, }; -use windmill_queue::CanceledBy; +use windmill_queue::{CanceledBy, MiniPulledJob}; -use crate::common::{build_args_values, sizeof_val, OccupancyMetrics}; +use crate::common::{ + build_args_values, s3_mode_args_to_worker_data, sizeof_val, OccupancyMetrics, S3ModeWorkerData, +}; use crate::handle_child::run_future_with_polling_update_job_poller; -use crate::{AuthedClientBackgroundTask, MAX_RESULT_SIZE}; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; +use crate::MAX_RESULT_SIZE; use bytes::Buf; use lazy_static::lazy_static; use urlencoding::encode; - +use windmill_common::client::AuthedClient; #[derive(Deserialize)] -struct PgDatabase { - host: String, - user: Option, - password: Option, - port: Option, - sslmode: Option, - dbname: String, - root_certificate_pem: Option, +pub struct PgDatabase { + pub host: String, + pub user: Option, + pub password: Option, + pub port: Option, + pub sslmode: Option, + pub dbname: String, + pub root_certificate_pem: Option, } lazy_static! { pub static ref CONNECTION_CACHE: Arc>> = Arc::new(Mutex::new(None)); + pub static ref CONNECTION_COUNTER: Arc>> = + Arc::new(RwLock::new(HashMap::new())); pub static ref LAST_QUERY: AtomicU64 = AtomicU64::new(0); - pub static ref RUNNING: AtomicBool = AtomicBool::new(false); } fn do_postgresql_inner<'a>( @@ -66,7 +72,8 @@ fn do_postgresql_inner<'a>( column_order: Option<&'a mut Option>>, siz: &'a AtomicUsize, skip_collect: bool, -) -> error::Result>>> { + s3: Option, +) -> error::Result>>> { let mut query_params = vec![]; let arg_indices = parse_pg_statement_arg_indices(&query); @@ -104,6 +111,20 @@ fn do_postgresql_inner<'a>( .execute_raw(&query, query_params) .await .map_err(to_anyhow)?; + } else if let Some(ref s3) = s3 { + let rows_stream = client + .query_raw(&query, query_params) + .map_err(to_anyhow) + .await? + .map_err(to_anyhow) + .map(|row_result| { + row_result.and_then(|row| postgres_row_to_json_value(row).map_err(to_anyhow)) + }); + + let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + + return Ok(to_raw_value(&s3.to_return_s3_obj())); } else { let rows = client .query_raw(&query, query_params) @@ -134,17 +155,17 @@ fn do_postgresql_inner<'a>( if *CLOUD_HOSTED { let siz = siz.load(Ordering::Relaxed); if siz > MAX_RESULT_SIZE * 4 { - return Err(anyhow::anyhow!( + return Err(Error::ExecutionErr(format!( "Query result too large for cloud (size = {} > {})", siz, - MAX_RESULT_SIZE & 4 - )); + MAX_RESULT_SIZE & 4, + ))); } } if let Ok(v) = r { res.push(v); } else { - return Err(to_anyhow(r.err().unwrap())); + return Err(to_anyhow(r.err().unwrap()).into()); } } } @@ -156,25 +177,25 @@ fn do_postgresql_inner<'a>( } pub async fn do_postgresql( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, column_order: &mut Option>, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result> { - let pg_args = build_args_values(job, client, db).await?; + let pg_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); + let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { Some( client - .get_authed() - .await .get_resource_value_interpolated::( &inline_db_res_path, Some(job.id.to_string()), @@ -211,14 +232,10 @@ pub async fn do_postgresql( ); let database_string_clone = database_string.clone(); - RUNNING.store(true, std::sync::atomic::Ordering::Relaxed); - LAST_QUERY.store( - chrono::Utc::now().timestamp().try_into().unwrap_or(0), - std::sync::atomic::Ordering::Relaxed, - ); let mtex; if !*CLOUD_HOSTED { - mtex = Some(CONNECTION_CACHE.lock().await); + mtex = CONNECTION_CACHE.try_lock().ok(); + increment_connection_counter(&database_string).await; } else { mtex = None; } @@ -226,9 +243,15 @@ pub async fn do_postgresql( let has_cached_con = mtex .as_ref() .is_some_and(|x| x.as_ref().is_some_and(|y| y.0 == database_string)); - let new_client = if has_cached_con { + + // tracing::error!("HAS CACHED CON: {}", has_cached_con); + let (new_client, mtex) = if has_cached_con { tracing::info!("Using cached connection"); - None + LAST_QUERY.store( + chrono::Utc::now().timestamp().try_into().unwrap_or(0), + std::sync::atomic::Ordering::Relaxed, + ); + (None, mtex) } else if sslmode == "require" { tracing::info!("Creating new connection"); let mut connector = TlsConnector::builder(); @@ -266,7 +289,7 @@ pub async fn do_postgresql( tracing::error!("connection error: {}", e); } }); - Some((client, handle)) + (Some((client, handle)), None) } else { tracing::info!("Creating new connection"); let (client, connection) = tokio::time::timeout( @@ -284,9 +307,13 @@ pub async fn do_postgresql( tracing::error!("connection error: {}", e); } }); - Some((client, handle)) + (Some((client, handle)), None) }; + let sig = parse_pgsql_sig(&query).map_err(|x| Error::ExecutionErr(x.to_string()))?; + + let (query, _) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig.args, &pg_args)?; + let queries = parse_sql_blocks(query); let (client, handle) = if let Some((client, handle)) = new_client.as_ref() { @@ -296,7 +323,6 @@ pub async fn do_postgresql( (client, None) }; - let sig = parse_pgsql_sig(&query).map_err(|x| Error::ExecutionErr(x.to_string()))?; let param_idx_to_arg_and_value = sig .args .iter() @@ -316,6 +342,7 @@ pub async fn do_postgresql( None, &size, annotations.return_last_result && i < queries.len() - 1, + s3.clone(), ) }) .collect::>>()?; @@ -342,13 +369,14 @@ pub async fn do_postgresql( Some(column_order), &size, false, + s3, )? }; let result = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, result_f, @@ -359,61 +387,96 @@ pub async fn do_postgresql( ) .await?; + // drop the mtex to avoid holding the lock for too long, result has been returned + drop(mtex); + *mem_peak = size.load(Ordering::Relaxed) as i32; - RUNNING.store(false, std::sync::atomic::Ordering::Relaxed); - if let Some(handle) = handle { - if let Some(mut mtex) = mtex { - let abort_handler = handle.abort_handle(); + if !*CLOUD_HOSTED { + // tracing::error!("Found handle"); + if let Ok(mut mtex) = CONNECTION_CACHE.try_lock() { + if mtex.as_ref().is_none_or(|x| x.0 != database_string) { + // tracing::error!("Locked conn cached"); + let abort_handler = handle.abort_handle(); - if let Some(new_client) = new_client { - *mtex = Some((database_string, new_client.0)); - } - drop(mtex); - LAST_QUERY.store( - chrono::Utc::now().timestamp().try_into().unwrap_or(0), - std::sync::atomic::Ordering::Relaxed, - ); - - tokio::spawn(async move { - loop { - tokio::time::sleep(Duration::from_secs(5)).await; - let last_query = LAST_QUERY.load(std::sync::atomic::Ordering::Relaxed); - let now = chrono::Utc::now().timestamp().try_into().unwrap_or(0); - - //we cache connection for 5 minutes at most - if last_query + 60 * 5 < now - && !RUNNING.load(std::sync::atomic::Ordering::Relaxed) - { - tracing::info!("Closing cache connection due to inactivity"); - break; - } - let mtex = CONNECTION_CACHE.lock().await; - if mtex.is_none() { - // connection is not in the mutex anymore - break; - } else if let Some(mtex) = mtex.as_ref() { - if mtex.0.as_str() != &database_string_clone { - // connection is not the latest one - break; + let mut cache_new_con = false; + if let Some(new_client) = new_client { + cache_new_con = is_most_used_conn(&database_string).await; + if cache_new_con { + *mtex = Some((database_string, new_client.0)); + } else { + new_client.1.abort(); } + } else { + handle.abort(); } - tracing::debug!("Keeping cached connection alive due to activity") + if cache_new_con { + LAST_QUERY.store( + chrono::Utc::now().timestamp().try_into().unwrap_or(0), + std::sync::atomic::Ordering::Relaxed, + ); + tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(5)).await; + let last_query = + LAST_QUERY.load(std::sync::atomic::Ordering::Relaxed); + let now = chrono::Utc::now().timestamp().try_into().unwrap_or(0); + + //we cache connection for 5 minutes at most + if last_query + 60 * 1 < now { + // tracing::error!("Closing cache connection due to inactivity"); + tracing::info!( + "Closing cache pg executor connection due to inactivity" + ); + break; + } + let mtex = CONNECTION_CACHE.lock().await; + if mtex.is_none() { + // connection is not in the mutex anymore + break; + } else if let Some(mtex) = mtex.as_ref() { + if mtex.0.as_str() != &database_string_clone { + // connection is not the latest one + break; + } + } + + tracing::debug!( + "Keeping cached pg executor connection alive due to activity" + ) + } + let mut mtex = CONNECTION_CACHE.lock().await; + *mtex = None; + abort_handler.abort(); + }); + } + } else { + handle.abort(); } - let mut mtex = CONNECTION_CACHE.lock().await; - *mtex = None; - abort_handler.abort(); - }); + } else { + handle.abort(); + } } else { handle.abort(); } } - let raw_result = to_raw_value(&result); - *mem_peak = (raw_result.get().len() / 1000) as i32; + *mem_peak = (result.get().len() / 1000) as i32; // And then check that we got back the same string we sent over. - return Ok(raw_result); + return Ok(result); +} + +async fn is_most_used_conn(database_string: &str) -> bool { + let counter_map = CONNECTION_COUNTER.read().await; + let current_count = counter_map.get(database_string).copied().unwrap_or(0); + let max_count = counter_map.values().copied().max().unwrap_or(0); + current_count >= max_count +} + +async fn increment_connection_counter(database_string: &str) { + let mut counter_map = CONNECTION_COUNTER.write().await; + *counter_map.entry(database_string.to_string()).or_insert(0) += 1; } fn map_as_single_type( @@ -766,6 +829,7 @@ pub fn pg_cell_to_json_value( Type::BYTEA_ARRAY => get_array(row, column, column_i, |a: Vec| { Ok(JSONValue::String(format!("\\x{}", hex::encode(a)))) })?, + Type::VOID => JSONValue::Null, _ => get_basic(row, column, column_i, |a: String| Ok(JSONValue::String(a)))?, }) } diff --git a/backend/windmill-worker/src/php_executor.rs b/backend/windmill-worker/src/php_executor.rs index 4f579a54a8..ec8478a9a5 100644 --- a/backend/windmill-worker/src/php_executor.rs +++ b/backend/windmill-worker/src/php_executor.rs @@ -7,9 +7,10 @@ use tokio::{fs::File, io::AsyncReadExt, process::Command}; use uuid::Uuid; use windmill_common::{ error::{self, to_anyhow, Result}, - jobs::QueuedJob, - worker::write_file, + worker::{write_file, Connection}, }; +use windmill_queue::MiniPulledJob; + use windmill_parser::Typ; use windmill_queue::{append_logs, CanceledBy}; @@ -19,9 +20,10 @@ use crate::{ read_result, start_child_process, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, - NSJAIL_PATH, PHP_PATH, + COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, + PHP_PATH, }; +use windmill_common::client::AuthedClient; const NSJAIL_CONFIG_RUN_PHP_CONTENT: &str = include_str!("../nsjail/run.php.config.proto"); @@ -66,7 +68,7 @@ pub async fn composer_install( canceled_by: &mut Option, job_id: &Uuid, w_id: &str, - db: &sqlx::Pool, + conn: &Connection, job_dir: &str, worker_name: &str, requirements: String, @@ -93,7 +95,7 @@ pub async fn composer_install( handle_child( job_id, - db, + conn, mem_peak, canceled_by, child_process, @@ -104,6 +106,7 @@ pub async fn composer_install( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -136,9 +139,10 @@ pub async fn handle_php_job( requirements_o: Option<&String>, mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, job_dir: &str, inner_content: &String, base_internal_url: &str, @@ -164,14 +168,14 @@ pub async fn handle_php_job( let autoload_line = if let Some(composer_json) = composer_json { let logs1 = "\n\n--- COMPOSER INSTALL ---\n".to_string(); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; composer_install( mem_peak, canceled_by, &job.id, &job.workspace_id, - db, + conn, job_dir, worker_name, composer_json, @@ -186,7 +190,7 @@ pub async fn handle_php_job( let init_logs = "\n\n--- PHP CODE EXECUTION ---\n".to_string(); - append_logs(&job.id, job.workspace_id.to_string(), init_logs, db).await; + append_logs(&job.id, job.workspace_id.to_string(), init_logs, conn).await; let _ = write_file(job_dir, "main.php", inner_content)?; @@ -260,12 +264,13 @@ try {{ let reserved_variables_args_out_f = async { let args_and_out_f = async { - create_args_and_out_file(&client, job, job_dir, db).await?; + create_args_and_out_file(&client, job, job_dir, conn).await?; Ok(()) as Result<()> }; let reserved_variables_f = async { - let client = client.get_authed().await; - let vars = get_reserved_variables(job, &client.token, db).await?; + let vars = + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()) + .await?; Ok(vars) as Result> }; let (_, reserved_variables) = tokio::try_join!(args_and_out_f, reserved_variables_f)?; @@ -326,7 +331,7 @@ try {{ handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -337,6 +342,7 @@ try {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 1180652d35..ad46bfd3cd 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -3,6 +3,7 @@ use std::{ fs, path::Path, process::Stdio, + str::FromStr, sync::Arc, }; @@ -10,7 +11,6 @@ use anyhow::anyhow; use itertools::Itertools; use regex::Regex; use serde_json::value::RawValue; -use sqlx::{Pool, Postgres}; use tokio::{ fs::{metadata, DirBuilder, File}, io::AsyncReadExt, @@ -20,36 +20,37 @@ use tokio::{ }; use uuid::Uuid; #[cfg(all(feature = "enterprise", feature = "parquet", unix))] -use windmill_common::ee::{get_license_plan, LicensePlan}; +use windmill_common::ee_oss::{get_license_plan, LicensePlan}; use windmill_common::{ error::{ self, Error::{self}, }, - jobs::QueuedJob, utils::calculate_hash, - worker::{write_file, PythonAnnotations, WORKER_CONFIG}, - DB, + worker::{ + copy_dir_recursively, pad_string, write_file, Connection, PythonAnnotations, WORKER_CONFIG, + }, }; #[cfg(feature = "enterprise")] use windmill_common::variables::get_secret_value_as_admin; use std::env::var; -use windmill_queue::{append_logs, CanceledBy}; +use windmill_queue::{append_logs, CanceledBy, PrecomputedAgentInfo}; lazy_static::lazy_static! { - static ref PYTHON_PATH: Option = var("PYTHON_PATH").ok().map(|v| { + pub(crate) static ref PYTHON_PATH: Option = var("PYTHON_PATH").ok().map(|v| { tracing::warn!("PYTHON_PATH is set to {} and thus python will not be managed by uv and stay static regardless of annotation and instance settings. NOT RECOMMENDED", v); v }); - static ref UV_PATH: String = + pub(crate) static ref UV_PATH: String = var("UV_PATH").unwrap_or_else(|_| "/usr/local/bin/uv".to_string()); static ref PY_CONCURRENT_DOWNLOADS: usize = var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + static ref NON_ALPHANUM_CHAR: Regex = regex::Regex::new(r"[^0-9A-Za-z=.-]").unwrap(); static ref TRUSTED_HOST: Option = var("PY_TRUSTED_HOST").ok().or(var("PIP_TRUSTED_HOST").ok()); @@ -61,15 +62,55 @@ lazy_static::lazy_static! { static ref EPHEMERAL_TOKEN_CMD: Option = var("EPHEMERAL_TOKEN_CMD").ok(); } +#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +lazy_static::lazy_static! { + static ref PIPTAR_UPLOAD_CHANNEL: tokio::sync::mpsc::UnboundedSender = { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + // Spawn background task to handle uploads sequentially + tokio::spawn(handle_piptar_uploads(rx)); + + tx + }; +} + +#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +#[derive(Debug)] +struct PiptarUploadTask { + venv_path: String, + cache_dir: String, +} + +#[cfg(all(feature = "enterprise", feature = "parquet", unix))] +async fn handle_piptar_uploads(mut rx: tokio::sync::mpsc::UnboundedReceiver) { + use crate::global_cache::build_tar_and_push; + use windmill_common::s3_helpers::get_object_store; + + while let Some(task) = rx.recv().await { + if let Some(os) = get_object_store().await { + match build_tar_and_push(os, task.venv_path.clone(), task.cache_dir, None, false).await { + Ok(()) => { + tracing::info!("Successfully uploaded piptar for {}", task.venv_path); + } + Err(e) => { + tracing::error!("Failed to upload piptar for {}: {}", task.venv_path, e); + } + } + } else { + tracing::warn!("S3 object store not available for piptar upload: {}", task.venv_path); + } + } +} + const NSJAIL_CONFIG_DOWNLOAD_PY_CONTENT: &str = include_str!("../nsjail/download.py.config.proto"); const NSJAIL_CONFIG_RUN_PYTHON3_CONTENT: &str = include_str!("../nsjail/run.python3.config.proto"); const RELATIVE_PYTHON_LOADER: &str = include_str!("../loader.py"); #[cfg(all(feature = "enterprise", feature = "parquet", unix))] -use crate::global_cache::{build_tar_and_push, pull_from_tar}; +use crate::global_cache::pull_from_tar; #[cfg(all(feature = "enterprise", feature = "parquet", unix))] -use windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS; +use windmill_common::s3_helpers::OBJECT_STORE_SETTINGS; use crate::{ common::{ @@ -77,345 +118,11 @@ use crate::{ start_child_process, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, INSTANCE_PYTHON_VERSION, - NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, - UV_CACHE_DIR, + worker_utils::ping_job_status, + PyV, PyVAlias, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, + PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, UV_CACHE_DIR, }; - -// To change latest stable version: -// 1. Change placeholder in instanceSettings.ts -// 2. Change LATEST_STABLE_PY in dockerfile -// 3. Change #[default] annotation for PyVersion in backend -#[derive(Eq, PartialEq, Clone, Copy, Default, Debug)] -pub enum PyVersion { - Py310, - #[default] - Py311, - Py312, - Py313, -} - -impl PyVersion { - pub async fn from_instance_version(job_id: &Uuid, w_id: &str, db: &Pool) -> Self { - let mut err = None; - let pyv = match INSTANCE_PYTHON_VERSION.read().await.clone() { - Some(v) => PyVersion::from_string_with_dots(&v).unwrap_or_else(|| { - let v = PyVersion::default(); - err = Some(format!("\nCannot parse INSTANCE_PYTHON_VERSION ({:?}), fallback to latest_stable ({v:?})", *INSTANCE_PYTHON_VERSION)); - v - }), - // Use latest stable - None => PyVersion::default(), - }; - - if let Some(msg) = err { - append_logs(job_id, w_id, &msg, db).await; - tracing::error!(msg); - } - pyv - } - /// e.g.: `/tmp/windmill/cache/python_3xy` - pub fn to_cache_dir(&self) -> String { - use windmill_common::worker::ROOT_CACHE_DIR; - format!("{ROOT_CACHE_DIR}{}", &self.to_cache_dir_top_level()) - } - /// e.g.: `python_3xy` - pub fn to_cache_dir_top_level(&self) -> String { - format!("python_{}", self.to_string_no_dot()) - } - /// e.g.: `3xy` - pub fn to_string_no_dot(&self) -> String { - self.to_string_with_dot().replace('.', "") - } - /// e.g.: `3.xy` - pub fn to_string_with_dot(&self) -> &str { - use PyVersion::*; - match self { - Py310 => "3.10", - Py311 => "3.11", - Py312 => "3.12", - Py313 => "3.13", - } - } - pub fn from_string_with_dots(value: &str) -> Option { - use PyVersion::*; - match value { - "3.10" => Some(Py310), - "3.11" => Some(Py311), - "3.12" => Some(Py312), - "3.13" => Some(Py313), - "default" => Some(PyVersion::default()), - _ => { - tracing::warn!( - "Cannot convert string (\"{value}\") to PyVersion\nExpected format x.yz" - ); - None - } - } - } - pub fn from_string_no_dots(value: &str) -> Option { - use PyVersion::*; - match value { - "310" => Some(Py310), - "311" => Some(Py311), - "312" => Some(Py312), - "313" => Some(Py313), - "default" => Some(PyVersion::default()), - _ => { - tracing::warn!( - "Cannot convert string (\"{value}\") to PyVersion\nExpected format xyz" - ); - None - } - } - } - /// e.g.: `# py3xy` -> `PyVersion::Py3XY` - pub fn parse_version(line: &str) -> Option { - Self::from_string_no_dots(line.replace(" ", "").replace("#py", "").as_str()) - } - pub fn from_py_annotations(a: PythonAnnotations) -> Option { - let PythonAnnotations { py310, py311, py312, py313, .. } = a; - use PyVersion::*; - if py313 { - Some(Py313) - } else if py312 { - Some(Py312) - } else if py311 { - Some(Py311) - } else if py310 { - Some(Py310) - } else { - None - } - } - pub fn from_numeric(n: u32) -> Option { - use PyVersion::*; - match n { - 310 => Some(Py310), - 311 => Some(Py311), - 312 => Some(Py312), - 313 => Some(Py313), - _ => None, - } - } - pub fn to_numeric(&self) -> u32 { - use PyVersion::*; - match self { - Py310 => 310, - Py311 => 311, - Py312 => 312, - Py313 => 313, - } - } - pub async fn get_python( - &self, - job_id: &Uuid, - mem_peak: &mut i32, - // canceled_by: &mut Option, - db: &Pool, - worker_name: &str, - w_id: &str, - occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - ) -> error::Result> { - // lazy_static::lazy_static! { - // static ref PYTHON_PATHS: Arc>> = Arc::new(RwLock::new(HashMap::new())); - // } - - let res = self - .get_python_inner(job_id, mem_peak, db, worker_name, w_id, occupancy_metrics) - .await; - - if let Err(ref e) = res { - tracing::error!( - "worker_name: {worker_name}, w_id: {w_id}, job_id: {job_id}\n - Error while getting python from uv, falling back to system python: {e:?}" - ); - append_logs( - job_id, - w_id, - format!( - "\nError while getting python from uv, falling back to system python: {e:?}" - ), - db, - ) - .await; - } - res - } - async fn get_python_inner( - self, - job_id: &Uuid, - mem_peak: &mut i32, - // canceled_by: &mut Option, - db: &Pool, - worker_name: &str, - w_id: &str, - occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - ) -> error::Result> { - let py_path = self.find_python().await; - - // Runtime is not installed - if py_path.is_err() { - // Install it - if let Err(err) = self - .install_python(job_id, mem_peak, db, worker_name, w_id, occupancy_metrics) - .await - { - tracing::error!("Cannot install python: {err}"); - return Err(err); - } else { - // Try to find one more time - let py_path = self.find_python().await; - - if let Err(err) = py_path { - tracing::error!("Cannot find python version {err}"); - return Err(err); - } - - // TODO: Cache the result - py_path - } - } else { - py_path - } - } - async fn install_python( - self, - job_id: &Uuid, - mem_peak: &mut i32, - // canceled_by: &mut Option, - db: &Pool, - worker_name: &str, - w_id: &str, - occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - ) -> error::Result<()> { - let v = self.to_string_with_dot(); - append_logs(job_id, w_id, format!("\nINSTALLING PYTHON ({})", v), db).await; - // Create dirs for newly installed python - // If we dont do this, NSJAIL will not be able to mount cache - // For the default version directory created during startup (main.rs) - DirBuilder::new() - .recursive(true) - .create(self.to_cache_dir()) - .await - .expect("could not create initial worker dir"); - - let logs = String::new(); - - #[cfg(windows)] - let uv_cmd = "uv"; - - #[cfg(unix)] - let uv_cmd = UV_PATH.as_str(); - - let mut child_cmd = Command::new(uv_cmd); - child_cmd - .env_clear() - .env("HOME", HOME_ENV.to_string()) - .env("PATH", PATH_ENV.to_string()) - .envs(PROXY_ENVS.clone()) - .args(["python", "install", v, "--python-preference=only-managed"]) - // TODO: Do we need these? - .envs([("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR)]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - #[cfg(windows)] - { - child_cmd - .env("SystemRoot", SYSTEM_ROOT.as_str()) - .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) - .env( - "TMP", - std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), - ) - .env( - "LOCALAPPDATA", - std::env::var("LOCALAPPDATA") - .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), - ); - } - - let child_process = start_child_process(child_cmd, "uv").await?; - - append_logs(&job_id, &w_id, logs, db).await; - handle_child( - job_id, - db, - mem_peak, - &mut None, - child_process, - false, - worker_name, - &w_id, - "uv", - None, - false, - occupancy_metrics, - ) - .await - } - async fn find_python(self) -> error::Result> { - #[cfg(windows)] - let uv_cmd = "uv"; - - #[cfg(unix)] - let uv_cmd = UV_PATH.as_str(); - - let mut child_cmd = Command::new(uv_cmd); - - child_cmd.env_clear(); - - #[cfg(windows)] - { - child_cmd - .env("SystemRoot", SYSTEM_ROOT.as_str()) - .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) - .env( - "TMP", - std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), - ) - .env( - "LOCALAPPDATA", - std::env::var("LOCALAPPDATA") - .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), - ); - } - - let output = child_cmd - // .current_dir(job_dir) - .env("HOME", HOME_ENV.to_string()) - .env("PATH", PATH_ENV.to_string()) - .args([ - "python", - "find", - self.to_string_with_dot(), - "--system", - "--python-preference=only-managed", - ]) - .envs([ - ("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR), - ("UV_PYTHON_PREFERENCE", "only-managed"), - ]) - // .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await?; - - // Check if the command was successful - if output.status.success() { - // Convert the output to a String - let stdout = - String::from_utf8(output.stdout).expect("Failed to convert output to String"); - return Ok(Some(stdout.replace('\n', ""))); - } else { - // If the command failed, print the error - let stderr = - String::from_utf8(output.stderr).expect("Failed to convert error output to String"); - return Err(error::Error::FindPythonError(stderr)); - } - } -} +use windmill_common::client::AuthedClient; #[cfg(windows)] use crate::SYSTEM_ROOT; @@ -459,11 +166,11 @@ pub async fn uv_pip_compile( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &Pool, + conn: &Connection, worker_name: &str, w_id: &str, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - py_version: PyVersion, + py_version: PyV, // Debug-only flag no_cache: bool, ) -> error::Result { @@ -500,31 +207,34 @@ pub async fn uv_pip_compile( requirements.to_string() }; + let py_version_str = py_version.clone().to_string(); // Include python version to requirements.in // We need it because same hash based on requirements.in can get calculated even for different python versions // To prevent from overwriting same requirements.in but with different python versions, we include version to hash - let requirements = format!("# py{}\n{}", py_version.to_string_no_dot(), requirements); + let requirements = format!("# py: {}\n{}", py_version.to_string(), requirements); #[cfg(feature = "enterprise")] - let requirements = replace_pip_secret(db, w_id, &requirements, worker_name, job_id).await?; + let requirements = replace_pip_secret(conn, w_id, &requirements, worker_name, job_id).await?; let req_hash = format!("py-{}", calculate_hash(&requirements)); if !no_cache { - if let Some(cached) = sqlx::query_scalar!( - "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", - // Python version is included in hash, - // hash will be the different for every python version - req_hash - ) - .fetch_optional(db) - .await? - { - logs.push_str(&format!( - "\nFound cached resolution: {req_hash}, on python version: {}", - py_version.to_string_with_dot() - )); - return Ok(cached); + if let Some(db) = conn.as_sql() { + if let Some(cached) = sqlx::query_scalar!( + "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", + // Python version is included in hash, + // hash will be the different for every python version + req_hash + ) + .fetch_optional(db) + .await? + { + logs.push_str(&format!( + "\nFound cached resolution: {req_hash}, on python version: {}", + &py_version_str + )); + return Ok(cached); + } } } @@ -535,7 +245,7 @@ pub async fn uv_pip_compile( { // Make sure we have python runtime installed py_version - .get_python(job_id, mem_peak, db, worker_name, w_id, occupancy_metrics) + .try_get_python(job_id, mem_peak, conn, worker_name, w_id, occupancy_metrics) .await?; let mut args = vec![ @@ -557,12 +267,7 @@ pub async fn uv_pip_compile( UV_CACHE_DIR, ]; - args.extend([ - "-p", - &py_version.to_string_with_dot(), - "--python-preference", - "only-managed", - ]); + args.extend(["-p", &py_version_str, "--python-preference", "only-managed"]); if no_cache { args.extend(["--no-cache"]); @@ -619,6 +324,7 @@ pub async fn uv_pip_compile( child_cmd .env("SystemRoot", SYSTEM_ROOT.as_str()) .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env("HOME", crate::USERPROFILE_ENV.as_str()) .env( "LOCALAPPDATA", std::env::var("LOCALAPPDATA") @@ -627,14 +333,37 @@ pub async fn uv_pip_compile( .env( "TMP", std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ) + .env( + "APPDATA", + std::env::var("APPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Roaming", crate::USERPROFILE_ENV.as_str())), + ) + .env( + "ComSpec", + std::env::var("ComSpec").unwrap_or_else(|_| String::from("C:\\Windows\\System32\\cmd.exe")), + ) + .env( + "PATHEXT", + std::env::var("PATHEXT").unwrap_or_else(|_| + String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL") + ), + ) + .env( + "ProgramData", + std::env::var("ProgramData").unwrap_or_else(|_| String::from("C:\\ProgramData")), + ) + .env( + "ProgramFiles", + std::env::var("ProgramFiles").unwrap_or_else(|_| String::from("C:\\Program Files")), ); } let child_process = start_child_process(child_cmd, uv_cmd).await?; - append_logs(&job_id, &w_id, logs, db).await; + append_logs(&job_id, &w_id, logs, conn).await; handle_child( job_id, - db, + conn, mem_peak, canceled_by, child_process, @@ -646,6 +375,7 @@ pub async fn uv_pip_compile( None, false, occupancy_metrics, + None, ) .await .map_err(|e| { @@ -661,8 +391,8 @@ pub async fn uv_pip_compile( let mut req_content = "".to_string(); file.read_to_string(&mut req_content).await?; let lockfile = format!( - "# py{}\n{}", - py_version.to_string_no_dot(), + "# py: {}\n{}", + py_version.to_string(), req_content .lines() .filter(|x| !x.trim_start().starts_with('#')) @@ -670,11 +400,13 @@ pub async fn uv_pip_compile( .collect::>() .join("\n") ); - sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + if let Some(db) = conn.as_sql() { + sqlx::query!( + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", req_hash, lockfile ).fetch_optional(db).await?; + } Ok(lockfile) } @@ -709,8 +441,8 @@ pub async fn uv_pip_compile( async fn postinstall( additional_python_paths: &mut Vec, job_dir: &str, - job: &QueuedJob, - db: &sqlx::Pool, + job: &MiniPulledJob, + conn: &Connection, ) -> windmill_common::error::Result<()> { // It is guranteed that additional_python_paths only contains paths within windmill/cache/ // All other paths you would usually expect in PYTHONPATH are NOT included. These are added in downstream @@ -771,7 +503,7 @@ async fn postinstall( &job.id, &job.workspace_id, "\n\nCopying some packages from cache to job_dir...\n".to_string(), - db, + conn, ) .await; // Remove PATHs we just moved @@ -782,73 +514,29 @@ async fn postinstall( Ok(()) } -fn copy_dir_recursively(src: &Path, dst: &Path) -> windmill_common::error::Result<()> { - if !dst.exists() { - fs::create_dir_all(dst)?; - } - - tracing::debug!("Copying recursively from {:?} to {:?}", src, dst); - - for entry in fs::read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - - if src_path.is_dir() && !src_path.is_symlink() { - copy_dir_recursively(&src_path, &dst_path)?; - } else { - fs::copy(&src_path, &dst_path)?; - } - } - - tracing::debug!("Finished copying recursively from {:?} to {:?}", src, dst); - - Ok(()) -} - -async fn get_python_path( - py_version: PyVersion, - worker_name: &str, - job_id: &Uuid, - w_id: &str, - mem_peak: &mut i32, - db: &sqlx::Pool, - occupancy_metrics: &mut Option<&mut OccupancyMetrics>, -) -> windmill_common::error::Result { - let python_path = if let Some(python_path) = PYTHON_PATH.clone() { - python_path - } else if let Some(python_path) = py_version - .get_python(&job_id, mem_peak, db, worker_name, w_id, occupancy_metrics) - .await? - { - python_path - } else { - return Err(Error::ExecutionErr(format!( - "uv could not manage python path. Please manage it manually by setting PYTHON_PATH environment variable to your python binary path" - ))); - }; - Ok(python_path) -} - #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_python_job( requirements_o: Option<&String>, job_dir: &str, worker_dir: &str, worker_name: &str, - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, inner_content: &String, shared_mount: &str, base_internal_url: &str, envs: HashMap, new_args: &mut Option>>, occupancy_metrics: &mut OccupancyMetrics, + precomputed_agent_info: Option, ) -> windmill_common::error::Result> { - let script_path = crate::common::use_flow_root_path(job.script_path()); + let script_path = crate::common::use_flow_root_path(job.runnable_path()); + + let annotations = PythonAnnotations::parse(inner_content); let (py_version, mut additional_python_paths) = handle_python_deps( job_dir, @@ -857,30 +545,31 @@ pub async fn handle_python_job( &job.workspace_id, &script_path, &job.id, - db, + conn, worker_name, worker_dir, mem_peak, canceled_by, &mut Some(occupancy_metrics), + precomputed_agent_info, + annotations, ) .await?; - let PythonAnnotations { no_postinstall, .. } = PythonAnnotations::parse(inner_content); tracing::debug!("Finished handling python dependencies"); - let python_path = get_python_path( - py_version, - worker_name, - &job.id, - &job.workspace_id, - mem_peak, - db, - &mut Some(occupancy_metrics), - ) - .await?; + let python_path = py_version + .get_python( + worker_name, + &job.id, + &job.workspace_id, + mem_peak, + conn, + &mut Some(occupancy_metrics), + ) + .await?; - if !no_postinstall { - if let Err(e) = postinstall(&mut additional_python_paths, job_dir, job, db).await { + if !annotations.no_postinstall { + if let Err(e) = postinstall(&mut additional_python_paths, job_dir, job, conn).await { tracing::error!("Postinstall stage has failed. Reason: {e}"); } tracing::debug!("Finished deps postinstall stage"); @@ -892,9 +581,9 @@ pub async fn handle_python_job( &job.workspace_id, format!( "\n\n--- PYTHON ({}) CODE EXECUTION ---\n", - py_version.to_string_with_dot() + py_version.clone().to_string() ), - db, + conn, ) .await; } @@ -911,7 +600,7 @@ pub async fn handle_python_job( pre_spread, ) = prepare_wrapper( job_dir, - job.is_flow_step, + job.is_flow_step(), job.preprocessed, job.script_entrypoint_override.as_deref(), inner_content, @@ -923,7 +612,7 @@ pub async fn handle_python_job( let apply_preprocessor = pre_spread.is_some(); - create_args_and_out_file(&client, job, job_dir, db).await?; + create_args_and_out_file(&client, job, job_dir, conn).await?; tracing::debug!("Finished preparing wrapper"); let preprocessor = if let Some(pre_spread) = pre_spread { @@ -937,7 +626,7 @@ pub async fn handle_python_job( if v == '': del pre_args[k] kwargs = inner_script.preprocessor(**pre_args) - kwrags_json = res_to_json(kwargs) + kwrags_json = res_to_json(kwargs) with open("args.json", 'w') as f: f.write(kwrags_json)"# ) @@ -945,6 +634,8 @@ pub async fn handle_python_job( "".to_string() }; + let postprocessor = get_result_postprocessor(annotations.skip_result_postprocessing); + let os_main_override = if let Some(main_override) = main_name.as_ref() { format!("os.environ[\"MAIN_OVERRIDE\"] = \"{main_override}\"\n") } else { @@ -991,7 +682,8 @@ def res_to_json(res): for k, v in res.items(): if type(v).__name__ == 'bytes': res[k] = to_b_64(v) - return re.sub(replace_invalid_fields, ' null ', json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')) + unprocessed = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '') + return {postprocessor} try: {preprocessor} @@ -1010,7 +702,7 @@ except BaseException as e: tb = traceback.format_tb(exc_traceback) with open(result_json, 'w') as f: err = {{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }} - extra = e.__dict__ + extra = e.__dict__ if extra and len(extra) > 0: err['extra'] = extra flow_node_id = os.environ.get('WM_FLOW_STEP_ID') @@ -1025,12 +717,12 @@ except BaseException as e: tracing::debug!("Finished writing wrapper"); - let client = client.get_authed().await; - let mut reserved_variables = get_reserved_variables(job, &client.token, db).await?; + let mut reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; - // Add /tmp/windmill/cache/python_xyz/global-site-packages to PYTHONPATH. + // Add /tmp/windmill/cache/python_x_y_z/global-site-packages to PYTHONPATH. // Usefull if certain wheels needs to be preinstalled before execution. - let global_site_packages_path = py_version.to_cache_dir() + "/global-site-packages"; + let global_site_packages_path = py_version.to_cache_dir(true) + "/global-site-packages"; let additional_python_paths_folders = { let mut paths = additional_python_paths.clone(); if std::fs::metadata(&global_site_packages_path).is_ok() { @@ -1041,12 +733,19 @@ except BaseException as e: // Since we handle mount of global_site_packages on our own, we don't want it to be mounted automatically. // We do this because existence of every wheel in cache is mandatory and if it is not there and nsjail expects it, it is a bug. // On the other side global_site_packages is purely optional. - // NOTE: This behaviour can be changed in future, so verification of wheels can be offloaded from nsjail to windmill + // NOTE: This behaviour can be changed in future, so verification of wheels can be delegated from nsjail to windmill paths.insert(0, global_site_packages_path.clone()); - // ^^^^^^^^ + // ^^^^^^ ^ // We also want this be priorotized, that's why we insert it to the beginning } - paths.iter().join(":") + #[cfg(windows)] + { + paths.iter().join(";") + } + #[cfg(not(windows))] + { + paths.iter().join(":") + } }; #[cfg(windows)] @@ -1151,7 +850,7 @@ mount {{ handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -1162,6 +861,7 @@ mount {{ job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -1374,43 +1074,47 @@ async fn prepare_wrapper( #[cfg(feature = "enterprise")] async fn replace_pip_secret( - db: &DB, + conn: &Connection, w_id: &str, req: &str, worker_name: &str, job_id: &Uuid, ) -> error::Result { - if PIP_SECRET_VARIABLE.is_match(req) { - let mut joined = "".to_string(); - for req in req.lines() { - let nreq = if PIP_SECRET_VARIABLE.is_match(req) { - let capture = PIP_SECRET_VARIABLE.captures(req); - let variable = capture.unwrap().get(1).unwrap().as_str(); - if !variable.contains("/PIP_SECRET_") { - return Err(error::Error::internal_err(format!( + if let Some(db) = conn.as_sql() { + if PIP_SECRET_VARIABLE.is_match(req) { + let mut joined = "".to_string(); + for req in req.lines() { + let nreq = if PIP_SECRET_VARIABLE.is_match(req) { + let capture = PIP_SECRET_VARIABLE.captures(req); + let variable = capture.unwrap().get(1).unwrap().as_str(); + if !variable.contains("/PIP_SECRET_") { + return Err(error::Error::internal_err(format!( "invalid secret variable in pip requirements, (last part of path ma): {}", req ))); - } - let secret = get_secret_value_as_admin(db, w_id, variable).await?; - tracing::info!( - worker = %worker_name, - job_id = %job_id, - workspace_id = %w_id, - "found secret variable in pip requirements: {}", - req - ); - PIP_SECRET_VARIABLE - .replace(req, secret.as_str()) - .to_string() - } else { - req.to_string() - }; - joined.push_str(&nreq); - joined.push_str("\n"); - } + } + let secret = get_secret_value_as_admin(db, w_id, variable).await?; + tracing::info!( + worker = %worker_name, + job_id = %job_id, + workspace_id = %w_id, + "found secret variable in pip requirements: {}", + req + ); + PIP_SECRET_VARIABLE + .replace(req, secret.as_str()) + .to_string() + } else { + req.to_string() + }; + joined.push_str(&nreq); + joined.push_str("\n"); + } - Ok(joined) + Ok(joined) + } else { + Ok(req.to_string()) + } } else { Ok(req.to_string()) } @@ -1423,13 +1127,15 @@ async fn handle_python_deps( w_id: &str, script_path: &str, job_id: &Uuid, - db: &DB, + conn: &Connection, worker_name: &str, worker_dir: &str, mem_peak: &mut i32, canceled_by: &mut Option, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, -) -> error::Result<(PyVersion, Vec)> { + precomputed_agent_info: Option, + annotations: PythonAnnotations, +) -> error::Result<(PyV, Vec)> { create_dependencies_dir(job_dir).await; let mut additional_python_paths: Vec = WORKER_CONFIG @@ -1440,90 +1146,132 @@ async fn handle_python_deps( .unwrap_or_else(|| vec![]) .clone(); - let mut requirements; - let mut annotated_pyv = None; - let mut annotated_pyv_numeric = None; - let is_deployed = requirements_o.is_some(); - let instance_pyv = PyVersion::from_instance_version(job_id, w_id, db).await; - let annotations = windmill_common::worker::PythonAnnotations::parse(inner_content); - let requirements = match requirements_o { - Some(r) => r, + let (pyv, resolved_lines) = match requirements_o { + // Deployed + Some(r) => { + let rl = split_requirements(r); + (PyV::parse_from_requirements(&rl), rl) + } + // Preview None => { - let mut already_visited = vec![]; + let (v, requirements_lines, error_hint) = match conn { + Connection::Sql(db) => { + let mut version_specifiers = vec![]; + let (r, h) = windmill_parser_py_imports::parse_python_imports( + inner_content, + w_id, + script_path, + db, + &mut version_specifiers, + ) + .await?; - requirements = windmill_parser_py_imports::parse_python_imports( - inner_content, - w_id, - script_path, - db, - &mut already_visited, - &mut annotated_pyv_numeric, + let v = PyV::resolve( + version_specifiers, + job_id, + w_id, + annotations.py_select_latest, + Some(conn.clone()), + None, + None, + ) + .await?; + + (v, r, h) + } + Connection::Http(_) => match precomputed_agent_info { + Some(PrecomputedAgentInfo::Python { + requirements, + py_version, + py_version_v2, + }) => { + let v = { + let v_v2 = py_version_v2 + .clone() + .and_then(|s| pep440_rs::Version::from_str(&s).ok().map(PyV::from)); + let v_v1 = py_version.and_then(PyVAlias::try_from_v1).map(PyV::from); + + match v_v2.or(v_v1) { + Some(v) => v, + None => { + tracing::warn!( + workspace_id = %w_id, + " +Failed to get precomputed python version from server. Fallback to Default ({}) +Returned from server: py_version - {:?}, py_version_v2 - {:?} + ", + *PyV::default(), + py_version, + py_version_v2 + ); + Default::default() + } + } + }; + + let r = split_requirements(requirements.unwrap_or_default()); + let h = None; + + (v, r, h) + } + _ => Default::default(), + }, + }; + + ( + v.clone(), + if !requirements_lines.is_empty() { + uv_pip_compile( + job_id, + &requirements_lines.join("\n"), + mem_peak, + canceled_by, + job_dir, + conn, + worker_name, + w_id, + occupancy_metrics, + // annotated_pyv.unwrap_or(instance_pyv), + v, + annotations.no_cache, + ) + .await + .map_err(|e| { + Error::ExecutionErr(format!( + "pip compile failed: {}{}", + e.to_string(), + error_hint.unwrap_or_default() + )) + })? + .lines() + .map(|s| s.to_owned()) + .collect_vec() + } else { + vec![] + }, ) - .await? - .join("\n"); - - annotated_pyv = annotated_pyv_numeric.and_then(|v| PyVersion::from_numeric(v)); - - if !requirements.is_empty() { - requirements = uv_pip_compile( - job_id, - &requirements, - mem_peak, - canceled_by, - job_dir, - db, - worker_name, - w_id, - occupancy_metrics, - annotated_pyv.unwrap_or(instance_pyv), - annotations.no_cache, - ) - .await - .map_err(|e| { - Error::ExecutionErr(format!("pip compile failed: {}", e.to_string())) - })?; - } - &requirements } }; - /* - For deployed scripts we want to find out version in following order: - 1. Assigned version (written in lockfile) - 2. 3.11 - - For Previews: - 1. Annotated version - 2. Instance version - 3. Latest Stable - */ - let requirements_lines = split_requirements(requirements.as_str()); - let final_version = if is_deployed { - get_pyv_from_requirements_lines(&requirements_lines) - } else { - // This is not deployed script, meaning we test run it (Preview) - annotated_pyv.unwrap_or(instance_pyv) - }; - // If len > 0 it means there is atleast one dependency or assigned python version - if requirements.len() > 0 { + if !resolved_lines.is_empty() { let mut venv_path = handle_python_reqs( - requirements_lines, + resolved_lines, job_id, w_id, mem_peak, canceled_by, - db, + conn, worker_name, job_dir, worker_dir, occupancy_metrics, - final_version, + pyv.clone(), ) .await?; additional_python_paths.append(&mut venv_path); } - Ok((final_version, additional_python_paths)) + Ok((pyv, additional_python_paths)) } lazy_static::lazy_static! { @@ -1693,6 +1441,7 @@ async fn spawn_uv_install( .envs(PROXY_ENVS.clone()) .env("SystemRoot", SYSTEM_ROOT.as_str()) .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env("HOME", HOME_ENV.as_str()) .env( "TMP", std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), @@ -1702,6 +1451,29 @@ async fn spawn_uv_install( std::env::var("LOCALAPPDATA") .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), ) + .env( + "APPDATA", + std::env::var("APPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Roaming", crate::USERPROFILE_ENV.as_str())), + ) + .env( + "ComSpec", + std::env::var("ComSpec").unwrap_or_else(|_| String::from("C:\\Windows\\System32\\cmd.exe")), + ) + .env( + "PATHEXT", + std::env::var("PATHEXT").unwrap_or_else(|_| + String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL") + ), + ) + .env( + "ProgramData", + std::env::var("ProgramData").unwrap_or_else(|_| String::from("C:\\ProgramData")), + ) + .env( + "ProgramFiles", + std::env::var("ProgramFiles").unwrap_or_else(|_| String::from("C:\\Program Files")), + ) .args(&command_args[1..]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -1710,32 +1482,19 @@ async fn spawn_uv_install( } } -/// length = 5 -/// value = "foo" -/// output = "foo " -/// 12345 -fn pad_string(value: &str, total_length: usize) -> String { - if value.len() >= total_length { - value.to_string() // Return the original string if it's already long enough - } else { - let padding_needed = total_length - value.len(); - format!("{value}{}", " ".repeat(padding_needed)) // Pad with spaces - } -} - /// uv pip install, include cached or pull from S3 pub async fn handle_python_reqs( - requirements: Vec<&str>, + requirements: Vec, job_id: &Uuid, w_id: &str, mem_peak: &mut i32, _canceled_by: &mut Option, - db: &sqlx::Pool, + conn: &Connection, _worker_name: &str, job_dir: &str, worker_dir: &str, _occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - py_version: PyVersion, + py_version: PyV, ) -> error::Result> { let worker_dir = worker_dir.to_string(); @@ -1753,7 +1512,7 @@ pub async fn handle_python_reqs( counter_arc: Arc>, total_to_install: usize, instant: std::time::Instant, - db: Pool, + conn: &Connection, ) { #[cfg(not(all(feature = "enterprise", feature = "parquet", unix)))] { @@ -1761,7 +1520,7 @@ pub async fn handle_python_reqs( } #[cfg(all(feature = "enterprise", feature = "parquet", unix))] - if OBJECT_STORE_CACHE_SETTINGS.read().await.is_none() { + if OBJECT_STORE_SETTINGS.read().await.is_none() { (s3_pull, s3_push) = (false, false); } @@ -1782,7 +1541,7 @@ pub async fn handle_python_reqs( if s3_push { " > (S3) " } else { "" }, instant.elapsed().as_millis(), ), - db, + conn, ) .await; // Drop lock, so next print success can fire @@ -1825,7 +1584,7 @@ pub async fn handle_python_reqs( if req.starts_with('#') || req.starts_with('-') || req.trim().is_empty() { continue; } - let py_prefix = &py_version.to_cache_dir(); + let py_prefix = &py_version.to_cache_dir(false); let venv_p = format!( "{py_prefix}/{}", @@ -1844,7 +1603,7 @@ pub async fn handle_python_reqs( &job_id, w_id, format!("\nenv deps from local cache: {}\n", in_cache.join(", ")), - db, + conn, ) .await; } @@ -1858,7 +1617,7 @@ pub async fn handle_python_reqs( let (_done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); let job_id_2 = job_id.clone(); - let db_2 = db.clone(); + let conn_2 = conn.clone(); let w_id_2 = w_id.to_string(); // Wheels to install @@ -1908,9 +1667,12 @@ pub async fn handle_python_reqs( *mem_peak_lock }; + // Notify server that we are still alive // Detect if job has been canceled - let canceled = sqlx::query_scalar!( + let canceled = match conn_2 { + Connection::Sql(ref db) => { + sqlx::query_scalar!( "UPDATE v2_job_runtime r SET memory_peak = $1, ping = now() @@ -1919,17 +1681,25 @@ pub async fn handle_python_reqs( RETURNING canceled_by IS NOT NULL AS \"canceled!\"", mem_peak_actual, job_id_2 - ) - .fetch_optional(&db_2) - .await - .unwrap_or_else(|e| { - tracing::error!(%e, "error updating job {job_id_2}: {e:#}"); - Some(false) - }) - .unwrap_or_else(|| { - // if the job is not in queue, it can only be in the completed_job so it is already complete - false - }); + ) + .fetch_optional(db) + .await + .unwrap_or_else(|e| { + tracing::error!(%e, "error updating job {job_id_2}: {e:#}"); + Some(false) + }) + .unwrap_or_else(|| { + // if the job is not in queue, it can only be in the completed_job so it is already complete + false + }) + } + Connection::Http(_) => { + if let Err(e) = ping_job_status(&conn_2, &job_id_2, Some(mem_peak_actual), None).await { + tracing::error!(%e, "error pinging job {job_id_2}: {e:#}"); + } + false + } + }; if canceled { @@ -1986,7 +1756,7 @@ pub async fn handle_python_reqs( parallel_limit )); } - append_logs(&job_id, w_id, logs, db).await; + append_logs(&job_id, w_id, logs, conn).await; } let semaphore = Arc::new(Semaphore::new(parallel_limit)); @@ -1998,7 +1768,14 @@ pub async fn handle_python_reqs( let total_time = std::time::Instant::now(); let py_path = py_version - .get_python(job_id, mem_peak, db, _worker_name, w_id, _occupancy_metrics) + .try_get_python( + job_id, + mem_peak, + conn, + _worker_name, + w_id, + _occupancy_metrics, + ) .await?; let has_work = req_with_penv.len() > 0; @@ -2022,7 +1799,7 @@ pub async fn handle_python_reqs( "started setup python dependencies" ); - let db = db.clone(); + let conn = conn.clone(); let job_id = job_id.clone(); let job_dir = job_dir.to_owned(); let w_id = w_id.to_owned(); @@ -2033,6 +1810,10 @@ pub async fn handle_python_reqs( let py_path = py_path.clone(); let pids = pids.clone(); let worker_dir = worker_dir.clone(); + + #[cfg(all(feature = "enterprise", feature = "parquet", unix))] + let py_version = py_version.clone(); + handles.push(task::spawn(async move { // permit will be dropped anyway if this thread exits at any point // so we dont have to drop it manually @@ -2050,11 +1831,11 @@ pub async fn handle_python_reqs( let start = std::time::Instant::now(); #[cfg(all(feature = "enterprise", feature = "parquet", unix))] if is_not_pro { - if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { tokio::select! { // Cancel was called on the job _ = kill_rx.recv() => return Err(anyhow::anyhow!("S3 pull was canceled")), - pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level()) => { + pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level(false), None, false) => { if let Err(e) = pull { tracing::info!( workspace_id = %w_id, @@ -2071,13 +1852,13 @@ pub async fn handle_python_reqs( counter_arc, total_to_install, start, - db + &conn ).await; pids.lock().await.get_mut(i).and_then(|e| e.take()); // Create a file to indicate that installation was successfull let valid_path = venv_p.clone() + "/.valid.windmill"; - // This is atomic operation, meaning, that it either completes and wheel is valid, + // This is atomic operation, meaning, that it either completes and wheel is valid, // or it does not and wheel is invalid and will be reinstalled next run if let Err(e) = File::create(&valid_path).await{ tracing::error!( @@ -2110,7 +1891,7 @@ pub async fn handle_python_reqs( format!( "\nError while spawning proccess:\n{e}", ), - db, + &conn, ) .await; pids.lock().await.get_mut(i).and_then(|e| e.take()); @@ -2161,7 +1942,7 @@ pub async fn handle_python_reqs( "\nError while installing {}:\n{stderr_buf}", &req ), - db, + &conn, ) .await; pids.lock().await.get_mut(i).and_then(|e| e.take()); @@ -2198,14 +1979,22 @@ pub async fn handle_python_reqs( counter_arc, total_to_install, start, - db, // + &conn, // ) .await; #[cfg(all(feature = "enterprise", feature = "parquet", unix))] if s3_push { - if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { - tokio::spawn(build_tar_and_push(os, venv_p.clone(), py_version.to_cache_dir_top_level())); + // Send to upload channel for sequential processing + let upload_task = PiptarUploadTask { + venv_path: venv_p.clone(), + cache_dir: py_version.to_cache_dir_top_level(false), + }; + + if let Err(e) = PIPTAR_UPLOAD_CHANNEL.send(upload_task) { + tracing::warn!("Failed to queue piptar upload for {venv_p}: {e}"); + } else { + tracing::info!("Queued piptar upload for {venv_p}"); } } @@ -2220,7 +2009,7 @@ pub async fn handle_python_reqs( pids.lock().await.get_mut(i).and_then(|e| e.take()); // Create a file to indicate that installation was successfull let valid_path = venv_p.clone() + "/.valid.windmill"; - // This is atomic operation, meaning, that it either completes and wheel is valid, + // This is atomic operation, meaning, that it either completes and wheel is valid, // or it does not and wheel is invalid and will be reinstalled next run if let Err(e) = File::create(&valid_path).await{ tracing::error!( @@ -2252,7 +2041,13 @@ pub async fn handle_python_reqs( if has_work { let total_time = total_time.elapsed().as_millis(); - append_logs(&job_id, w_id, format!("\nenv set in {}ms", total_time), db).await; + append_logs( + &job_id, + w_id, + format!("\nenv set in {}ms", total_time), + conn, + ) + .await; } *mem_peak = *mem_peak_thread_safe.lock().await; @@ -2268,26 +2063,21 @@ pub async fn handle_python_reqs( }; } -fn split_requirements(requirements: &str) -> Vec<&str> { +pub fn split_requirements>(requirements: T) -> Vec { requirements - .split("\n") + .as_ref() + .lines() .filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty()) + .map(String::from) .collect() } -/// Check requirements/lockfile to figure out python version assigned to it. -fn get_pyv_from_requirements_lines(requirements_lines: &[&str]) -> PyVersion { - // If script is deployed we can try to parse first line to get assigned version - if let Some(v) = requirements_lines - .get(0) - .and_then(|line| PyVersion::parse_version(*line)) - { - // We have valid assigned version, we use it - v + +// Returns code snippet that needs to be injected into wrapper to post-process results or leave unprocessed +fn get_result_postprocessor<'a>(skip: bool) -> &'a str { + if skip { + "unprocessed" } else { - // If there is no assigned version in lockfile we automatically fallback to 3.11 - // In this case we have dependencies, but no associated python version - // This is the case for old deployed scripts - PyVersion::Py311 + "re.sub(replace_invalid_fields, ' null ', unprocessed)" } } @@ -2298,6 +2088,8 @@ use crate::{common::build_envs_map, dedicated_worker::handle_dedicated_process}; #[cfg(feature = "enterprise")] use windmill_common::variables; +use windmill_queue::MiniPulledJob; + #[cfg(feature = "enterprise")] pub async fn start_worker( requirements_o: Option<&String>, @@ -2311,13 +2103,15 @@ pub async fn start_worker( script_path: &str, token: &str, job_completed_tx: JobCompletedSender, - jobs_rx: tokio::sync::mpsc::Receiver>, + jobs_rx: tokio::sync::mpsc::Receiver>, killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> error::Result<()> { + use crate::{PyV, PyVAlias}; + let mut mem_peak: i32 = 0; let mut canceled_by: Option = None; let context = variables::get_reserved_variables( - db, + &Connection::Sql(db.clone()), w_id, &token, "dedicated_worker@windmill.dev", @@ -2331,11 +2125,11 @@ pub async fn start_worker( None, None, None, - None, ) .await .to_vec(); + let annotations = PythonAnnotations::parse(inner_content); let context_envs = build_envs_map(context).await; let (_, additional_python_paths) = handle_python_deps( job_dir, @@ -2344,12 +2138,14 @@ pub async fn start_worker( w_id, script_path, &Uuid::nil(), - db, + &Connection::Sql(db.clone()), worker_name, job_dir, &mut mem_peak, &mut canceled_by, &mut None, + None, + annotations, ) .await?; @@ -2367,6 +2163,7 @@ pub async fn start_worker( ) = prepare_wrapper(job_dir, false, None, None, inner_content, script_path).await?; { + let postprocessor = get_result_postprocessor(annotations.skip_result_postprocessing); let indented_transforms = transforms .lines() .map(|x| format!(" {}", x)) @@ -2418,7 +2215,8 @@ for line in sys.stdin: for k, v in res.items(): if type(v).__name__ == 'bytes': res[k] = to_b_64(v) - res_json = re.sub(replace_invalid_fields, ' null ', json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')) + unprocessed = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '') + res_json = {postprocessor} sys.stdout.write("wm_res[success]:" + res_json + "\n") except BaseException as e: exc_type, exc_value, exc_traceback = sys.exc_info() @@ -2432,7 +2230,7 @@ for line in sys.stdin: } let reserved_variables = windmill_common::variables::get_reserved_variables( - db, + &Connection::Sql(db.clone()), w_id, token, "dedicated_worker", @@ -2446,7 +2244,6 @@ for line in sys.stdin: None, None, None, - None, ) .await; @@ -2462,22 +2259,22 @@ for line in sys.stdin: proc_envs.insert("BASE_URL".to_string(), base_internal_url.to_string()); let py_version = if let Some(requirements) = requirements_o { - get_pyv_from_requirements_lines(&split_requirements(requirements.as_str())) + PyV::parse_from_requirements(&split_requirements(requirements.as_str())) } else { tracing::warn!(workspace_id = %w_id, "lockfile is empty for dedicated worker, thus python version cannot be inferred. Fallback to 3.11"); - PyVersion::Py311 + PyVAlias::Py311.into() }; - let python_path = get_python_path( - py_version, - worker_name, - &Uuid::nil(), - w_id, - &mut mem_peak, - db, - &mut None, - ) - .await?; + let python_path = py_version + .get_python( + worker_name, + &Uuid::nil(), + w_id, + &mut mem_peak, + &Connection::Sql(db.clone()), + &mut None, + ) + .await?; handle_dedicated_process( &python_path, job_dir, @@ -2497,3 +2294,4 @@ for line in sys.stdin: ) .await } + diff --git a/backend/windmill-worker/src/python_versions.rs b/backend/windmill-worker/src/python_versions.rs new file mode 100644 index 0000000000..2f12d34dc6 --- /dev/null +++ b/backend/windmill-worker/src/python_versions.rs @@ -0,0 +1,1019 @@ +use std::{ + ops::{Deref, DerefMut}, + process::Stdio, + str::FromStr, + sync::Arc, +}; + +use chrono::{DateTime, Duration, Utc}; +use itertools::Itertools; +use serde_json::Value; +use tokio::{fs::DirBuilder, process::Command, sync::RwLock}; +use uuid::Uuid; +use windmill_common::{ + error::{self, Error}, + worker::Connection, +}; + +use anyhow::{anyhow, bail}; +use windmill_queue::append_logs; + +use crate::{ + common::{start_child_process, OccupancyMetrics}, + handle_child::handle_child, + python_executor::{PYTHON_PATH, UV_PATH}, + worker_lockfiles::LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT, + HOME_ENV, INSTANCE_PYTHON_VERSION, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, WIN_ENVS, +}; + +#[derive(Eq, PartialEq, Clone, Copy, Default, Debug)] +#[repr(u32)] +pub enum PyVAlias { + Py310 = 10, + #[default] + Py311, + Py312, + Py313, +} + +impl Into for PyVAlias { + fn into(self) -> pep440_rs::Version { + pep440_rs::Version::new([self.major() as u64, self as u64]) + } +} + +impl Into for PyVAlias { + fn into(self) -> u32 { + self.major() * 100 + self as u32 + } +} + +impl From for PyVAlias { + fn from(value: PyV) -> Self { + match value.release() { + [major, minor, ..] => { + if let Some(alias) = Self::try_from_v1(format!("{}{}", *major, *minor)) { + return alias; + } + } + _ => (), + } + + tracing::warn!( + "Failed to convert Python Full Version to Alias. Fallback to default ({})", + *PyV::default() + ); + Self::default() + } +} +impl PyVAlias { + fn all>() -> Vec { + use PyVAlias::*; + vec![Py310.into(), Py311.into(), Py312.into(), Py313.into()] + } + // Get MAJOR part of alias. (semver: MAJOR.MINOR.PATCH) + fn major(&self) -> u32 { + use PyVAlias::*; + match self { + Py310 | Py311 | Py312 | Py313 => 3, + // Py400 | Py401 => 4 + } + } + + /// Converts numeric format to alias + /// Example: + /// 310u32 (in) -> PyVAlias::Py310 (out) + pub(crate) fn try_from_v1(numeric: T) -> Option { + use PyVAlias::*; + match numeric.to_string().as_str() { + "310" => Some(Py310), + "311" => Some(Py311), + "312" => Some(Py312), + "313" => Some(Py313), + _ => None, + } + } +} + +// To change latest stable version: +// 1. Change placeholder in instanceSettings.ts +// 2. Change LATEST_STABLE_PY in dockerfile +// 3. Change #[default] annotation for PyVersion in backend +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PyV(pub pep440_rs::Version); + +impl From for PyV { + fn from(value: pep440_rs::Version) -> Self { + Self(value) + } +} + +impl From for PyV { + fn from(value: PyVAlias) -> Self { + Self(value.into()) + } +} + +impl Default for PyV { + fn default() -> Self { + PyVAlias::default().into() + } +} + +impl Deref for PyV { + type Target = pep440_rs::Version; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for PyV { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl PyV { + pub async fn resolve( + version_specifiers: Vec, + job_id: &Uuid, + w_id: &str, + select_latest: bool, + // Needed for logs but optional + conn: Option, + // Usually for testing + custom_versions: Option>, + // For testing + gravitational_version: Option, + ) -> Result { + // Get all versions that can be fetched + let all_versions = custom_versions.unwrap_or(PyV::list_available_python_versions().await); + + // Narrow down to those that satisfy given version specifiers + let valid = all_versions + .clone() + .into_iter() + .filter(|v| version_specifiers.iter().all(|vs| (vs).contains(&*v))) + .collect_vec(); + + if !valid.is_empty() { + let mut result = valid[0].clone(); + // Is there at least one version specifier that has PATCH digit? + let patch_vs = version_specifiers + .iter() + .any(|vs| vs.version().release().get(2).is_some()); + + if select_latest { + return Ok(result.clone()); + } + + // Usually INSTANCE_PYTHON_VERSION + let gv = gravitational_version + .unwrap_or(PyV::gravitational_version(job_id, w_id, conn).await); + + // Will be used to determine if picked version matches gravity version + // Once first match occure, we will stop iterating + let gravity_matcher = pep440_rs::VersionSpecifier::from_version( + pep440_rs::Operator::EqualStar, + (*gv).clone(), + ) + .map_err(|e| { + Error::ArgumentErr(format!( + "{e}\nLikely means INSTANCE_PYTHON_VERSION is set incorrectly." + )) + })?; + + // Reminder of semver: MAJOR.MINOR.PATCH + // + // - Go from up to down + // - We will iterate until find the closest version to target. + // - If closest version has the same MINOR version, use it. + // - If it differs in MINOR version, take latest PATCH version. + + let [major, minor, ..] = result.release() else { + return Err(Error::InternalErr(format!("Failed to parse \"{}\". Available python versions are supposed to be in SEMVER format (MAJOR.MINOR)", *result))); + }; + // This represents newest version with oldest MINOR: + // + // I Iterable Newest in MINOR + // 1. 3.11.2 -> 3.11.2 + // 2. 3.11.1 -> 3.11.2 + // 3. 3.11.0 -> 3.11.2 + // 4. 3.10.2 -> 3.10.2 + // 5. 3.10.1 -> 3.10.2 + // 6. 3.10.0 -> 3.10.2 + let mut newest_in_minor = (result.clone(), (*major, *minor)); + + for v in valid.iter() { + if v < &gv { + // We will not continue if we start looking into versions older than gravity version. + break; + } + + let [major, minor, ..] = v.release() else { + return Err(Error::InternalErr(format!("Failed to parse \"{}\". Available python versions are supposed to be in SEMVER format (MAJOR.MINOR)", **v))); + }; + + // Since we go top to down we can assume + // the first occurence of new minor version contains the latest patch version. + if newest_in_minor.1 != (*major, *minor) { + newest_in_minor = (v.clone(), (*major, *minor)); + } + + if gravity_matcher.contains(v) { + // return as soon as gravity matcher has first hit. + // Only in case version specifiers do specify PATCH version OR gravity version specify PATCH + if patch_vs || gv.release().get(2).is_some() { + return Ok(v.clone()); + } else { + let Some(release_numbers) = v.release().get(0..=1) else { + return Err(Error::InternalErr(format!( + "Failed to get release numbers from: \"{}\". ", + **v + ))); + }; + return Ok(PyV(pep440_rs::Version::new(release_numbers))); + } + } + // If we are still in the loop, it means that we are getting closer to gravity version + else { + result = v.clone(); + } + } + + let [gravity_major, gravity_minor, ..] = gv.release() else { + return Err(Error::internal_err(format!("Cannot get MAJOR nor MINOR version of python gravity version ({}). Something might be wrong with INSTANCE_PYTHON_VERSION.", &*gv))); + }; + + if (*gravity_major, *gravity_minor) != newest_in_minor.1 { + // Return full version only if there is PATCH versions in version specifiers + if patch_vs { + return Ok(newest_in_minor.0); + } else { + let mm = newest_in_minor.1; + return Ok(PyV(pep440_rs::Version::new([mm.0, mm.1]))); + } + } + + Ok(result) + } else { + Err(anyhow!( + " + × No solution found when resolving python: + ╰─▶ Because you require python {}, we can conclude that your requirements are unsatisfiable. + + All versions: \n{} + \n", + version_specifiers.iter().map(|s| s.to_string()).join(", "), + all_versions + .iter() + .enumerate() + .map(|(i, v)| format!( + "{}{}", + windmill_common::worker::pad_string(&v.0.to_string(), 11), + if (i + 1) % 5 == 0 { "\n" } else { "" } + )) + .collect::() + ) + .into()) + } + } + /// e.g.: `/tmp/windmill/cache/python_3_x_y` + pub(crate) fn to_cache_dir(&self, ignore_patch: bool) -> String { + use windmill_common::worker::ROOT_CACHE_DIR; + format!( + "{ROOT_CACHE_DIR}{}", + self.to_cache_dir_top_level(ignore_patch) + ) + } + + /// e.g.: `python_3_x_y` + pub fn to_cache_dir_top_level(&self, ignore_patch: bool) -> String { + if ignore_patch { + if let [major, minor, ..] = self.release() { + return format!("python_{major}_{minor}"); + } + + tracing::warn!("failed to parse python's ({}) top level directory with no patch digit, fallback to full version.", self.to_string()); + } + format!("python_{}", self.to_string().replace(".", "_")) + } + + pub async fn gravitational_version( + job_id: &Uuid, + w_id: &str, + conn: Option, + ) -> Self { + let mut err = None; + let pyv = match INSTANCE_PYTHON_VERSION.read().await.clone() { + Some(v) if &v == "default" => PyVAlias::default().into(), + Some(v) => pep440_rs::Version::from_str(&v).unwrap_or_else(|_| { + let v = PyVAlias::default().into(); + err = Some(format!("\nCannot parse INSTANCE_PYTHON_VERSION ({:?}), fallback to latest_stable ({v:?})", *INSTANCE_PYTHON_VERSION)); + v + }), + // Use latest stable + None => PyVAlias::default().into(), + }; + + if let Some(msg) = err { + if let Some(conn) = conn { + append_logs(job_id, w_id, &msg, &conn).await; + } + tracing::error!(msg); + } + pyv.into() + } + + pub async fn list_available_python_versions() -> Vec { + match Self::list_available_python_versions_inner().await { + Ok(pyvs) => pyvs, + Err(e) => { + tracing::error!( + "Fallback to preconfigured aliases. Cannot list python versions due to this error: {e}" + ); + PyVAlias::all() + } + } + } + async fn list_available_python_versions_inner() -> anyhow::Result> { + lazy_static::lazy_static! { + static ref CACHED_VERSIONS: Arc>>> = Arc::new(RwLock::new(None)); + static ref LAST_CHECKED: Arc>> = Arc::new(RwLock::new(Utc::now())); + } + match ( + Utc::now().signed_duration_since(*LAST_CHECKED.read().await) > Duration::minutes(30), + CACHED_VERSIONS.read().await.clone(), + ) { + (false, Some(vs)) => return Ok(vs), + _ => {} + }; + + let output = { + #[cfg(windows)] + let uv_cmd = "uv"; + + #[cfg(unix)] + let uv_cmd = UV_PATH.as_str(); + + Command::new(uv_cmd) + .env_clear() + .envs(WIN_ENVS.to_vec()) + .args([ + "python", + "list", + "--all-versions", + "--output-format", + "json", + ]) + .stderr(Stdio::piped()) + .output() + .await? + }; + + // We want to skip all versions smaller then 3.10 + // Windmill is incompatible with 3.9 and older + let filter = pep440_rs::VersionSpecifier::from_version( + pep440_rs::Operator::GreaterThanEqual, + PyVAlias::Py310.into(), + )?; + + if output.status.success() { + let res = String::from_utf8(output.stdout)?; + tracing::error!("{}", &res); + let list = serde_json::from_str::>>(&res)? + .into_iter() + .filter_map(|e| { + if e.get("implementation").and_then(Value::as_str) == Some("pypy") { + None + } else { + Some( + e.get("version") + .and_then(Value::as_str) + .and_then(|s| pep440_rs::Version::from_str(s).ok()) + .map(PyV::from) + .ok_or(Error::internal_err("version is None")), + ) + } + }) + .collect::, Error>>()? + .into_iter() + .unique() + .sorted() + .filter(|pyv| filter.contains(&*pyv)) + .rev() + .collect_vec(); + + *LAST_CHECKED.write().await = Utc::now(); + CACHED_VERSIONS.write().await.replace(list.clone()); + + Ok(list) + } else { + // If the command failed, print the error + let stderr = String::from_utf8(output.stderr)?; + bail!( + "Cannot list python versions, is uv (0.5.19 and newer) installed? Err:\n{}", + stderr + ); + } + } + + /// Parse lockfile for assigned python version. + /// If not found returns 3.11 + pub fn parse_from_requirements>(requirements_lines: &[S]) -> Self { + Self::try_parse_from_requirements(requirements_lines).unwrap_or( + // If there is no assigned version in lockfile we automatically fallback to 3.11 + // In this case we have dependencies or other metadata, but no associated python version + // This is the case for old deployed scripts + PyVAlias::Py311.into(), + ) + } + + /// Parse lockfile for assigned python version. + /// If not found returns None + pub fn try_parse_from_requirements>(requirements_lines: &[S]) -> Option { + let parse_version = |s: &str| -> Option { + // Possible inputs: + // V2: + // # py: 3.11.0 or #py:3.11.0 or #py: 3.11.0 + // + // V1: + // # py311 or #py311 + let version_unparsed = s + .to_owned() + // Remove whitespaces. That leaves us with: + // V2: #py:3.11.0 + // V1: #py311 + // + // Remove # + // V2: py:3.11.0 + // V1: py311 + // + // Remove : + // V2: py3.11.0 + // V1: py311 + .replace([' ', '#', ':'], "") + // Remove "py" + // V2: 3.11.0 + // V1: 311 + .replace("py", ""); + + // We will support reading V1 syntax, but it will be overwritten next deploy + PyVAlias::try_from_v1(&version_unparsed) + .map(PyVAlias::into) + .or(pep440_rs::Version::from_str(&version_unparsed) + .ok() + .map(pep440_rs::Version::into)) + }; + let index = if requirements_lines.get(0).map_or(false, |line| { + line.as_ref() + .starts_with(LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT) + }) { + 1 + } else { + 0 + }; + requirements_lines + .get(index) + .map(S::as_ref) + .and_then(parse_version) + } + + pub async fn get_python( + &self, + worker_name: &str, + job_id: &Uuid, + w_id: &str, + mem_peak: &mut i32, + conn: &Connection, + occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + ) -> windmill_common::error::Result { + let python_path = if let Some(python_path) = PYTHON_PATH.clone() { + python_path + } else if let Some(python_path) = self + .try_get_python( + &job_id, + mem_peak, + conn, + worker_name, + w_id, + occupancy_metrics, + ) + .await? + { + python_path + } else { + return Err(Error::ExecutionErr(format!( + "uv could not manage python path. Please manage it manually by setting PYTHON_PATH environment variable to your python binary path" + ))); + }; + Ok(python_path) + } + + pub async fn try_get_python( + &self, + job_id: &Uuid, + mem_peak: &mut i32, + // canceled_by: &mut Option, + conn: &Connection, + worker_name: &str, + w_id: &str, + occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + ) -> error::Result> { + // lazy_static::lazy_static! { + // static ref PYTHON_PATHS: Arc>> = Arc::new(RwLock::new(HashMap::new())); + // } + + let res = self + .get_python_inner(job_id, mem_peak, conn, worker_name, w_id, occupancy_metrics) + .await; + + if let Err(ref e) = res { + tracing::error!( + "worker_name: {worker_name}, w_id: {w_id}, job_id: {job_id}\n + Error while getting python from uv, falling back to system python: {e:?}" + ); + append_logs( + job_id, + w_id, + format!( + "\nError while getting python from uv, falling back to system python: {e:?}" + ), + conn, + ) + .await; + } + res + } + async fn get_python_inner( + &self, + job_id: &Uuid, + mem_peak: &mut i32, + // canceled_by: &mut Option, + conn: &Connection, + worker_name: &str, + w_id: &str, + occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + ) -> error::Result> { + let py_path = self.find_python().await; + + // Runtime is not installed + if py_path.is_err() { + // Install it + if let Err(err) = self + .install_python(job_id, mem_peak, conn, worker_name, w_id, occupancy_metrics) + .await + { + tracing::error!("Cannot install python: {err}"); + return Err(err); + } else { + // Try to find one more time + let py_path = self.find_python().await; + + if let Err(err) = py_path { + tracing::error!("Cannot find python version {err}"); + return Err(err); + } + + // TODO: Cache the result + py_path + } + } else { + py_path + } + } + async fn install_python( + &self, + job_id: &Uuid, + mem_peak: &mut i32, + // canceled_by: &mut Option, + conn: &Connection, + worker_name: &str, + w_id: &str, + occupancy_metrics: &mut Option<&mut OccupancyMetrics>, + ) -> error::Result<()> { + let v = self.to_string(); + append_logs(job_id, w_id, format!("\nINSTALLING PYTHON ({})", v), conn).await; + // Create dirs for newly installed python + // If we dont do this, NSJAIL will not be able to mount cache + // For the default version directory created during startup (main.rs) + DirBuilder::new() + .recursive(true) + .create(self.to_cache_dir(false)) + .await + .expect("could not create initial worker dir"); + + let logs = String::new(); + + #[cfg(windows)] + let uv_cmd = "uv"; + + #[cfg(unix)] + let uv_cmd = UV_PATH.as_str(); + + let mut child_cmd = Command::new(uv_cmd); + child_cmd + .env_clear() + .env("HOME", HOME_ENV.to_string()) + .env("PATH", PATH_ENV.to_string()) + .envs(PROXY_ENVS.clone()) + .args(["python", "install", &v, "--python-preference=only-managed"]) + // TODO: Do we need these? + .envs([("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR)]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + child_cmd + .env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ) + .env( + "LOCALAPPDATA", + std::env::var("LOCALAPPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), + ); + } + + let child_process = start_child_process(child_cmd, "uv").await?; + + append_logs(&job_id, &w_id, logs, conn).await; + handle_child( + job_id, + conn, + mem_peak, + &mut None, + child_process, + false, + worker_name, + &w_id, + "uv", + None, + false, + occupancy_metrics, + None, + ) + .await + } + async fn find_python(&self) -> error::Result> { + #[cfg(windows)] + let uv_cmd = "uv"; + + #[cfg(unix)] + let uv_cmd = UV_PATH.as_str(); + + let mut child_cmd = Command::new(uv_cmd); + + child_cmd.env_clear(); + + #[cfg(windows)] + { + child_cmd + .env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ) + .env( + "LOCALAPPDATA", + std::env::var("LOCALAPPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), + ); + } + + let output = child_cmd + // .current_dir(job_dir) + .env("HOME", HOME_ENV.to_string()) + .env("PATH", PATH_ENV.to_string()) + .args([ + "python", + "find", + &self.to_string(), + "--system", + "--python-preference=only-managed", + ]) + .envs([ + ("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR), + ("UV_PYTHON_PREFERENCE", "only-managed"), + ]) + // .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await?; + + // Check if the command was successful + if output.status.success() { + // Convert the output to a String + let stdout = + String::from_utf8(output.stdout).expect("Failed to convert output to String"); + return Ok(Some(stdout.replace('\n', ""))); + } else { + // If the command failed, print the error + let stderr = + String::from_utf8(output.stderr).expect("Failed to convert error output to String"); + return Err(error::Error::FindPythonError(stderr)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Unsafe helper for testing + fn pyv(value: &str) -> PyV { + pep440_rs::Version::from_str(value).unwrap().into() + } + + async fn assert_resolution( + instance_version: &str, + select_highest: bool, + specifiers: Vec<&str>, + available: Vec, + expected: PyV, + ) { + let resolved = PyV::resolve( + specifiers + .into_iter() + .map(|s| pep440_rs::VersionSpecifier::from_str(s).unwrap()) + .collect_vec(), + &Uuid::nil(), + "", + select_highest, + None, + Some(available), + Some(pyv(instance_version)), + ) + .await + .unwrap(); + assert_eq!(expected, resolved); + } + + #[tokio::test] + async fn test_python_resolution_1() { + assert_resolution( + "1.0", + false, + vec![], + vec![ + pyv("1.2.0"), + pyv("1.1.0"), + pyv("1.0.0"), + pyv("0.9.0"), // + ], + pyv("1.0.0"), // + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_2() { + assert_resolution( + "1.0.0", + false, + vec!["!=1.*"], + vec![ + pyv("1.2"), + pyv("1.1"), + pyv("1.0.2"), + pyv("1.0.1"), + pyv("1.0.0"), + pyv("0.9.4"), + pyv("0.9.3"), + pyv("0.9.2"), + ], + pyv("0.9"), // + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_3() { + assert_resolution( + "0.9", + false, + vec!["!=0.9.*"], + vec![ + pyv("1.2"), + pyv("1.1"), + pyv("1.0.2"), + pyv("1.0.1"), + pyv("1.0.0"), + pyv("0.9.4"), + pyv("0.9.3"), + pyv("0.9.2"), + pyv("0.8.2"), + pyv("0.8.1"), + pyv("0.8.0"), + ], + pyv("1.0"), // + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_4() { + assert_resolution( + "0.9", + false, + vec!["<=0.8.1"], + vec![pyv("1.0.0"), pyv("0.9.0"), pyv("0.8.1"), pyv("0.8.0")], + pyv("0.8.1"), // + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_5() { + assert_resolution( + "0.0.1", + false, + vec!["!=0.1.0"], + vec![pyv("2.1.0"), pyv("1.1.0"), pyv("0.1.0")], + pyv("1.1.0"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_6() { + assert_resolution( + "1.1.1", + false, + vec![], + vec![ + pyv("3.0.1"), + pyv("3.0.0"), + pyv("2.2.2"), + pyv("2.2.1"), + pyv("2.2.0"), + ], + pyv("2.2"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_7() { + assert_resolution( + "2.2.1", + true, + vec![], + vec![ + pyv("3.0.1"), + pyv("3.0.0"), + pyv("2.2.2"), + pyv("2.2.1"), + pyv("2.2.0"), + ], + pyv("3.0.1"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_8() { + assert_resolution( + "2.2", + false, + vec![">2.2", ">=2.4", "<2.4.1"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + pyv("2.4.0"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_9() { + assert_resolution( + "2.2", + true, + vec![">2.2", ">2.3", "<2.4.1"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + pyv("2.4.0"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_10() { + assert_resolution( + "2.2", + false, + vec![">2.2", ">=2.4"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + // vec![pyv("2.4.1"), pyv("2.3"), pyv("2.2"), pyv("2.1")], + pyv("2.4"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_11() { + assert_resolution( + "2.4.1", + false, + vec![">2.2", ">=2.3", "<2.4"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + // vec![pyv("2.4.1"), pyv("2.3"), pyv("2.2"), pyv("2.1")], + pyv("2.3"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_12() { + assert_resolution( + "2.3", + false, + vec![">2.2", ">=2.3", "<2.4"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + // vec![pyv("2.4.1"), pyv("2.3"), pyv("2.2"), pyv("2.1")], + pyv("2.3"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_13() { + assert_resolution( + "2.3.1", + false, + vec![">2.2", ">=2.3", "<2.4"], + vec![ + pyv("2.4.1"), + pyv("2.4.0"), + pyv("2.3.1"), + pyv("2.3.0"), + pyv("2.2.1"), + pyv("2.2.0"), + pyv("2.1.1"), + pyv("2.1.0"), + ], + // vec![pyv("2.4.1"), pyv("2.3"), pyv("2.2"), pyv("2.1")], + pyv("2.3.1"), + ) + .await; + } + #[tokio::test] + async fn test_python_resolution_14() { + assert_resolution( + "2.3.0", + false, + vec![], + vec![pyv("2.4.1"), pyv("2.4.0"), pyv("2.3.1")], + pyv("2.3.1"), + ) + .await; + } + + #[tokio::test] + async fn test_python_resolution_16() { + assert_resolution( + "2.3", + false, + vec![], + vec![pyv("2.4.1"), pyv("2.4.0"), pyv("2.3.1")], + pyv("2.3"), + ) + .await; + } +} diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index bb4c1937ae..5056851389 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -2,7 +2,7 @@ use opentelemetry::trace::FutureExt; use serde::Serialize; -use sqlx::{types::Json, Pool, Postgres}; +use sqlx::types::Json; use std::{ collections::HashMap, sync::{ @@ -12,46 +12,121 @@ use std::{ }; use tracing::{field, Instrument}; #[cfg(not(feature = "otel"))] -use windmill_common::otel_ee::FutureExt; +use windmill_common::otel_oss::FutureExt; use uuid::Uuid; use windmill_common::{ add_time, error::{self, Error}, - jobs::{JobKind, QueuedJob}, + jobs::JobKind, utils::WarnAfterExt, - worker::{to_raw_value, WORKER_GROUP}, - DB, + worker::{to_raw_value, Connection, WORKER_GROUP}, + KillpillSender, DB, }; #[cfg(feature = "benchmark")] -use crate::bench::{BenchmarkInfo, BenchmarkIter}; +use windmill_common::bench::{BenchmarkInfo, BenchmarkIter}; -use windmill_queue::{append_logs, get_queued_job, CanceledBy, WrappedError}; - -use serde_json::{json, value::RawValue}; - -use tokio::{ - sync::{ - self, - mpsc::{Receiver, Sender}, - }, - task::JoinHandle, +use windmill_queue::{ + append_logs, get_queued_job, CanceledBy, JobCompleted, MiniPulledJob, WrappedError, }; +use serde_json::{json, value::RawValue, Value}; + +use tokio::task::JoinHandle; + use windmill_queue::{add_completed_job, add_completed_job_error}; use crate::{ bash_executor::ANSI_ESCAPE_RE, - common::{read_result, save_in_cache}, + common::{error_to_value, read_result, save_in_cache}, + otel_oss::add_root_flow_job_to_otlp, worker_flow::update_flow_status_after_job_completion, - AuthedClient, JobCompleted, JobCompletedSender, SameWorkerSender, SendResult, INIT_SCRIPT_TAG, + JobCompletedReceiver, JobCompletedSender, SameWorkerSender, SendResult, UpdateFlow, + INIT_SCRIPT_TAG, SAME_WORKER_REQUIREMENTS, }; +use windmill_common::client::AuthedClient; + +async fn process_jc( + jc: JobCompleted, + worker_name: &str, + base_internal_url: &str, + db: &DB, + worker_dir: &str, + same_worker_tx: Option<&SameWorkerSender>, + job_completed_sender: &JobCompletedSender, + #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, +) { + let success: bool = jc.success; + + let span = tracing::span!( + tracing::Level::INFO, + "job_postprocessing", + job_id = %jc.job.id, root_job = field::Empty, workspace_id = %jc.job.workspace_id, worker = %worker_name,tag = %jc.job.tag, + // hostname = %hostname, + language = field::Empty, + script_path = field::Empty, + flow_step_id = field::Empty, + parent_job = field::Empty, + otel.name = field::Empty + ); + let rj = if let Some(root_job) = jc.job.flow_innermost_root_job { + root_job + } else { + jc.job.id + }; + windmill_common::otel_oss::set_span_parent(&span, &rj); + + if let Some(lg) = jc.job.script_lang.as_ref() { + span.record("language", lg.as_str()); + } + if let Some(step_id) = jc.job.flow_step_id.as_ref() { + span.record( + "otel.name", + format!("job_postprocessing {}", step_id).as_str(), + ); + span.record("flow_step_id", step_id.as_str()); + } else { + span.record("otel.name", "job postprocessing"); + } + if let Some(parent_job) = jc.job.parent_job.as_ref() { + span.record("parent_job", parent_job.to_string().as_str()); + } + if let Some(script_path) = jc.job.runnable_path.as_ref() { + span.record("script_path", script_path.as_str()); + } + if let Some(root_job) = jc.job.flow_innermost_root_job.as_ref() { + span.record("root_job", root_job.to_string().as_str()); + } + + let root_job = handle_receive_completed_job( + jc, + &base_internal_url, + &db, + worker_dir, + same_worker_tx, + &worker_name, + job_completed_sender.clone(), + #[cfg(feature = "benchmark")] + bench, + ) + .instrument(span) + .await; + + if let Some(root_job) = root_job { + add_root_flow_job_to_otlp(&root_job, success); + } +} + +enum JobCompletedRx { + JobCompleted(SendResult), + Killpill, +} pub fn start_background_processor( - mut job_completed_rx: Receiver, - job_completed_sender: Sender, + job_completed_rx: JobCompletedReceiver, + job_completed_sender: JobCompletedSender, same_worker_queue_size: Arc, job_completed_processor_is_done: Arc, base_internal_url: String, @@ -59,98 +134,69 @@ pub fn start_background_processor( worker_dir: String, same_worker_tx: SameWorkerSender, worker_name: String, - killpill_tx: sync::broadcast::Sender<()>, + killpill_tx: KillpillSender, is_dedicated_worker: bool, ) -> JoinHandle<()> { tokio::spawn(async move { let mut has_been_killed = false; + let JobCompletedReceiver { bounded_rx, mut killpill_rx, unbounded_rx } = job_completed_rx; + #[cfg(feature = "benchmark")] let mut infos = BenchmarkInfo::new(); //if we have been killed, we want to drain the queue of jobs while let Some(sr) = { if has_been_killed && same_worker_queue_size.load(Ordering::SeqCst) == 0 { - job_completed_rx.try_recv().ok() + unbounded_rx + .try_recv() + .ok() + .map(JobCompletedRx::JobCompleted) + .or_else(|| bounded_rx.try_recv().ok().map(JobCompletedRx::JobCompleted)) } else { - job_completed_rx.recv().await + tokio::select! { + biased; + result = unbounded_rx.recv_async() => { + result.ok().map(JobCompletedRx::JobCompleted) + } + result = bounded_rx.recv_async() => { + result.ok().map(JobCompletedRx::JobCompleted) + } + + _ = killpill_rx.recv() => { + Some(JobCompletedRx::Killpill) + } + } } } { #[cfg(feature = "benchmark")] let mut bench = BenchmarkIter::new(); match sr { - SendResult::JobCompleted(jc) => { + JobCompletedRx::JobCompleted(SendResult::JobCompleted(jc)) => { let is_init_script_and_failure = !jc.success && jc.job.tag.as_str() == INIT_SCRIPT_TAG; let is_dependency_job = matches!( - jc.job.job_kind, + jc.job.kind, JobKind::Dependencies | JobKind::FlowDependencies ); - let success = jc.success; - - let span = tracing::span!( - tracing::Level::INFO, - "job_postprocessing", - job_id = %jc.job.id, root_job = field::Empty, workspace_id = %jc.job.workspace_id, worker = %worker_name,tag = %jc.job.tag, - // hostname = %hostname, - language = field::Empty, - script_path = field::Empty, - flow_step_id = field::Empty, - parent_job = field::Empty, - otel.name = field::Empty - ); - let rj = if let Some(root_job) = jc.job.root_job { - root_job - } else { - jc.job.id - }; - windmill_common::otel_ee::set_span_parent(&span, &rj); - - if let Some(lg) = jc.job.language.as_ref() { - span.record("language", lg.as_str()); - } - if let Some(step_id) = jc.job.flow_step_id.as_ref() { - span.record( - "otel.name", - format!("job_postprocessing {}", step_id).as_str(), - ); - span.record("flow_step_id", step_id.as_str()); - } else { - span.record("otel.name", "job postprocessing"); - } - if let Some(parent_job) = jc.job.parent_job.as_ref() { - span.record("parent_job", parent_job.to_string().as_str()); - } - if let Some(script_path) = jc.job.script_path.as_ref() { - span.record("script_path", script_path.as_str()); - } - if let Some(root_job) = jc.job.root_job.as_ref() { - span.record("root_job", root_job.to_string().as_str()); - } - - let root_job = handle_receive_completed_job( + process_jc( jc, + &worker_name, &base_internal_url, &db, &worker_dir, - &same_worker_tx, - &worker_name, - job_completed_sender.clone(), + Some(&same_worker_tx), + &job_completed_sender, #[cfg(feature = "benchmark")] &mut bench, ) - .instrument(span) .await; - if let Some(root_job) = root_job { - windmill_common::otel_ee::add_root_flow_job_to_otlp(&root_job, success); - } - if is_init_script_and_failure { tracing::error!("init script errored, exiting"); - killpill_tx.send(()).unwrap_or_default(); + killpill_tx.send(); break; } if is_dependency_job && is_dedicated_worker { @@ -162,7 +208,7 @@ pub fn start_background_processor( .execute(&db) .await .expect("update config to trigger restart of all dedicated workers at that config"); - killpill_tx.send(()).unwrap_or_default(); + killpill_tx.send(); } add_time!(bench, "job completed processed"); @@ -171,7 +217,7 @@ pub fn start_background_processor( infos.add_iter(bench, true); } } - SendResult::UpdateFlow { + JobCompletedRx::JobCompleted(SendResult::UpdateFlow(UpdateFlow { flow, w_id, success, @@ -179,24 +225,24 @@ pub fn start_background_processor( worker_dir, stop_early_override, token, - } => { + })) => { // let r; tracing::info!(parent_flow = %flow, "updating flow status"); if let Err(e) = update_flow_status_after_job_completion( &db, - &AuthedClient { - base_internal_url: base_internal_url.to_string(), - workspace: w_id.clone(), - token: token.clone(), - force_client: None, - }, + &AuthedClient::new( + base_internal_url.to_string(), + w_id.clone(), + token.clone(), + None, + ), flow, &Uuid::nil(), &w_id, success, Arc::new(result), true, - same_worker_tx.clone(), + &same_worker_tx, &worker_dir, stop_early_override, &worker_name, @@ -209,7 +255,7 @@ pub fn start_background_processor( tracing::error!("Error updating flow status after job completion for {flow} on {worker_name}: {e:#}"); } } - SendResult::Kill => { + JobCompletedRx::Killpill => { has_been_killed = true; } } @@ -228,76 +274,46 @@ pub fn start_background_processor( }) } -async fn send_job_completed( - job_completed_tx: JobCompletedSender, - job: Arc, - result: Arc>, - result_columns: Option>, - mem_peak: i32, - canceled_by: Option, - success: bool, - cached_res_path: Option, - token: String, - duration: Option, -) { - let jc = JobCompleted { - job, - result, - result_columns, - mem_peak, - canceled_by, - success, - cached_res_path, - token, - duration, - }; +async fn send_job_completed(job_completed_tx: JobCompletedSender, jc: JobCompleted) { job_completed_tx - .send(jc) - .with_context(windmill_common::otel_ee::otel_ctx()) + .send_job(jc, true) + .with_context(windmill_common::otel_oss::otel_ctx()) .await .expect("send job completed") } pub async fn process_result( - job: Arc, + job: Arc, result: error::Result>>, job_dir: &str, job_completed_tx: JobCompletedSender, mem_peak: i32, canceled_by: Option, cached_res_path: Option, - token: String, - column_order: Option>, - new_args: Option>>, - db: &DB, + token: &str, + result_columns: Option>, + preprocessed_args: Option>>, + conn: &Connection, duration: Option, ) -> error::Result { match result { - Ok(r) => { - // Update script args to preprocessed args - if let Some(preprocessed_args) = new_args { - sqlx::query!( - "UPDATE v2_job SET args = $1, preprocessed = TRUE WHERE id = $2", - Json(preprocessed_args) as Json>>, - job.id - ) - .execute(db) - .await?; - } - + Ok(result) => { send_job_completed( job_completed_tx, - job, - r, - column_order, - mem_peak, - canceled_by, - true, - cached_res_path, - token, - duration, + JobCompleted { + job, + preprocessed_args, + result, + result_columns, + mem_peak, + canceled_by, + success: true, + cached_res_path, + token: token.to_string(), + duration, + }, ) - .with_context(windmill_common::otel_ee::otel_ctx()) + .with_context(windmill_common::otel_oss::otel_ctx()) .await; Ok(true) } @@ -309,20 +325,33 @@ pub async fn process_result( if res.as_ref().is_some_and(|x| !x.get().is_empty()) { res.unwrap() } else { - let last_10_log_lines = sqlx::query_scalar!( + match conn { + Connection::Sql(db) => { + let last_10_log_lines = sqlx::query_scalar!( "SELECT right(logs, 600) FROM job_logs WHERE job_id = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", &job.id, &job.workspace_id ).fetch_one(db).await.ok().flatten().unwrap_or("".to_string()); - let log_lines = last_10_log_lines - .split("CODE EXECUTION ---") - .last() - .unwrap_or(&last_10_log_lines); + let log_lines = last_10_log_lines + .split("CODE EXECUTION ---") + .last() + .unwrap_or(&last_10_log_lines); - extract_error_value(&program, log_lines, i, job.flow_step_id.clone()) + extract_error_value( + &program, + log_lines, + i, + job.flow_step_id.clone(), + ) + } + Connection::Http(_) => { + to_raw_value(&"See logs for more details".to_string()) + } + } } } + Error::ExecutionRawError(e) => to_raw_value(&e), err @ _ => to_raw_value(&SerializedError { message: format!("execution error:\n{err:#}",), name: "ExecutionErr".to_string(), @@ -333,17 +362,20 @@ pub async fn process_result( send_job_completed( job_completed_tx, - job, - Arc::new(to_raw_value(&error_value)), - None, - mem_peak, - canceled_by, - false, - cached_res_path, - token, - duration, + JobCompleted { + job, + result: Arc::new(to_raw_value(&error_value)), + result_columns: None, + preprocessed_args: None, + mem_peak, + canceled_by, + success: false, + cached_res_path, + token: token.to_string(), + duration, + }, ) - .with_context(windmill_common::otel_ee::otel_ctx()) + .with_context(windmill_common::otel_oss::otel_ctx()) .await; Ok(false) } @@ -355,23 +387,19 @@ pub async fn handle_receive_completed_job( base_internal_url: &str, db: &DB, worker_dir: &str, - same_worker_tx: &SameWorkerSender, + same_worker_tx: Option<&SameWorkerSender>, worker_name: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, -) -> Option> { +) -> Option> { let token = jc.token.clone(); let workspace = jc.job.workspace_id.clone(); - let client = AuthedClient { - base_internal_url: base_internal_url.to_string(), - workspace, - token, - force_client: None, - }; + let client = AuthedClient::new(base_internal_url.to_string(), workspace, token, None); let job = jc.job.clone(); let mem_peak = jc.mem_peak.clone(); let canceled_by = jc.canceled_by.clone(); - match process_completed_job( + + let processed_completed_job = process_completed_job( jc, &client, db, @@ -382,8 +410,9 @@ pub async fn handle_receive_completed_job( #[cfg(feature = "benchmark")] bench, ) - .await - { + .await; + + match processed_completed_job { Err(err) => { handle_job_error( db, @@ -417,23 +446,24 @@ pub async fn process_completed_job( canceled_by, duration, result_columns, + preprocessed_args, .. }: JobCompleted, client: &AuthedClient, db: &DB, worker_dir: &str, - same_worker_tx: SameWorkerSender, + same_worker_tx: Option<&SameWorkerSender>, worker_name: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, -) -> error::Result>> { +) -> error::Result>> { if success { // println!("bef completed job{:?}", SystemTime::now()); if let Some(cached_path) = cached_res_path { save_in_cache(db, client, &job, cached_path, result.clone()).await; } - let is_flow_step = job.is_flow_step; + let is_flow_step = job.is_flow_step(); let parent_job = job.parent_job.clone(); let job_id = job.id.clone(); let workspace_id = job.workspace_id.clone(); @@ -441,6 +471,7 @@ pub async fn process_completed_job( if job.flow_step_id.as_deref() == Some("preprocessor") { // Do this before inserting to `v2_job_completed` for backwards compatibility // when we set `flow_status->_metadata->preprocessed_args` to true. + sqlx::query!( r#"UPDATE v2_job SET args = '{"reason":"PREPROCESSOR_ARGS_ARE_DISCARDED"}'::jsonb, @@ -455,8 +486,19 @@ pub async fn process_completed_job( "error while deleting args of preprocessing step: {e:#}" )) })?; + } else if let Some(preprocessed_args) = preprocessed_args { + // Update script args to preprocessed args + sqlx::query!( + "UPDATE v2_job SET args = $1, preprocessed = TRUE WHERE id = $2", + Json(preprocessed_args) as Json>>, + job.id + ) + .execute(db) + .await?; } + add_time!(bench, "pre add_completed_job"); + add_completed_job( db, &job, @@ -486,7 +528,7 @@ pub async fn process_completed_job( true, result, false, - same_worker_tx.clone(), + &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS).to_owned(), &worker_dir, None, worker_name, @@ -514,7 +556,7 @@ pub async fn process_completed_job( None, ) .await?; - if job.is_flow_step { + if job.is_flow_step() { if let Some(parent_job) = job.parent_job { tracing::error!(parent_flow = %parent_job, subflow = %job.id, "process completed job error, updating flow status"); let r = update_flow_status_after_job_completion( @@ -526,7 +568,7 @@ pub async fn process_completed_job( false, Arc::new(serde_json::value::to_raw_value(&result).unwrap()), false, - same_worker_tx, + &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS).to_owned(), &worker_dir, None, worker_name, @@ -543,48 +585,64 @@ pub async fn process_completed_job( return Ok(None); } +async fn handle_non_flow_job_error( + db: &DB, + job: &MiniPulledJob, + mem_peak: i32, + canceled_by: Option, + err: Value, + worker_name: &str, +) -> Result { + append_logs( + &job.id, + &job.workspace_id, + format!("Unexpected error during job execution:\n{err:#?}"), + &db.into(), + ) + .await; + add_completed_job_error( + db, + job, + mem_peak, + canceled_by, + err, + worker_name, + false, + None, + ) + .await +} + #[tracing::instrument(name = "job_error", level = "info", skip_all, fields(job_id = %job.id))] pub async fn handle_job_error( - db: &Pool, + db: &DB, client: &AuthedClient, - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: i32, canceled_by: Option, err: Error, unrecoverable: bool, - same_worker_tx: SameWorkerSender, + same_worker_tx: Option<&SameWorkerSender>, worker_dir: &str, worker_name: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, ) { - let err = match err { - Error::JsonErr(err) => err, - _ => json!({"message": err.to_string(), "name": "InternalErr"}), - }; + let err = error_to_value(err); let update_job_future = || async { - append_logs( - &job.id, - &job.workspace_id, - format!("Unexpected error during job execution:\n{err:#?}"), - db, - ) - .await; - add_completed_job_error( + handle_non_flow_job_error( db, job, mem_peak, canceled_by.clone(), err.clone(), worker_name, - false, - None, ) .await }; - let update_job_future = if job.is_flow_step || job.is_flow() { + let update_job_future = if job.is_flow_step() || job.is_flow() { let (flow, job_status_to_update) = if let Some(parent_job_id) = job.parent_job { if let Err(e) = update_job_future().await { tracing::error!( @@ -608,7 +666,7 @@ pub async fn handle_job_error( false, Arc::new(serde_json::value::to_raw_value(&wrapped_error).unwrap()), unrecoverable, - same_worker_tx, + &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS).clone(), worker_dir, None, worker_name, @@ -628,12 +686,12 @@ pub async fn handle_job_error( &parent_job.id, &job.workspace_id, format!("Unexpected error during flow job error handling:\n{err}"), - db, + &db.into(), ) .await; let _ = add_completed_job_error( db, - &parent_job, + &MiniPulledJob::from(&parent_job), mem_peak, canceled_by.clone(), e, diff --git a/backend/windmill-worker/src/rust_executor.rs b/backend/windmill-worker/src/rust_executor.rs index 95fd822100..045158511e 100644 --- a/backend/windmill-worker/src/rust_executor.rs +++ b/backend/windmill-worker/src/rust_executor.rs @@ -4,13 +4,17 @@ use uuid::Uuid; use windmill_parser_rust::parse_rust_deps_into_manifest; use itertools::Itertools; -use tokio::{fs::File, io::AsyncReadExt, process::Command}; +use tokio::{ + fs::{create_dir_all, File}, + io::AsyncReadExt, + process::Command, +}; use windmill_common::{ error::{self, Error}, - jobs::QueuedJob, utils::calculate_hash, - worker::{save_cache, write_file}, + worker::{save_cache, write_file, Connection}, }; +use windmill_queue::MiniPulledJob; use windmill_queue::{append_logs, CanceledBy}; use crate::{ @@ -19,20 +23,27 @@ use crate::{ read_result, start_child_process, OccupancyMetrics, }, handle_child::handle_child, - AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, - PROXY_ENVS, RUST_CACHE_DIR, TZ_ENV, + DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUST_CACHE_DIR, + TZ_ENV, }; +use windmill_common::client::AuthedClient; #[cfg(windows)] use crate::SYSTEM_ROOT; const NSJAIL_CONFIG_RUN_RUST_CONTENT: &str = include_str!("../nsjail/run.rust.config.proto"); +const NSJAIL_CONFIG_COMPILE_RUST_CONTENT: &str = + include_str!("../nsjail/download.rust.config.proto"); lazy_static::lazy_static! { static ref HOME_DIR: String = std::env::var("HOME").expect("Could not find the HOME environment variable"); static ref CARGO_HOME: String = std::env::var("CARGO_HOME").unwrap_or_else(|_| { CARGO_HOME_DEFAULT.clone() }); static ref RUSTUP_HOME: String = std::env::var("RUSTUP_HOME").unwrap_or_else(|_| { RUSTUP_HOME_DEFAULT.clone() }); static ref CARGO_PATH: String = std::env::var("CARGO_PATH").unwrap_or_else(|_| format!("{}/bin/cargo", CARGO_HOME.as_str())); + // static ref CARGO_SWEEP_PATH: String = std::env::var("CARGO_SWEEP_PATH").unwrap_or_else(|_| format!("{}/bin/cargo-sweep", CARGO_HOME.as_str())); + static ref SWEEP_MAXSIZE: String = std::env::var("CARGO_SWEEP_MAXSIZE").unwrap_or("25GB".to_owned()); + static ref NO_SHARED_BUILD_DIR: bool = std::env::var("RUST_NO_SHARED_BUILD_DIR").ok().map(|flag| flag == "true").unwrap_or(false); + } #[cfg(windows)] @@ -41,7 +52,21 @@ lazy_static::lazy_static! { static ref RUSTUP_HOME_DEFAULT: String = format!("{}\\.rustup", *HOME_DIR); } -#[cfg(unix)] +#[cfg(debug_assertions)] +const DEV_CONF_NSJAIL: &'static str = r#" +# Mount nix store for nixos to work properly +mount { + src: "/nix/store" + dst: "/nix/store" + is_bind: true + mandatory: false +} +"#; + +#[cfg(not(debug_assertions))] +const DEV_CONF_NSJAIL: &'static str = ""; + +#[cfg(not(windows))] lazy_static::lazy_static! { static ref CARGO_HOME_DEFAULT: String = format!("{}/.cargo", *HOME_DIR); static ref RUSTUP_HOME_DEFAULT: String = format!("{}/.rustup", *HOME_DIR); @@ -127,7 +152,7 @@ pub async fn generate_cargo_lockfile( mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + conn: &Connection, worker_name: &str, w_id: &str, occupancy_metrics: &mut OccupancyMetrics, @@ -153,7 +178,7 @@ pub async fn generate_cargo_lockfile( let gen_lockfile_process = start_child_process(gen_lockfile_cmd, CARGO_PATH.as_str()).await?; handle_child( job_id, - db, + conn, mem_peak, canceled_by, gen_lockfile_process, @@ -164,6 +189,7 @@ pub async fn generate_cargo_lockfile( None, false, &mut Some(occupancy_metrics), + None, ) .await?; @@ -174,70 +200,231 @@ pub async fn generate_cargo_lockfile( Ok(req_content) } +async fn get_build_dir( + job: &MiniPulledJob, + job_dir: &str, + conn: &Connection, + worker_name: &str, + is_preview: bool, +) -> anyhow::Result { + let (bd, run_sweep) = job + .runnable_path + .as_ref() + .and_then(|p| { + if !is_preview || *NO_SHARED_BUILD_DIR { + None + } else { + if *DISABLE_NSJAIL { + // If nsjail is disabled then entire worker has shared build directory + // It drastically improves cache hit-rate. + Some((format!("{RUST_CACHE_DIR}/build/{worker_name}"), true)) + } else { + // If nsjail is enabled, having global shared directory is vulnerability and target for an attack + // Instead we either: + // 1. Create different build directory for workspace script and user. Balanced caching while mainining high degree of security. + // 2. If user is not known or something else goes wrong - use random build dir. This is equivalent to no cache at all. + Some(( + format!( + "{RUST_CACHE_DIR}/build/{}@{}@{}", + &job.workspace_id, + p.replace('/', "."), + &job.created_by + ), + true, + )) + } + } + }) + .unwrap_or((format!("{RUST_CACHE_DIR}/build/{}", Uuid::new_v4()), false)); + + { + let (t, r, g) = ( + create_dir_all(format!("{}/target", &bd)).await, + create_dir_all(format!("{}/registry", &bd)).await, + create_dir_all(format!("{}/git", &bd)).await, + ); + + t.and(r) + .and(g) + .map_err(|e| anyhow::anyhow!("Could not create build dir for rust.\ne: {e}"))?; + } + + if run_sweep { + // Also run sweep to make sure target isn't using too much disk + let mut sweep_cmd = Command::new(CARGO_PATH.as_str()); + sweep_cmd + .current_dir(job_dir) + .env_clear() + .env("PATH", PATH_ENV.as_str()) + .env("CARGO_HOME", CARGO_HOME.as_str()) + .env("HOME", HOME_ENV.as_str()) + .env("CARGO_TARGET_DIR", &(bd.clone() + "/target")) + .env("RUSTUP_HOME", RUSTUP_HOME.as_str()) + .args(["sweep", "--maxsize", SWEEP_MAXSIZE.as_str()]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + sweep_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); + sweep_cmd.env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()), + ); + sweep_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); + } + + let (job_id, conn, w_id, wk_name) = ( + job.id.clone(), + conn.clone(), + job.workspace_id.clone(), + worker_name.to_owned(), + ); + + tokio::spawn(async move { + if let Err(e) = match start_child_process(sweep_cmd, CARGO_PATH.as_str()).await { + Ok(sweep_process) => { + handle_child( + &job_id, + &conn, + &mut 0, + &mut None, + sweep_process, + false, + &wk_name, + &w_id, + "cargo sweep", + None, + false, + &mut None, + None, + ) + .await + } + Err(e) => Err(e), + } { + tracing::warn!( + workspace_id = %w_id, + job_id = %job_id, + "Failed to run `cargo sweep`. Rust cache may grow over time, cargo sweep is meant to clean up unused cache.\ne: {e}\n" + ); + } + }); + } + Ok(bd) +} + pub async fn build_rust_crate( - job_id: &Uuid, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + conn: &Connection, worker_name: &str, - w_id: &str, base_internal_url: &str, hash: &str, occupancy_metrics: &mut OccupancyMetrics, + is_preview: bool, ) -> error::Result { let bin_path = format!("{}/{hash}", RUST_CACHE_DIR); - let mut build_rust_cmd = Command::new(CARGO_PATH.as_str()); - build_rust_cmd - .current_dir(job_dir) - .env_clear() - .envs(PROXY_ENVS.clone()) - .env("PATH", PATH_ENV.as_str()) - .env("BASE_INTERNAL_URL", base_internal_url) - .env("HOME", HOME_ENV.as_str()) - .env("CARGO_HOME", CARGO_HOME.as_str()) - .env("RUSTUP_HOME", RUSTUP_HOME.as_str()) - .args(vec!["build", "--release"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + let build_dir = get_build_dir(job, job_dir, conn, worker_name, is_preview).await?; - #[cfg(windows)] - { - build_rust_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); - build_rust_cmd.env( - "TMP", - std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()), - ); - build_rust_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); - } + let child = if !*DISABLE_NSJAIL { + let _ = write_file( + job_dir, + "download.config.proto", + &NSJAIL_CONFIG_COMPILE_RUST_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{CACHE_DIR}", RUST_CACHE_DIR) + .replace("{CARGO_HOME}", CARGO_HOME.as_str()) + .replace("{DEV}", DEV_CONF_NSJAIL) + .replace("{BUILD}", &build_dir), + )?; + let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); + nsjail_cmd + .current_dir(job_dir) + .env_clear() + .env("PATH", PATH_ENV.as_str()) + .env("TZ", TZ_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(PROXY_ENVS.clone()) + .env("HOME", HOME_ENV.as_str()) + .env("CARGO_HOME", CARGO_HOME.as_str()) + .env("RUSTUP_HOME", RUSTUP_HOME.as_str()) + .env("CARGO_TARGET_DIR", &(build_dir.clone() + "/target")) + .args(vec![ + "--config", + "download.config.proto", + "--", + CARGO_PATH.as_ref(), + "build", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if !is_preview { + nsjail_cmd.arg("--release"); + } + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + } else { + let mut build_rust_cmd = Command::new(CARGO_PATH.as_str()); + build_rust_cmd + .current_dir(job_dir) + .env_clear() + .envs(PROXY_ENVS.clone()) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .env("HOME", HOME_ENV.as_str()) + .env("CARGO_HOME", CARGO_HOME.as_str()) + .env("RUSTUP_HOME", RUSTUP_HOME.as_str()) + .env("CARGO_TARGET_DIR", &(build_dir.clone() + "/target")) + .args(vec!["build"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); - let build_rust_process = start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await?; + if !is_preview { + build_rust_cmd.arg("--release"); + } + #[cfg(windows)] + { + build_rust_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); + build_rust_cmd.env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()), + ); + build_rust_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); + } + start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await? + }; handle_child( - job_id, - db, + &job.id, + conn, mem_peak, canceled_by, - build_rust_process, + child, false, worker_name, - w_id, + &job.workspace_id, "rust build", None, false, &mut Some(occupancy_metrics), + None, ) .await?; - append_logs(job_id, w_id, "\n\n", db).await; + append_logs(&job.id, &job.workspace_id, "\n\n", conn).await; tokio::fs::copy( - &format!("{job_dir}/target/release/main"), + &format!( + "{build_dir}/target/{}/main", + if is_preview { "debug" } else { "release" }, + ), format! {"{job_dir}/main"}, ) .await .map_err(|e| { Error::ExecutionErr(format!( - "could not copy built binary from [...]/target/release/main to {job_dir}/main: {e:?}" + "could not copy built binary from [...]/target/.../main to {job_dir}/main: {e:?}" )) })?; @@ -245,6 +432,7 @@ pub async fn build_rust_crate( &bin_path, &format!("{RUST_OBJECT_STORE_PREFIX}{hash}"), &format!("{job_dir}/main"), + false, ) .await { @@ -275,9 +463,10 @@ pub fn compute_rust_hash(code: &str, requirements_o: Option<&String>) -> String pub async fn handle_rust_job( mem_peak: &mut i32, canceled_by: &mut Option, - job: &QueuedJob, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, inner_content: &str, job_dir: &str, requirements_o: Option<&String>, @@ -293,7 +482,11 @@ pub async fn handle_rust_job( let bin_path = format!("{}/{hash}", RUST_CACHE_DIR); let remote_path = format!("{RUST_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await; + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; + + let (cache, cache_logs) = + windmill_common::worker::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { let target = format!("{job_dir}/main"); @@ -309,11 +502,11 @@ pub async fn handle_rust_job( )) })?; - create_args_and_out_file(client, job, job_dir, db).await?; + create_args_and_out_file(client, job, job_dir, conn).await?; cache_logs } else { let logs1 = format!("{cache_logs}\n\n--- CARGO BUILD ---\n"); - append_logs(&job.id, &job.workspace_id, logs1, db).await; + append_logs(&job.id, &job.workspace_id, logs1, conn).await; gen_cargo_crate(inner_content, job_dir)?; @@ -323,28 +516,25 @@ pub async fn handle_rust_job( } } - create_args_and_out_file(client, job, job_dir, db).await?; + create_args_and_out_file(client, job, job_dir, conn).await?; build_rust_crate( - &job.id, + &job, mem_peak, canceled_by, job_dir, - db, + conn, worker_name, - &job.workspace_id, base_internal_url, &hash, occupancy_metrics, + requirements_o.is_none(), ) .await? }; let logs2 = format!("{cache_logs}\n\n--- RUST CODE EXECUTION ---\n"); - append_logs(&job.id, &job.workspace_id, logs2, db).await; - - let client = &client.get_authed().await; - let reserved_variables = get_reserved_variables(job, &client.token, db).await?; + append_logs(&job.id, &job.workspace_id, logs2, conn).await; let child = if !*DISABLE_NSJAIL { let _ = write_file( @@ -355,6 +545,7 @@ pub async fn handle_rust_job( .replace("{CACHE_DIR}", RUST_CACHE_DIR) .replace("{CACHE_HASH}", &hash) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) + .replace("{DEV}", DEV_CONF_NSJAIL) .replace("{SHARED_MOUNT}", shared_mount), )?; let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); @@ -395,7 +586,7 @@ pub async fn handle_rust_job( }; handle_child( &job.id, - db, + conn, mem_peak, canceled_by, child, @@ -406,6 +597,7 @@ pub async fn handle_rust_job( job.timeout, false, &mut Some(occupancy_metrics), + None, ) .await?; read_result(job_dir).await diff --git a/backend/windmill-worker/src/sanitized_sql_params.rs b/backend/windmill-worker/src/sanitized_sql_params.rs new file mode 100644 index 0000000000..465b67658f --- /dev/null +++ b/backend/windmill-worker/src/sanitized_sql_params.rs @@ -0,0 +1,100 @@ +use anyhow::anyhow; +use std::collections::HashMap; + +use serde_json::Value; +use windmill_common::error; +use windmill_parser::Arg; +use windmill_parser_sql::{SANITIZED_ENUM_STR, SANITIZED_RAW_STRING_STR}; + +/// Identifier must be a continuous ASCII alphanumeric word, not starting with +/// a number, that can contain underscores +fn sanitize_identifier(arg: &Arg, input: &str) -> Result<(), error::Error> { + if input.is_empty() { + return Err(error::Error::BadRequest(format!( + "Interpolated argument `{}` cannot be empty", + arg.name + ))); + } + if input + .chars() + .next() + .map(|c| c.is_ascii_alphabetic()) + .unwrap_or(false) + && input.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') + { + Ok(()) + } else { + Err(error::Error::BadRequest(format!("Interpolated argument `{}` contained forbidden characters. Received `{}` but should only contain alphanumerical characters and `_`.", arg.name, input))) + } +} + +pub fn sanitize_and_interpolate_unsafe_sql_args( + code: &str, + args: &Vec, + args_map: &HashMap, +) -> Result<(String, Vec), error::Error> { + let mut ret = code.to_string(); + let mut args_to_skip = vec![]; + + for arg in args { + if let Some(typ) = &arg.otyp { + let pattern = format!("%%{}%%", arg.name); + match typ.as_str() { + SANITIZED_ENUM_STR => { + let replace = + args_map + .get(&arg.name) + .and_then(|rv| rv.as_str()) + .ok_or(anyhow!( + "Sanitized enum `{}` needs to receive a string", + arg.name + ))?; + let windmill_parser::Typ::Str(Some(variants)) = &arg.typ else { + return Err(error::Error::ArgumentErr(format!( + "Wrong type of argument for sanitized enum `{}`", + arg.name + ))); + }; + if variants.iter().all(|v| v != replace) { + return Err(error::Error::ArgumentErr(format!( + "Sanitized enum argument `{}` expected one of `[{}]` but received `{}`", + arg.name, + variants + .iter() + .map(|s| format!("{s}")) + .collect::>() + .join(","), + replace, + ))); + } + + sanitize_identifier(&arg, replace)?; + ret = ret.replace(&pattern, replace); + args_to_skip.push(arg.name.to_string()); + } + SANITIZED_RAW_STRING_STR => { + let replace = + args_map + .get(&arg.name) + .and_then(|rv| rv.as_str()) + .ok_or(anyhow!( + "Sanitized raw string `{}` needs to receive a string", + arg.name + ))?; + let windmill_parser::Typ::Str(_) = &arg.typ else { + return Err(error::Error::ArgumentErr(format!( + "Wrong type of argument for sanitized raw string `{}`", + arg.name + ))); + }; + sanitize_identifier(&arg, replace)?; + ret = ret.replace(&pattern, &replace); + args_to_skip.push(arg.name.to_string()); + } + _ => continue, + } + } + } + + Ok((ret, args_to_skip)) +} diff --git a/backend/windmill-worker/src/schema.rs b/backend/windmill-worker/src/schema.rs new file mode 100644 index 0000000000..f5cce5cd90 --- /dev/null +++ b/backend/windmill-worker/src/schema.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; +use windmill_common::schema::{SchemaValidationRule, SchemaValidator}; +use windmill_parser::{MainArgSignature, Typ}; + + +fn make_rules_for_arg_typ(typ: &Typ) -> Vec { + let mut rules = vec![]; + + match typ { + Typ::Str(enum_variants) => { + rules.push(SchemaValidationRule::IsString); + + if let Some(enum_variants) = enum_variants { + rules.push(SchemaValidationRule::StrictEnum( + enum_variants + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect(), + )); + } + } + Typ::Int => { + rules.push(SchemaValidationRule::IsInteger); + } + Typ::Float => { + rules.push(SchemaValidationRule::IsNumber); + } + Typ::Bool => { + rules.push(SchemaValidationRule::IsBool); + } + Typ::List(typ) => { + rules.push(SchemaValidationRule::IsArray(make_rules_for_arg_typ(typ))); + } + Typ::Bytes => { + rules.push(SchemaValidationRule::IsString); + rules.push(SchemaValidationRule::IsBytes); + } + Typ::Datetime => { + rules.push(SchemaValidationRule::IsString); + rules.push(SchemaValidationRule::IsDatetime); + } + Typ::Email => { + rules.push(SchemaValidationRule::IsString); + rules.push(SchemaValidationRule::IsEmail); + } + Typ::Sql => { + rules.push(SchemaValidationRule::IsString); + } + Typ::Object(props) => { + let mut obj_rules = vec![]; + + for prop in props { + obj_rules.push((prop.key.to_string(), make_rules_for_arg_typ(&prop.typ))); + } + + rules.push(SchemaValidationRule::IsObject(obj_rules)) + } + Typ::OneOf(variants) => { + let mut rules_map = HashMap::new(); + + for variant in variants { + let mut obj_rules = vec![]; + + for prop in &variant.properties { + obj_rules.push((prop.key.to_string(), make_rules_for_arg_typ(&prop.typ))); + } + rules_map.insert(variant.label.to_string(), vec![SchemaValidationRule::IsObject(obj_rules)]); + } + + rules.push(SchemaValidationRule::IsOneOf(rules_map)) + } + Typ::Resource(_) => (), + Typ::DynSelect(_) => (), + Typ::Unknown => (), + } + + rules +} + +pub fn schema_validator_from_main_arg_sig(sig: &MainArgSignature) -> SchemaValidator { + let mut rules = vec![]; + let mut required = vec![]; + + for arg in &sig.args { + if !arg.has_default { + required.push(arg.name.to_string()); + } + + rules.push((arg.name.to_string(), make_rules_for_arg_typ(&arg.typ))); + } + + SchemaValidator { required, rules } +} + diff --git a/backend/windmill-worker/src/snowflake_executor.rs b/backend/windmill-worker/src/snowflake_executor.rs index 3315df6bc1..8828aeb76b 100644 --- a/backend/windmill-worker/src/snowflake_executor.rs +++ b/backend/windmill-worker/src/snowflake_executor.rs @@ -2,24 +2,32 @@ use base64::{engine, Engine as _}; use chrono::Datelike; use core::fmt::Write; use futures::future::BoxFuture; -use futures::{FutureExt, TryFutureExt}; +use futures::{FutureExt, StreamExt, TryStreamExt}; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use reqwest::{Client, Response}; use serde_json::{json, value::RawValue, Value}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use windmill_common::error::to_anyhow; +use windmill_common::s3_helpers::convert_json_line_stream; +use windmill_common::worker::Connection; -use windmill_common::jobs::QueuedJob; use windmill_common::{error::Error, worker::to_raw_value}; -use windmill_parser_sql::{parse_db_resource, parse_snowflake_sig, parse_sql_blocks}; -use windmill_queue::{CanceledBy, HTTP_CLIENT}; +use windmill_parser_sql::{ + parse_db_resource, parse_s3_mode, parse_snowflake_sig, parse_sql_blocks, +}; +use windmill_queue::{CanceledBy, MiniPulledJob, HTTP_CLIENT}; use serde::{Deserialize, Serialize}; -use crate::common::{build_http_client, resolve_job_timeout, OccupancyMetrics}; +use crate::common::{ + build_http_client, resolve_job_timeout, s3_mode_args_to_worker_data, OccupancyMetrics, + S3ModeWorkerData, +}; use crate::handle_child::run_future_with_polling_update_job_poller; -use crate::{common::build_args_values, AuthedClientBackgroundTask}; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; +use crate::common::build_args_values; +use windmill_common::client::AuthedClient; #[derive(Serialize)] struct Claims { @@ -123,16 +131,23 @@ fn do_snowflake_inner<'a>( column_order: Option<&'a mut Option>>, skip_collect: bool, http_client: &'a Client, + s3: Option, ) -> windmill_common::error::Result>>> { - body.insert("statement".to_string(), json!(query)); - - let mut bindings = serde_json::Map::new(); let sig = parse_snowflake_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + + body.insert("statement".to_string(), json!(query)); + + let mut bindings = serde_json::Map::new(); + let mut i = 1; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_v = job_args.get(&arg.name).cloned().unwrap_or(json!("")); let snowflake_v = convert_typ_val(arg_t, arg_v); @@ -168,7 +183,7 @@ fn do_snowflake_inner<'a>( .parse_snowflake_response::() .await?; - if response.resultSetMetaData.numRows > 10000 { + if s3.is_none() && response.resultSetMetaData.numRows > 10000 { return Err(Error::ExecutionErr( "More than 10000 rows were requested, use LIMIT 10000 to limit the number of rows" .to_string(), @@ -185,54 +200,72 @@ fn do_snowflake_inner<'a>( ); } - let mut rows = response.data; + // Clones are because, in s3 mode, reqwest::Body::wrap_stream requires the stream to be + // 'static even though it doesn't make sense to be in our case since the request is + // awaited and the stream is fully read before the function returns. + // Turns out it is a real pain to trick the compiler, even using unsafe + let cloned_account_identifier: String = account_identifier.to_string(); + let cloned_token = token.to_string(); - if response.resultSetMetaData.partitionInfo.len() > 1 { - for idx in 1..response.resultSetMetaData.partitionInfo.len() { - let url = format!( - "https://{}.snowflakecomputing.com/api/v2/statements/{}", - account_identifier.to_uppercase(), - response.statementHandle - ); - let mut request = HTTP_CLIENT - .get(url) - .bearer_auth(token) - .query(&[("partition", idx.to_string())]); - - if token_is_keypair { - request = - request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); - } - - let response = request - .send() - .await - .parse_snowflake_response::() - .await?; - - rows.extend(response.data); + let rows_stream = async_stream::stream! { + for row in response.data { + yield Ok::, windmill_common::error::Error>(row); } + + if response.resultSetMetaData.partitionInfo.len() > 1 { + for idx in 1..response.resultSetMetaData.partitionInfo.len() { + let url = format!( + "https://{}.snowflakecomputing.com/api/v2/statements/{}", + cloned_account_identifier.to_uppercase(), + response.statementHandle + ); + let mut request = HTTP_CLIENT + .get(url) + .bearer_auth(cloned_token.as_str()) + .query(&[("partition", idx.to_string())]); + + if token_is_keypair { + request = + request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); + } + + let response = request + .send() + .await + .parse_snowflake_response::() + .await?; + + for row in response.data { + yield Ok(row); + } + } + } + }; + + let rows_stream = rows_stream.map_ok(move |row| { + let mut row_map = serde_json::Map::new(); + row.iter() + .zip(response.resultSetMetaData.rowType.iter()) + .for_each(|(val, row_type)| { + row_map.insert(row_type.name.clone(), parse_val(&val, &row_type.r#type)); + }); + row_map + }); + + if let Some(s3) = s3 { + let rows_stream = + rows_stream.map(|r| serde_json::value::to_value(&r?).map_err(to_anyhow)); + let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?; + s3.upload(stream.boxed()).await?; + Ok(to_raw_value(&s3.to_return_s3_obj())) + } else { + let rows = rows_stream + .collect::>() + .await + .into_iter() + .collect::, _>>()?; + Ok(to_raw_value(&rows)) } - - let rows = to_raw_value( - &rows - .iter() - .map(|row| { - let mut row_map = serde_json::Map::new(); - row.iter() - .zip(response.resultSetMetaData.rowType.iter()) - .for_each(|(val, row_type)| { - row_map.insert( - row_type.name.clone(), - parse_val(&val, &row_type.r#type), - ); - }); - row_map - }) - .collect::>(), - ); - - Ok(rows) } }; @@ -240,25 +273,24 @@ fn do_snowflake_inner<'a>( } pub async fn do_snowflake( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, query: &str, - db: &sqlx::Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, column_order: &mut Option>, occupancy_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let snowflake_args = build_args_values(job, client, db).await?; + let snowflake_args = build_args_values(job, client, conn).await?; let inline_db_res_path = parse_db_resource(&query); + let s3 = parse_s3_mode(&query)?.map(|s3| s3_mode_args_to_worker_data(s3, client.clone(), job)); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { Some( client - .get_authed() - .await .get_resource_value_interpolated::( &inline_db_res_path, Some(job.id.to_string()), @@ -358,7 +390,7 @@ pub async fn do_snowflake( json!(database.database.unwrap().to_uppercase()), ); } - let timeout = resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout) + let timeout = resolve_job_timeout(&conn, &job.workspace_id, job.id, job.timeout) .await .0 .as_secs(); @@ -367,7 +399,7 @@ pub async fn do_snowflake( let queries = parse_sql_blocks(query); let (timeout_duration, _, _) = - resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await; + resolve_job_timeout(&conn, &job.workspace_id, job.id, job.timeout).await; let http_client = build_http_client(timeout_duration)?; @@ -386,6 +418,7 @@ pub async fn do_snowflake( None, annotations.return_last_result && i < queries.len() - 1, &http_client, + s3.clone(), ) }) .collect::>>()?; @@ -415,15 +448,16 @@ pub async fn do_snowflake( Some(column_order), false, &http_client, + s3.clone(), )? }; let r = run_future_with_polling_update_job_poller( job.id, job.timeout, - db, + conn, mem_peak, canceled_by, - result_f.map_err(to_anyhow), + result_f, worker_name, &job.workspace_id, &mut Some(occupancy_metrics), diff --git a/backend/windmill-worker/src/windmill-client.js b/backend/windmill-worker/src/windmill-client.js index 3ae15e7453..1ecf28e664 100644 --- a/backend/windmill-worker/src/windmill-client.js +++ b/backend/windmill-worker/src/windmill-client.js @@ -2998,6 +2998,8 @@ var $RawScript = { "mssql", "graphql", "nativets", + "duckdb", + // for related places search: ADD_NEW_LANG ], }, path: { diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index db6ea56bb5..058ad19598 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -9,45 +9,49 @@ // #[cfg(feature = "otel")] // use opentelemetry::{global, KeyValue}; +use anyhow::anyhow; +use futures::TryFutureExt; +use windmill_common::client::AuthedClient; use windmill_common::{ + agent_workers::DECODED_AGENT_TOKEN, apps::AppScriptId, - auth::{fetch_authed_from_permissioned_as, JWTAuthClaims, JobPerms}, - cache::{ScriptData, ScriptMetadata}, - jwt, + cache::{future::FutureCachedExt, ScriptData, ScriptMetadata}, + schema::{should_validate_schema, SchemaValidator}, scripts::PREVIEW_IS_TAR_CODEBASE_HASH, - utils::WarnAfterExt, + utils::{create_directory_async, WarnAfterExt}, worker::{ - get_memory, get_vcpus, get_windmill_memory_usage, get_worker_memory_usage, write_file, - ROOT_CACHE_DIR, ROOT_CACHE_NOMOUNT_DIR, TMP_DIR, + make_pull_query, write_file, Connection, HttpClient, MAX_TIMEOUT, ROOT_CACHE_DIR, + ROOT_CACHE_NOMOUNT_DIR, TMP_DIR, }, + KillpillSender, }; #[cfg(feature = "enterprise")] -use windmill_common::ee::LICENSE_KEY_VALID; +use windmill_common::ee_oss::LICENSE_KEY_VALID; -use anyhow::{Context, Result}; +use anyhow::Result; use const_format::concatcp; #[cfg(feature = "prometheus")] use prometheus::IntCounter; -use tracing::{field, Instrument}; +use tracing::{field, Instrument, Span}; #[cfg(feature = "prometheus")] use windmill_common::METRICS_DEBUG_ENABLED; #[cfg(feature = "prometheus")] use windmill_common::METRICS_ENABLED; -use reqwest::Response; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use sqlx::{types::Json, Pool, Postgres}; +use serde::{Deserialize, Serialize}; +use sqlx::types::Json; use std::{ collections::HashMap, - fs::DirBuilder, + fmt::Display, sync::{ atomic::{AtomicBool, AtomicU16, Ordering}, Arc, }, time::Duration, }; +use windmill_parser::MainArgSignature; use uuid::Uuid; @@ -55,17 +59,17 @@ use windmill_common::{ cache::{self, RawData}, error::{self, to_anyhow, Error}, flows::FlowNodeId, - jobs::{JobKind, QueuedJob}, + jobs::JobKind, scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang, PREVIEW_IS_CODEBASE_HASH}, - users::SUPERADMIN_SECRET_EMAIL, utils::StripPath, - worker::{update_ping, CLOUD_HOSTED, NO_LOGS, WORKER_CONFIG, WORKER_GROUP}, + worker::{CLOUD_HOSTED, NO_LOGS, WORKER_CONFIG, WORKER_GROUP}, DB, IS_READY, }; use windmill_queue::{ - append_logs, canceled_job_to_result, empty_result, pull, push, CanceledBy, PulledJob, PushArgs, - PushIsolationLevel, HTTP_CLIENT, + append_logs, canceled_job_to_result, empty_result, get_same_worker_job, pull, push_init_job, + CanceledBy, JobAndPerms, JobCompleted, MiniPulledJob, PrecomputedAgentInfo, PulledJob, + SameWorkerPayload, HTTP_CLIENT, }; #[cfg(feature = "prometheus")] @@ -81,7 +85,8 @@ use tokio::fs::symlink_file as symlink; use tokio::{ sync::{ - mpsc::{self, Sender}, + broadcast, + mpsc::{self, Receiver, Sender}, RwLock, }, task::JoinHandle, @@ -91,10 +96,11 @@ use tokio::{ use rand::Rng; use crate::{ + agent_workers::queue_init_job, bash_executor::{handle_bash_job, handle_powershell_job}, bun_executor::handle_bun_job, common::{ - build_args_map, cached_result_path, get_cached_resource_value_if_valid, + build_args_map, cached_result_path, error_to_value, get_cached_resource_value_if_valid, get_reserved_variables, update_worker_ping_for_failed_init_script, OccupancyMetrics, }, csharp_executor::handle_csharp_job, @@ -107,20 +113,31 @@ use crate::{ js_eval::{eval_fetch_timeout, transpile_ts}, pg_executor::do_postgresql, result_processor::{process_result, start_background_processor}, - worker_flow::{handle_flow, update_flow_status_in_progress}, + schema::schema_validator_from_main_arg_sig, + worker_flow::handle_flow, worker_lockfiles::{ handle_app_dependency_job, handle_dependency_job, handle_flow_dependency_job, }, + worker_utils::{insert_ping, queue_vacuum, update_worker_ping_full}, }; #[cfg(feature = "rust")] use crate::rust_executor::handle_rust_job; +#[cfg(feature = "nu")] +use crate::nu_executor::{handle_nu_job, JobHandlerInput as JobHandlerInputNu}; + +#[cfg(feature = "java")] +use crate::java_executor::{handle_java_job, JobHandlerInput as JobHandlerInputJava}; + #[cfg(feature = "php")] use crate::php_executor::handle_php_job; #[cfg(feature = "python")] -use crate::python_executor::{handle_python_job, PyVersion}; +use crate::{ + python_executor::handle_python_job, + python_versions::{PyV, PyVAlias}, +}; #[cfg(feature = "python")] use crate::ansible_executor::handle_ansible_job; @@ -128,12 +145,12 @@ use crate::ansible_executor::handle_ansible_job; #[cfg(feature = "mysql")] use crate::mysql_executor::do_mysql; +#[cfg(feature = "duckdb")] +use crate::duckdb_executor::do_duckdb; + #[cfg(feature = "oracledb")] use crate::oracledb_executor::do_oracledb; -use backon::ConstantBuilder; -use backon::{BackoffBuilder, Retryable}; - #[cfg(feature = "enterprise")] use crate::dedicated_worker::create_dedicated_worker_map; @@ -147,118 +164,16 @@ use crate::mssql_executor::do_mssql; use crate::bigquery_executor::do_bigquery; #[cfg(feature = "benchmark")] -use crate::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter}; +use windmill_common::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter}; use windmill_common::add_time; -pub async fn create_token_for_owner_in_bg( - db: &Pool, - job: &QueuedJob, -) -> Arc> { - let rw_lock = Arc::new(RwLock::new(String::new())); - // skipping test runs - if job.workspace_id != "" { - let mut locked = rw_lock.clone().write_owned().await; - let db = db.clone(); - let w_id = job.workspace_id.clone(); - let owner = job.permissioned_as.clone(); - let email = job.email.clone(); - let job_id = job.id.clone(); +pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_10"); +pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_11"); +pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_12"); +pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_13"); - let label = if job.permissioned_as != format!("u/{}", job.created_by) - && job.permissioned_as != job.created_by - { - format!("ephemeral-script-end-user-{}", job.created_by) - } else { - "ephemeral-script".to_string() - }; - tokio::spawn(async move { - let token = create_token_for_owner( - &db.clone(), - &w_id, - &owner, - &label, - *SCRIPT_TOKEN_EXPIRY, - &email, - &job_id, - ) - .warn_after_seconds(5) - .await - .expect("could not create job token"); - *locked = token; - }); - }; - return rw_lock; -} - -#[tracing::instrument(level = "trace", skip_all)] -pub async fn create_token_for_owner( - db: &Pool, - w_id: &str, - owner: &str, - label: &str, - expires_in: u64, - email: &str, - job_id: &Uuid, -) -> error::Result { - // TODO: Bad implementation. We should not have access to this DB here. - if let Some(token) = JOB_TOKEN.as_ref() { - return Ok(token.clone()); - } - - let job_authed = match sqlx::query_as!( - JobPerms, - "SELECT * FROM job_perms WHERE job_id = $1 AND workspace_id = $2", - job_id, - w_id - ) - .fetch_optional(db) - .await - { - Ok(Some(jp)) => jp.into(), - _ => { - tracing::warn!("Could not get permissions for job {job_id} from job_perms table, getting permissions directly..."); - fetch_authed_from_permissioned_as(owner.to_string(), email.to_string(), w_id, db) - .await - .map_err(|e| { - Error::internal_err(format!( - "Could not get permissions directly for job {job_id}: {e:#}" - )) - })? - } - }; - - let payload = JWTAuthClaims { - email: job_authed.email, - username: job_authed.username, - is_admin: job_authed.is_admin, - is_operator: job_authed.is_operator, - groups: job_authed.groups, - folders: job_authed.folders, - label: Some(label.to_string()), - workspace_id: w_id.to_string(), - exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in as i64)).timestamp() - as usize, - job_id: Some(job_id.to_string()), - scopes: None, - }; - - let token = jwt::encode_with_internal_secret(&payload) - .await - .with_context(|| format!("Could not encode JWT token for job {job_id}"))?; - - Ok(format!("jwt_{}", token)) -} - -pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_310"); -pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_311"); -pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_312"); -pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_313"); - -pub const TAR_PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_310"); -pub const TAR_PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_311"); -pub const TAR_PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_312"); -pub const TAR_PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_313"); +pub const TAR_JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/java"); pub const UV_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "uv"); pub const PY_INSTALL_DIR: &str = concatcp!(ROOT_CACHE_DIR, "py_runtime"); @@ -269,7 +184,14 @@ pub const DENO_CACHE_DIR_NPM: &str = concatcp!(ROOT_CACHE_DIR, "deno/npm"); pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go"); pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust"); +pub const NU_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "nu"); pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp"); + +// JAVA +pub const JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "java"); +pub const COURSIER_CACHE_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/coursier-cache"); +pub const JAVA_REPOSITORY_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/repository"); +// for related places search: ADD_NEW_LANG pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun"); pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun"); pub const BUN_CODEBASE_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "script_bundle"); @@ -283,8 +205,9 @@ const NUM_SECS_READINGS: u64 = 60; const INCLUDE_DEPS_PY_SH_CONTENT: &str = include_str!("../nsjail/download_deps.py.sh"); -pub const DEFAULT_CLOUD_TIMEOUT: u64 = 900; -pub const DEFAULT_SELFHOSTED_TIMEOUT: u64 = 604800; // 7 days +const WORKER_SHELL_NAP_TIME_DURATION: u64 = 15; +const TIMEOUT_TO_RESET_WORKER_SHELL_NAP_TIME_DURATION: u64 = 2 * 60; + pub const DEFAULT_SLEEP_QUEUE: u64 = 50; // only 1 native job so that we don't have to worry about concurrency issues on non dedicated native jobs workers @@ -292,8 +215,8 @@ pub const DEFAULT_NATIVE_JOBS: usize = 1; const VACUUM_PERIOD: u32 = 50000; -#[cfg(any(target_os = "linux"))] -const DROP_CACHE_PERIOD: u32 = 1000; +// #[cfg(any(target_os = "linux"))] +// const DROP_CACHE_PERIOD: u32 = 1000; pub const MAX_BUFFERED_DEDICATED_JOBS: usize = 3; @@ -322,19 +245,25 @@ lazy_static::lazy_static! { const DOTNET_DEFAULT_PATH: &str = "C:\\Program Files\\dotnet\\dotnet.exe"; #[cfg(unix)] const DOTNET_DEFAULT_PATH: &str = "/usr/bin/dotnet"; +pub const SAME_WORKER_REQUIREMENTS: &'static str = + "SameWorkerSender is required because this job may be part of a flow"; lazy_static::lazy_static! { - pub static ref JOB_TOKEN: Option = std::env::var("JOB_TOKEN").ok(); - pub static ref SLEEP_QUEUE: u64 = std::env::var("SLEEP_QUEUE") .ok() - .and_then(|x| x.parse::().ok()) - .unwrap_or(DEFAULT_SLEEP_QUEUE * std::env::var("NUM_WORKERS") - .ok() - .map(|x| x.parse().ok()) - .flatten() - .unwrap_or(2) / 2); + .and_then(|x| x.parse::().ok()) + .unwrap_or_else(|| { + if std::env::var("MODE").unwrap_or_default() == "agent" { + 1000 + } else { + DEFAULT_SLEEP_QUEUE * std::env::var("NUM_WORKERS") + .ok() + .map(|x| x.parse().ok()) + .flatten() + .unwrap_or(2) / 2 + } + }); pub static ref DISABLE_NUSER: bool = std::env::var("DISABLE_NUSER") @@ -361,6 +290,8 @@ lazy_static::lazy_static! { let mut proxy_env = Vec::new(); if let Some(no_proxy) = NO_PROXY.as_ref() { proxy_env.push(("NO_PROXY", no_proxy.to_string())); + } else if HTTPS_PROXY.is_some() || HTTP_PROXY.is_some() { + proxy_env.push(("NO_PROXY", "localhost,127.0.0.1".to_string())); } if let Some(http_proxy) = HTTP_PROXY.as_ref() { proxy_env.push(("HTTP_PROXY", http_proxy.to_string())); @@ -381,6 +312,7 @@ lazy_static::lazy_static! { pub static ref NSJAIL_PATH: String = std::env::var("NSJAIL_PATH").unwrap_or_else(|_| "nsjail".to_string()); pub static ref PATH_ENV: String = std::env::var("PATH").unwrap_or_else(|_| String::new()); pub static ref HOME_ENV: String = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + pub static ref GIT_PATH: String = std::env::var("GIT_PATH").unwrap_or_else(|_| "/usr/bin/git".to_string()); pub static ref NODE_PATH: Option = std::env::var("NODE_PATH").ok(); @@ -393,16 +325,18 @@ lazy_static::lazy_static! { pub static ref NPM_CONFIG_REGISTRY: Arc>> = Arc::new(RwLock::new(None)); pub static ref BUNFIG_INSTALL_SCOPES: Arc>> = Arc::new(RwLock::new(None)); pub static ref NUGET_CONFIG: Arc>> = Arc::new(RwLock::new(None)); + pub static ref MAVEN_REPOS: Arc>> = Arc::new(RwLock::new(None)); + pub static ref NO_DEFAULT_MAVEN: AtomicBool = AtomicBool::new(std::env::var("NO_DEFAULT_MAVEN") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(false)); pub static ref PIP_EXTRA_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); pub static ref PIP_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); pub static ref INSTANCE_PYTHON_VERSION: Arc>> = Arc::new(RwLock::new(None)); pub static ref JOB_DEFAULT_TIMEOUT: Arc>> = Arc::new(RwLock::new(None)); - static ref MAX_TIMEOUT: u64 = std::env::var("TIMEOUT") - .ok() - .and_then(|x| x.parse::().ok()) - .unwrap_or_else(|| if *CLOUD_HOSTED { DEFAULT_CLOUD_TIMEOUT } else { DEFAULT_SELFHOSTED_TIMEOUT }); + pub static ref MAX_WAIT_FOR_SIGINT: u64 = std::env::var("MAX_WAIT_FOR_SIGINT") .ok() @@ -416,10 +350,6 @@ lazy_static::lazy_static! { pub static ref MAX_TIMEOUT_DURATION: Duration = Duration::from_secs(*MAX_TIMEOUT); - pub static ref SCRIPT_TOKEN_EXPIRY: u64 = std::env::var("SCRIPT_TOKEN_EXPIRY") - .ok() - .and_then(|x| x.parse::().ok()) - .unwrap_or(*MAX_TIMEOUT); pub static ref GLOBAL_CACHE_INTERVAL: u64 = std::env::var("GLOBAL_CACHE_INTERVAL") .ok() @@ -439,10 +369,51 @@ lazy_static::lazy_static! { } +type Envs = Vec<(String, String)>; + #[cfg(windows)] lazy_static::lazy_static! { pub static ref SYSTEM_ROOT: String = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string()); pub static ref USERPROFILE_ENV: String = std::env::var("USERPROFILE").unwrap_or_else(|_| "/tmp".to_string()); + static ref TMP: String = std::env::var("TMP").unwrap_or_else(|_| "/tmp".to_string()); + static ref LOCALAPPDATA: String = std::env::var("LOCALAPPDATA").unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())); + pub static ref WIN_ENVS: Envs = vec![ + ("SystemRoot".into(), SYSTEM_ROOT.clone()), + ("USERPROFILE".into(), USERPROFILE_ENV.clone()), + ("TMP".into(), TMP.clone()), + ("LOCALAPPDATA".into(), LOCALAPPDATA.clone()) + ]; + +} + +#[cfg(not(windows))] +lazy_static::lazy_static! { + pub static ref WIN_ENVS: Envs = vec![]; +} + +#[derive(Debug)] +pub enum NextJob { + Sql(PulledJob), + Http(JobAndPerms), +} + +impl NextJob { + pub fn job(self) -> MiniPulledJob { + match self { + NextJob::Sql(job) => job.job, + NextJob::Http(job) => job.job, + } + } +} + +impl std::ops::Deref for NextJob { + type Target = MiniPulledJob; + fn deref(&self) -> &Self::Target { + match self { + NextJob::Sql(job) => &job.job, + NextJob::Http(job) => &job.job, + } + } } //only matter if CLOUD_HOSTED @@ -450,191 +421,136 @@ pub const MAX_RESULT_SIZE: usize = 1024 * 1024 * 2; // 2MB pub const INIT_SCRIPT_TAG: &str = "init_script"; -pub struct AuthedClientBackgroundTask { - pub base_internal_url: String, - pub workspace: String, - pub token: Arc>, -} - -impl AuthedClientBackgroundTask { - pub async fn get_authed(&self) -> AuthedClient { - return AuthedClient { - base_internal_url: self.base_internal_url.clone(), - workspace: self.workspace.clone(), - token: self.get_token().await, - force_client: None, - }; - } - pub async fn get_token(&self) -> String { - return self.token.read().await.clone(); - } -} -#[derive(Clone)] -pub struct AuthedClient { - pub base_internal_url: String, - pub workspace: String, - pub token: String, - pub force_client: Option, -} - -impl AuthedClient { - pub async fn get(&self, url: &str, query: Vec<(&str, String)>) -> anyhow::Result { - self.force_client - .as_ref() - .unwrap_or(&HTTP_CLIENT) - .get(url) - .query(&query) - .header( - reqwest::header::ACCEPT, - reqwest::header::HeaderValue::from_static("application/json"), - ) - .header( - reqwest::header::AUTHORIZATION, - reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.token))?, - ) - .send() - .await - .context(format!( - "Executing request from authed http client to {url} with query {query:?}", - )) - } - - pub async fn get_id_token(&self, audience: &str) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/oidc/token/{}", - self.base_internal_url, self.workspace, audience - ); - let response = self.get(&url, vec![]).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding oidc token as json string")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } - - pub async fn get_resource_value(&self, path: &str) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/resources/get_value/{}", - self.base_internal_url, self.workspace, path - ); - let response = self.get(&url, vec![]).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding resource value as json")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } - - pub async fn get_variable_value(&self, path: &str) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/variables/get_value/{}", - self.base_internal_url, self.workspace, path - ); - let response = self.get(&url, vec![]).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding variable value as json")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } - - pub async fn get_resource_value_interpolated( - &self, - path: &str, - job_id: Option, - ) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/resources/get_value_interpolated/{}", - self.base_internal_url, self.workspace, path - ); - let mut query = Vec::with_capacity(1usize); - if let Some(v) = &job_id { - query.push(("job_id", v.to_string())); - } - let response = self.get(&url, query).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding interpolated resource value as json")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } - - pub async fn get_completed_job_result( - &self, - path: &str, - json_path: Option, - ) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/jobs_u/completed/get_result/{}", - self.base_internal_url, self.workspace, path - ); - let query = if let Some(json_path) = json_path { - vec![("json_path", json_path)] - } else { - vec![] - }; - let response = self.get(&url, query).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding completed job result as json")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } - - pub async fn get_result_by_id( - &self, - flow_job_id: &str, - node_id: &str, - json_path: Option, - ) -> anyhow::Result { - let url = format!( - "{}/api/w/{}/jobs/result_by_id/{}/{}", - self.base_internal_url, self.workspace, flow_job_id, node_id - ); - let query = if let Some(json_path) = json_path { - vec![("json_path", json_path)] - } else { - vec![] - }; - let response = self.get(&url, query).await?; - match response.status().as_u16() { - 200u16 => Ok(response - .json::() - .await - .context("decoding result by id as json")?), - _ => Err(anyhow::anyhow!(response.text().await.unwrap_or_default())), - } - } -} - -#[allow(dead_code)] -#[derive(Clone)] -pub struct JobCompletedSender(Sender); - #[derive(Clone)] pub struct SameWorkerSender(pub Sender, pub Arc); -pub struct SameWorkerPayload { - pub job_id: Uuid, - pub recoverable: bool, +#[allow(dead_code)] +#[derive(Clone)] +pub enum JobCompletedSender { + Sql(SqlJobCompletedSender), + Http(HttpClient), + NeverUsed, +} + +#[derive(Clone)] +pub struct SqlJobCompletedSender { + sender: flume::Sender, + unbounded_sender: flume::Sender, + killpill_tx: broadcast::Sender<()>, +} + +pub struct JobCompletedReceiver { + pub bounded_rx: flume::Receiver, + pub killpill_rx: broadcast::Receiver<()>, + pub unbounded_rx: flume::Receiver, +} + +impl JobCompletedReceiver { + pub fn clone(&self) -> Self { + Self { + bounded_rx: self.bounded_rx.clone(), + killpill_rx: self.killpill_rx.resubscribe(), + unbounded_rx: self.unbounded_rx.clone(), + } + } } impl JobCompletedSender { + pub fn new_job_completed_sender_sql(buffer_size: u8) -> (Self, JobCompletedReceiver) { + let (sender, receiver) = flume::bounded::(buffer_size as usize); + let (unbounded_sender, unbounded_rx) = flume::unbounded::(); + let (killpill_tx, killpill_rx) = broadcast::channel::<()>(10); + ( + Self::Sql(SqlJobCompletedSender { sender, unbounded_sender, killpill_tx }), + JobCompletedReceiver { bounded_rx: receiver, killpill_rx, unbounded_rx }, + ) + } + + pub fn new(conn: &Connection, buffer_size: u8) -> (Self, Option) { + match conn { + Connection::Sql(_) => { + let result = Self::new_job_completed_sender_sql(buffer_size); + (result.0, Some(result.1)) + } + Connection::Http(client) => (Self::Http(client.clone()), None), + } + } + + pub fn new_never_used() -> (Self, Option>) { + (Self::NeverUsed, None) + } + + pub async fn send_job(&self, jc: JobCompleted, wait_for_capacity: bool) -> anyhow::Result<()> { + match self { + Self::Sql(SqlJobCompletedSender { sender, unbounded_sender, .. }) => { + if wait_for_capacity { + sender + } else { + unbounded_sender + } + .send_async(SendResult::JobCompleted(jc)) + .await + .map_err(|_e| { + anyhow::anyhow!("Failed to send job completed to background processor") + }) + } + Self::Http(client) => { + crate::agent_workers::send_result(client, jc).await?; + Ok(()) + } + Self::NeverUsed => { + tracing::error!( + "Sending job completed to NeverUsed JobCompletedSender, this should not happen" + ); + Ok(()) + } + } + } + pub async fn send( &self, - jc: JobCompleted, - ) -> Result<(), tokio::sync::mpsc::error::SendError> { - self.0.send(SendResult::JobCompleted(jc)).await + send_result: SendResult, + wait_for_capacity: bool, + ) -> Result<(), flume::SendError> { + match self { + Self::Sql(SqlJobCompletedSender { sender, unbounded_sender, .. }) => { + if wait_for_capacity { + sender.send_async(send_result).await + } else { + unbounded_sender.send_async(send_result).await + } + } + Self::Http(_) => { + tracing::error!("Sending job completed to http client, this should not happen"); + Ok(()) + } + Self::NeverUsed => { + tracing::error!( + "Sending job completed to NeverUsed JobCompletedSender, this should not happen" + ); + Ok(()) + } + } + } + + pub async fn kill(&self) -> Result<(), broadcast::error::SendError<()>> { + match self { + Self::Sql(SqlJobCompletedSender { killpill_tx, .. }) => { + tracing::info!("Sending killpill to bg processors"); + killpill_tx.send(())?; + Ok(()) + } + Self::Http(_) => { + tracing::error!("Sending kill to http client, this should not happen"); + Ok(()) + } + Self::NeverUsed => { + tracing::error!( + "Sending kill to NeverUsed JobCompletedSender, this should not happen" + ); + Ok(()) + } + } } } @@ -663,11 +579,11 @@ pub async fn drop_cache() { Ok(mut file) => { // Write '3' to the file to drop caches if let Err(e) = tokio::io::AsyncWriteExt::write_all(&mut file, b"3").await { - tracing::warn!("Failed to write to /proc/sys/vm/drop_caches (expected to not work in not in privileged mode, only required to forcefully drop the cache to avoid spurrious oom killer): {}", e); + tracing::warn!("Failed to write to /proc/sys/vm/drop_caches (expected to work only in privileged mode, only required to forcefully drop the cache to avoid spurrious oom killer): {}", e); } } Err(e) => { - tracing::warn!("Failed to open /proc/sys/vm/drop_caches (expected to not work in not in privileged mode, only required to forcefully drop the cache to avoid spurrious oom killer):: {}", e); + tracing::warn!("Failed to open /proc/sys/vm/drop_caches (expected to work only in privileged mode, only required to forcefully drop the cache to avoid spurrious oom killer):: {}", e); } } } @@ -677,17 +593,17 @@ const OUTSTANDING_WAIT_TIME_THRESHOLD_MS: i64 = 1000; async fn insert_wait_time( job_id: Uuid, root_job_id: Option, - db: &Pool, + db: &DB, wait_time: i64, ) -> sqlx::error::Result<()> { sqlx::query!( - "INSERT INTO outstanding_wait_time(job_id, self_wait_time_ms) VALUES ($1, $2) - ON CONFLICT (job_id) DO UPDATE SET self_wait_time_ms = EXCLUDED.self_wait_time_ms", - job_id, - wait_time - ) - .execute(db) - .await?; + "INSERT INTO outstanding_wait_time(job_id, self_wait_time_ms) VALUES ($1, $2) + ON CONFLICT (job_id) DO UPDATE SET self_wait_time_ms = EXCLUDED.self_wait_time_ms", + job_id, + wait_time + ) + .execute(db) + .await?; if let Some(root_id) = root_job_id { // TODO: queued_job.root_job is not guaranteed to be the true root job (e.g. parallel flow @@ -698,16 +614,16 @@ async fn insert_wait_time( COALESCE(outstanding_wait_time.aggregate_wait_time_ms, 0) + EXCLUDED.aggregate_wait_time_ms", root_id, wait_time - ) - .execute(db) - .await?; + ) + .execute(db) + .await?; } Ok(()) } fn add_outstanding_wait_time( - queued_job: &QueuedJob, - db: &Pool, + conn: &Connection, + queued_job: &MiniPulledJob, waiting_threshold: i64, ) -> () { let wait_time; @@ -723,32 +639,258 @@ fn add_outstanding_wait_time( } let job_id = queued_job.id; - let root_job_id = queued_job.root_job; - let db = db.clone(); + let root_job_id = queued_job.flow_innermost_root_job; + let conn = conn.clone(); - tokio::spawn(async move { + if let Some(db) = conn.as_sql() { + let db = db.clone(); + tokio::spawn(async move { match insert_wait_time(job_id, root_job_id, &db, wait_time).await { Ok(()) => tracing::warn!("job {job_id} waited for an executor for a significant amount of time. Recording value wait_time={}ms", wait_time), Err(e) => tracing::error!("Failed to insert outstanding wait time: {}", e), } - }.in_current_span()); + }.in_current_span()); + } } -// struct WorkerMtrics { -// job_ -// } +async fn extract_job_and_perms(job: NextJob, conn: &Connection) -> JobAndPerms { + match (job, conn) { + (NextJob::Sql(job), Connection::Sql(db)) => job.get_job_and_perms(db).await, + (NextJob::Sql(_), Connection::Http(_)) => panic!("sql job on http connection"), + (NextJob::Http(job), _) => job, + } +} + +fn create_span(arc_job: &Arc, worker_name: &str, hostname: &str) -> Span { + let span = tracing::span!(tracing::Level::INFO, "job", + job_id = %arc_job.id, root_job = field::Empty, workspace_id = %arc_job.workspace_id, worker = %worker_name, hostname = %hostname, tag = %arc_job.tag, + language = field::Empty, + script_path = field::Empty, flow_step_id = field::Empty, parent_job = field::Empty, + otel.name = field::Empty); + + let rj = arc_job.flow_innermost_root_job.unwrap_or(arc_job.id); + + if let Some(lg) = arc_job.script_lang.as_ref() { + span.record("language", lg.as_str()); + } + if let Some(step_id) = arc_job.flow_step_id.as_ref() { + span.record("otel.name", format!("job {}", step_id).as_str()); + span.record("flow_step_id", step_id.as_str()); + } else { + span.record("otel.name", "job"); + } + if let Some(parent_job) = arc_job.parent_job.as_ref() { + span.record("parent_job", parent_job.to_string().as_str()); + } + if let Some(script_path) = arc_job.runnable_path.as_ref() { + span.record("script_path", script_path.as_str()); + } + if let Some(root_job) = arc_job.flow_innermost_root_job.as_ref() { + span.record("root_job", root_job.to_string().as_str()); + } + + windmill_common::otel_oss::set_span_parent(&span, &rj); + span +} + +pub async fn handle_all_job_kind_error( + conn: &Connection, + authed_client: &AuthedClient, + job: Arc, + err: Error, + same_worker_tx: Option<&SameWorkerSender>, + worker_dir: &str, + worker_name: &str, + job_completed_tx: JobCompletedSender, + #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, +) { + match conn { + Connection::Sql(db) => { + handle_job_error( + db, + authed_client, + job.as_ref(), + 0, + None, + err, + false, + same_worker_tx, + &worker_dir, + &worker_name, + job_completed_tx.clone(), + #[cfg(feature = "benchmark")] + bench, + ) + .await; + } + Connection::Http(_) => { + job_completed_tx + .send_job( + JobCompleted { + preprocessed_args: None, + job: job.clone(), + result: Arc::new(windmill_common::worker::to_raw_value(&error_to_value( + err, + ))), + result_columns: None, + mem_peak: 0, + canceled_by: None, + success: false, + cached_res_path: None, + token: authed_client.token.clone(), + duration: None, + }, + false, + ) + .await + .expect("send job completed"); + } + } +} + +pub fn start_interactive_worker_shell( + conn: Connection, + hostname: String, + worker_name: String, + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, + job_completed_tx: JobCompletedSender, + base_internal_url: String, + worker_dir: String, +) -> JoinHandle<()> { + tokio::spawn(async move { + let mut occupancy_metrics = OccupancyMetrics::new(Instant::now()); + + let mut last_executed_job: Option = + Instant::now().checked_sub(Duration::from_millis(2500)); + + loop { + if let Ok(_) = killpill_rx.try_recv() { + break; + } else { + let pulled_job = match &conn { + Connection::Sql(db) => { + let query = ("".to_string(), make_pull_query(&[hostname.to_owned()])); + + #[cfg(feature = "benchmark")] + let mut bench = windmill_common::bench::BenchmarkIter::new(); + let job = pull( + &db, + false, + &worker_name, + Some(&query), + #[cfg(feature = "benchmark")] + &mut bench, + ) + .await; + + job.map(|x| x.job.map(NextJob::Sql)) + } + Connection::Http(client) => { + crate::agent_workers::pull_job(&client, None, Some(true)) + .await + .map_err(|e| error::Error::InternalErr(e.to_string())) + .map(|x| x.map(|y| NextJob::Http(y))) + } + }; + + match pulled_job { + Ok(Some(job)) => { + tracing::debug!(worker = %worker_name, hostname = %hostname, "started handling of job {}", job.id); + + let job_dir = create_job_dir(&worker_dir, job.id).await; + #[cfg(feature = "benchmark")] + let mut bench = windmill_common::bench::BenchmarkIter::new(); + + let JobAndPerms { + job, + raw_code, + raw_lock, + raw_flow, + parent_runnable_path, + token, + precomputed_agent_info: precomputed_bundle, + } = extract_job_and_perms(job, &conn).await; + + let authed_client = AuthedClient::new( + base_internal_url.to_owned(), + job.workspace_id.clone(), + token, + None, + ); + + let arc_job = Arc::new(job); + + let _ = handle_queued_job( + arc_job.clone(), + raw_code, + raw_lock, + raw_flow, + parent_runnable_path, + &conn, + &authed_client, + &hostname, + &worker_name, + &worker_dir, + &job_dir, + None, + &base_internal_url, + job_completed_tx.clone(), + &mut occupancy_metrics, + &mut killpill_rx, + precomputed_bundle, + #[cfg(feature = "benchmark")] + &mut bench, + ) + .await; + + last_executed_job = Some(Instant::now()); + } + Ok(None) => { + let now = Instant::now(); + match last_executed_job { + Some(last) + if now.duration_since(last).as_secs() + > TIMEOUT_TO_RESET_WORKER_SHELL_NAP_TIME_DURATION => + { + tokio::time::sleep(Duration::from_secs( + WORKER_SHELL_NAP_TIME_DURATION, + )) + .await; + } + _ => { + tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 10)).await; + } + } + } + + Err(err) => { + tracing::error!(worker = %worker_name, hostname = %hostname, "Failed to pull jobs: {}", err); + tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 20)).await; + } + }; + } + } + }) +} + +async fn create_job_dir(worker_directory: &str, job_id: impl Display) -> String { + let job_dir_path = format!("{}/{}", worker_directory, job_id); + + create_directory_async(&job_dir_path).await; + + job_dir_path +} pub async fn run_worker( - db: &Pool, + conn: &Connection, hostname: &str, worker_name: String, i_worker: u64, _num_workers: u32, ip: &str, mut killpill_rx: tokio::sync::broadcast::Receiver<()>, - killpill_tx: tokio::sync::broadcast::Sender<()>, + killpill_tx: KillpillSender, base_internal_url: &str, - agent_mode: bool, ) { #[cfg(not(feature = "enterprise"))] if !*DISABLE_NSJAIL { @@ -765,16 +907,16 @@ pub async fn run_worker( #[cfg(feature = "python")] { - let (db, worker_name, hostname, worker_dir) = ( - db.clone(), + let (conn, worker_name, hostname, worker_dir) = ( + conn.clone(), worker_name.clone(), hostname.to_owned(), worker_dir.clone(), ); tokio::spawn(async move { - if let Err(e) = PyVersion::from_instance_version(&Uuid::nil(), "", &db) + if let Err(e) = PyV::gravitational_version(&Uuid::nil(), "", Some(conn.clone())) .await - .get_python(&Uuid::nil(), &mut 0, &db, &worker_name, "", &mut None) + .try_get_python(&Uuid::nil(), &mut 0, &conn, &worker_name, "", &mut None) .await { tracing::error!( @@ -784,8 +926,8 @@ pub async fn run_worker( "Cannot preinstall or find Instance Python version to worker: {e}"// ); } - if let Err(e) = PyVersion::Py311 - .get_python(&Uuid::nil(), &mut 0, &db, &worker_name, "", &mut None) + if let Err(e) = PyV::from(PyVAlias::Py311) + .try_get_python(&Uuid::nil(), &mut 0, &conn, &worker_name, "", &mut None) .await { tracing::error!( @@ -803,10 +945,7 @@ pub async fn run_worker( write_file(&HOME_ENV, ".netrc", netrc).expect("could not write netrc"); } - DirBuilder::new() - .recursive(true) - .create(&worker_dir) - .expect("could not create initial worker dir"); + create_directory_async(&worker_dir).await; if !*DISABLE_NSJAIL { let _ = write_file( @@ -818,7 +957,9 @@ pub async fn run_worker( let mut last_ping = Instant::now() - Duration::from_secs(NUM_SECS_PING + 1); - update_ping(hostname, &worker_name, ip, db).await; + insert_ping(hostname, &worker_name, ip, conn) + .await + .expect("initial ping could be sent"); #[cfg(feature = "prometheus")] let uptime_metric = if METRICS_ENABLED.load(Ordering::Relaxed) { @@ -1027,7 +1168,11 @@ pub async fn run_worker( .unwrap(); #[cfg(feature = "benchmark")] - benchmark_init(benchmark_jobs, &db).await; + { + if let Some(db) = conn.as_sql() { + benchmark_init(benchmark_jobs, db).await; + } + } #[cfg(feature = "prometheus")] if let Some(ws) = WORKER_STARTED.as_ref() { @@ -1036,27 +1181,48 @@ pub async fn run_worker( let (same_worker_tx, mut same_worker_rx) = mpsc::channel::(5); - let (job_completed_tx, job_completed_rx) = mpsc::channel::(3); - - let job_completed_tx = JobCompletedSender(job_completed_tx); + let (job_completed_tx, job_completed_rx) = JobCompletedSender::new(&conn, 10); let same_worker_queue_size = Arc::new(AtomicU16::new(0)); let same_worker_tx = SameWorkerSender(same_worker_tx, same_worker_queue_size.clone()); - let job_completed_processor_is_done = Arc::new(AtomicBool::new(false)); + let job_completed_processor_is_done = + Arc::new(AtomicBool::new(matches!(conn, Connection::Http(_)))); - let send_result = start_background_processor( - job_completed_rx, - job_completed_tx.0.clone(), - same_worker_queue_size.clone(), - job_completed_processor_is_done.clone(), - base_internal_url.to_string(), - db.clone(), - worker_dir.clone(), - same_worker_tx.clone(), - worker_name.clone(), - killpill_tx.clone(), - is_dedicated_worker, - ); + let send_result = match (conn, job_completed_rx) { + (Connection::Sql(db), Some(job_completed_receiver)) => Some(start_background_processor( + job_completed_receiver, + job_completed_tx.clone(), + same_worker_queue_size.clone(), + job_completed_processor_is_done.clone(), + base_internal_url.to_string(), + db.clone(), + worker_dir.clone(), + same_worker_tx.clone(), + worker_name.clone(), + killpill_tx.clone(), + is_dedicated_worker, + )), + _ => None, + }; + // If we're the first worker to run, we start another background process that listens for a specific tag. + // This tag is associated only with jobs using Bash as the script language. + // For agent workers, the expected tag format is the worker name suffixed with "-ssh". + // For regular workers, the tag is simply the machine's hostname and if not found the randomly generated hostname. + let interactive_shell = if i_worker == 1 { + let it_shell = start_interactive_worker_shell( + conn.clone(), + hostname.to_owned(), + worker_name.clone(), + killpill_rx.resubscribe(), + job_completed_tx.clone(), + base_internal_url.to_owned(), + worker_dir.clone(), + ); + + Some(it_shell) + } else { + None + }; let mut last_executed_job: Option = None; @@ -1069,12 +1235,20 @@ pub async fn run_worker( let vacuum_shift = rand::rng().random_range(0..VACUUM_PERIOD); IS_READY.store(true, Ordering::Relaxed); - tracing::info!( - worker = %worker_name, hostname = %hostname, - "listening for jobs, WORKER_GROUP: {}, config: {:?}", - *WORKER_GROUP, - WORKER_CONFIG.read().await - ); + if let Some(token) = DECODED_AGENT_TOKEN.as_ref() { + tracing::info!( + worker = %worker_name, hostname = %hostname, + "listening for jobs, agent mode, tags: {:?}", + token.tags + ); + } else { + tracing::info!( + worker = %worker_name, hostname = %hostname, + "listening for jobs, WORKER_GROUP: {}, config: {:?}", + *WORKER_GROUP, + WORKER_CONFIG.read().await + ); + } // (dedi_path, dedicated_worker_tx, dedicated_worker_handle) // Option>>, @@ -1082,30 +1256,35 @@ pub async fn run_worker( #[cfg(feature = "enterprise")] let (dedicated_workers, is_flow_worker, dedicated_handles): ( - HashMap>>, + HashMap>>, bool, Vec>, - ) = create_dedicated_worker_map( - &killpill_tx, - &killpill_rx, - db, - &worker_dir, - base_internal_url, - &worker_name, - &job_completed_tx, - ) - .await; + ) = match conn { + Connection::Sql(pool) => { + create_dedicated_worker_map( + &killpill_tx, + &killpill_rx, + pool, + &worker_dir, + base_internal_url, + &worker_name, + &job_completed_tx, + ) + .await + } + Connection::Http(_) => (HashMap::new(), false, vec![]), + }; #[cfg(not(feature = "enterprise"))] let (dedicated_workers, is_flow_worker, dedicated_handles): ( - HashMap>>, + HashMap>>, bool, Vec>, ) = (HashMap::new(), false, vec![]); if i_worker == 1 { - if let Err(e) = queue_init_bash_maybe(db, same_worker_tx.clone(), &worker_name).await { - killpill_tx.send(()).unwrap_or_default(); + if let Err(e) = queue_init_bash_maybe(conn, same_worker_tx.clone(), &worker_name).await { + killpill_tx.send(); tracing::error!(worker = %worker_name, hostname = %hostname, "Error queuing init bash script for worker {worker_name}: {e:#}"); return; } @@ -1131,7 +1310,7 @@ pub async fn run_worker( }; let mut suspend_first_success = false; let mut last_reading = Instant::now() - Duration::from_secs(NUM_SECS_READINGS + 1); - let mut last_30jobs_suspended: Vec = vec![false; 30]; + let mut last_30jobs_suspended = 0; let mut last_suspend_first = Instant::now(); let mut killed_but_draining_same_worker_jobs = false; @@ -1141,11 +1320,12 @@ pub async fn run_worker( { if let Ok(_) = killpill_rx.try_recv() { tracing::info!(worker = %worker_name, hostname = %hostname, "killpill received on worker waiting for valid key"); - job_completed_tx - .0 - .send(SendResult::Kill) - .await - .expect("send kill to job completed tx"); + if send_result.is_some() { + job_completed_tx + .kill() + .await + .expect("send kill to job completed tx"); + } break; } let valid_key = *LICENSE_KEY_VALID.read().await; @@ -1153,7 +1333,7 @@ pub async fn run_worker( if !valid_key { tracing::error!( worker = %worker_name, hostname = %hostname, - "Invalid license key, workers require a valid license key, sleeping for 30s waiting for valid key to be set" + "Invalid license key, workers require a valid license key, sleeping for 10s waiting for valid key to be set" ); tokio::time::sleep(Duration::from_secs(10)).await; continue; @@ -1182,100 +1362,41 @@ pub async fn run_worker( } if last_ping.elapsed().as_secs() > NUM_SECS_PING { - let tags = WORKER_CONFIG.read().await.worker_tags.clone(); - - let memory_usage = get_worker_memory_usage(); - let wm_memory_usage = get_windmill_memory_usage(); - - let (vcpus, memory) = if *REFRESH_CGROUP_READINGS - && last_reading.elapsed().as_secs() > NUM_SECS_READINGS - { - last_reading = Instant::now(); - (get_vcpus(), get_memory()) - } else { - (None, None) - }; - - let (occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m) = - occupancy_metrics.update_occupancy_metrics(); - - if let Err(e) = (|| sqlx::query!( - "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, - occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus), - memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", + let read_cgroups = + *REFRESH_CGROUP_READINGS && last_reading.elapsed().as_secs() > NUM_SECS_READINGS; + update_worker_ping_full( + &conn, + read_cgroups, jobs_executed, - tags.as_slice(), - occupancy_rate, - memory_usage, - wm_memory_usage, &worker_name, - vcpus, - memory, - occupancy_rate_15s, - occupancy_rate_5m, - occupancy_rate_30m - ).execute(db)).retry( - ConstantBuilder::default() - .with_delay(std::time::Duration::from_secs(2)) - .with_max_times(10) - .build(), + &hostname, + &mut occupancy_metrics, + &killpill_tx, ) - .notify(|err, dur| { - tracing::error!( - worker = %worker_name, hostname = %hostname, - "retrying updating worker ping in {dur:#?}, err: {err:#?}" - ); - }) - .sleep(tokio::time::sleep) - .await { - tracing::error!( - worker = %worker_name, hostname = %hostname, - "failed to update worker ping, exiting: {}", e); - killpill_tx.send(()).unwrap_or_default(); - } - tracing::info!( - worker = %worker_name, hostname = %hostname, - "ping update, memory: container={}MB, windmill={}MB", - memory_usage.unwrap_or_default() / (1024 * 1024), - wm_memory_usage.unwrap_or_default() / (1024 * 1024) - ); + .await; + if read_cgroups { + last_reading = Instant::now(); + } last_ping = Instant::now(); } if (jobs_executed as u32 + vacuum_shift) % VACUUM_PERIOD == 0 { - let db2 = db.clone(); - let current_span = tracing::Span::current(); - let worker_name = worker_name.clone(); - let hostname = hostname.to_string(); - tokio::task::spawn( - (async move { - tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue"); - if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status") - .execute(&db2) - .await - { - tracing::error!(worker = %worker_name, hostname = %hostname, "failed to vacuum queue: {}", e); - } - tracing::info!(worker = %worker_name, hostname = %hostname, "vacuumed queue"); - }) - .instrument(current_span), - ); + queue_vacuum(&conn, &worker_name, &hostname).await; jobs_executed += 1; } - #[cfg(any(target_os = "linux"))] - if (jobs_executed as u32 + 1) % DROP_CACHE_PERIOD == 0 { - drop_cache().await; - jobs_executed += 1; - } + // #[cfg(any(target_os = "linux"))] + // if (jobs_executed as u32 + 1) % DROP_CACHE_PERIOD == 0 { + // drop_cache().await; + // jobs_executed += 1; + // } #[cfg(feature = "benchmark")] if benchmark_jobs > 0 && infos.iters == benchmark_jobs as u64 { tracing::info!("benchmark finished, exiting"); job_completed_tx - .0 - .send(SendResult::Kill) + .kill() .await .expect("send kill to job completed tx"); break; @@ -1297,50 +1418,43 @@ pub async fn run_worker( "received {} from same worker channel", same_worker_job.job_id ); - let r = sqlx::query_as::<_, PulledJob>( - " - WITH ping AS ( - UPDATE v2_job_runtime SET ping = NOW() WHERE id = $1 RETURNING id - ) - SELECT * FROM v2_as_queue WHERE id = (SELECT id FROM ping) - ", - ) - .bind(same_worker_job.job_id) - .fetch_optional(db) - .await - .map_err(|e| { - Error::internal_err(format!( - "Impossible to fetch same_worker job {}: {}", - same_worker_job.job_id, e - )) - }); - let _ = sqlx::query!( - "UPDATE v2_job_queue SET started_at = NOW() WHERE id = $1", - same_worker_job.job_id - ) - .execute(db) - .await; - if r.is_err() && !same_worker_job.recoverable { - tracing::error!( - worker = %worker_name, hostname = %hostname, - "failed to fetch same_worker job on a non recoverable job, exiting" - ); - job_completed_tx - .0 - .send(SendResult::Kill) + + match &conn { + Connection::Sql(db) => { + let job = get_same_worker_job(db, &same_worker_job).await; + // tracing::error!("r: {:?}", r); + if job.is_err() && !same_worker_job.recoverable { + tracing::error!( + worker = %worker_name, hostname = %hostname, + "failed to fetch same_worker job on a non recoverable job, exiting: {job:?}", + ); + job_completed_tx + .kill() + .await + .expect("send kill to job completed tx"); + break; + } else { + job.map(|x| x.map(NextJob::Sql)) + } + } + Connection::Http(client) => client + .post( + &format!( + "/api/agent_workers/same_worker_job/{}", + same_worker_job.job_id + ), + None, + &same_worker_job, + ) .await - .expect("send kill to job completed tx"); - break; - } else { - r + .map_err(|e| error::Error::InternalErr(e.to_string())) + .map(|x: Option| x.map(|y| NextJob::Http(y))), } } else if let Ok(_) = killpill_rx.try_recv() { if !killed_but_draining_same_worker_jobs { - tracing::info!(worker = %worker_name, hostname = %hostname, "received killpill for worker {}, jobs are not pulled anymore except same_worker jobs", i_worker); killed_but_draining_same_worker_jobs = true; job_completed_tx - .0 - .send(SendResult::Kill) + .kill() .await .expect("send kill to job completed tx"); } @@ -1355,75 +1469,91 @@ pub async fn run_worker( continue; } } else { - let pull_time = Instant::now(); - let likelihood_of_suspend = - (1.0 + last_30jobs_suspended.iter().filter(|&&x| x).count() as f64) / 31.0; - let suspend_first = suspend_first_success - || rand::random::() < likelihood_of_suspend - || last_suspend_first.elapsed().as_secs_f64() > 5.0; + match &conn { + Connection::Sql(db) => { + let pull_time = Instant::now(); + let likelihood_of_suspend = last_30jobs_suspended as f64 / 30.0; + let suspend_first = suspend_first_success + || rand::random::() < likelihood_of_suspend + || last_suspend_first.elapsed().as_secs_f64() > 5.0; - if suspend_first { - last_suspend_first = Instant::now(); + if suspend_first { + last_suspend_first = Instant::now(); + } + + let job = pull( + &db, + suspend_first, + &worker_name, + None, + #[cfg(feature = "benchmark")] + &mut bench, + ) + .await; + + add_time!(bench, "job pulled from DB"); + let duration_pull_s = pull_time.elapsed().as_secs_f64(); + let err_pull = job.is_ok(); + // let empty = job.as_ref().is_ok_and(|x| x.is_none()); + + if duration_pull_s > 0.5 { + let empty = job.as_ref().is_ok_and(|x| x.job.is_none()); + tracing::warn!(worker = %worker_name, hostname = %hostname, "pull took more than 0.5s ({duration_pull_s}), this is a sign that the database is VERY undersized for this load. empty: {empty}, err: {err_pull}"); + #[cfg(feature = "prometheus")] + if empty { + if let Some(wp) = worker_pull_over_500_counter_empty.as_ref() { + wp.inc(); + } + } else if let Some(wp) = worker_pull_over_500_counter.as_ref() { + wp.inc(); + } + } else if duration_pull_s > 0.1 { + let empty = job.as_ref().is_ok_and(|x| x.job.is_none()); + tracing::warn!(worker = %worker_name, hostname = %hostname, "pull took more than 0.1s ({duration_pull_s}) this is a sign that the database is undersized for this load. empty: {empty}, err: {err_pull}"); + #[cfg(feature = "prometheus")] + if empty { + if let Some(wp) = worker_pull_over_100_counter_empty.as_ref() { + wp.inc(); + } + } else if let Some(wp) = worker_pull_over_100_counter.as_ref() { + wp.inc(); + } + } + + if let Ok(j) = job.as_ref() { + let suspend_success = j.suspended; + if suspend_first { + if last_30jobs_suspended < 30 { + last_30jobs_suspended += 1; + } + } else { + last_30jobs_suspended -= 1; + } + suspend_first_success = suspend_first && suspend_success; + #[cfg(feature = "prometheus")] + if j.job.is_some() { + if let Some(wp) = worker_pull_duration_counter.as_ref() { + wp.inc_by(duration_pull_s); + } + if let Some(wp) = worker_pull_duration.as_ref() { + wp.observe(duration_pull_s); + } + } else { + if let Some(wp) = worker_pull_duration_counter_empty.as_ref() { + wp.inc_by(duration_pull_s); + } + if let Some(wp) = worker_pull_duration_empty.as_ref() { + wp.observe(duration_pull_s); + } + } + } + job.map(|x| x.job.map(NextJob::Sql)) + } + Connection::Http(client) => crate::agent_workers::pull_job(&client, None, None) + .await + .map_err(|e| error::Error::InternalErr(e.to_string())) + .map(|x| x.map(|y| NextJob::Http(y))), } - - let job = pull(&db, suspend_first, &worker_name).await; - - add_time!(bench, "job pulled from DB"); - let duration_pull_s = pull_time.elapsed().as_secs_f64(); - let err_pull = job.is_ok(); - // let empty = job.as_ref().is_ok_and(|x| x.is_none()); - - if !agent_mode && duration_pull_s > 0.5 { - let empty = job.as_ref().is_ok_and(|x| x.0.is_none()); - tracing::warn!(worker = %worker_name, hostname = %hostname, "pull took more than 0.5s ({duration_pull_s}), this is a sign that the database is VERY undersized for this load. empty: {empty}, err: {err_pull}"); - #[cfg(feature = "prometheus")] - if empty { - if let Some(wp) = worker_pull_over_500_counter_empty.as_ref() { - wp.inc(); - } - } else if let Some(wp) = worker_pull_over_500_counter.as_ref() { - wp.inc(); - } - } else if !agent_mode && duration_pull_s > 0.1 { - let empty = job.as_ref().is_ok_and(|x| x.0.is_none()); - tracing::warn!(worker = %worker_name, hostname = %hostname, "pull took more than 0.1s ({duration_pull_s}) this is a sign that the database is undersized for this load. empty: {empty}, err: {err_pull}"); - #[cfg(feature = "prometheus")] - if empty { - if let Some(wp) = worker_pull_over_100_counter_empty.as_ref() { - wp.inc(); - } - } else if let Some(wp) = worker_pull_over_100_counter.as_ref() { - wp.inc(); - } - } - - if let Ok(j) = job.as_ref() { - let suspend_success = j.1; - if suspend_first { - last_30jobs_suspended.push(suspend_success); - if last_30jobs_suspended.len() > 30 { - last_30jobs_suspended.remove(0); - } - } - suspend_first_success = suspend_first && suspend_success; - #[cfg(feature = "prometheus")] - if j.0.is_some() { - if let Some(wp) = worker_pull_duration_counter.as_ref() { - wp.inc_by(duration_pull_s); - } - if let Some(wp) = worker_pull_duration.as_ref() { - wp.observe(duration_pull_s); - } - } else { - if let Some(wp) = worker_pull_duration_counter_empty.as_ref() { - wp.inc_by(duration_pull_s); - } - if let Some(wp) = worker_pull_duration_empty.as_ref() { - wp.observe(duration_pull_s); - } - } - } - job.map(|x| x.0) } }; @@ -1442,16 +1572,17 @@ pub async fn run_worker( tracing::debug!(worker = %worker_name, hostname = %hostname, "started handling of job {}", job.id); - if matches!(job.job_kind, JobKind::Script | JobKind::Preview) { + if matches!(job.kind, JobKind::Script | JobKind::Preview) { if !dedicated_workers.is_empty() { let key_o = if is_flow_worker { job.flow_step_id.as_ref().map(|x| x.to_string()) } else { - job.script_path.as_ref().map(|x| x.to_string()) + job.runnable_path.as_ref().map(|x| x.to_string()) }; if let Some(key) = key_o { if let Some(dedicated_worker_tx) = dedicated_workers.get(&key) { - if let Err(e) = dedicated_worker_tx.send(Arc::new(job.job)).await { + if let Err(e) = dedicated_worker_tx.send(Arc::new(job.job())).await + { tracing::info!("failed to send jobs to dedicated workers. Likely dedicated worker has been shut down. This is normal: {e:?}"); } @@ -1466,26 +1597,30 @@ pub async fn run_worker( } } } - if matches!(job.job_kind, JobKind::Noop) { + + if matches!(job.kind, JobKind::Noop) { add_time!(bench, "send job completed START"); job_completed_tx - .send(JobCompleted { - job: Arc::new(job.job), - success: true, - result: Arc::new(empty_result()), - result_columns: None, - mem_peak: 0, - cached_res_path: None, - token: "".to_string(), - canceled_by: None, - duration: None, - }) + .send_job( + JobCompleted { + preprocessed_args: None, + job: Arc::new(job.job()), + success: true, + result: Arc::new(empty_result()), + result_columns: None, + mem_peak: 0, + cached_res_path: None, + token: "".to_string(), + canceled_by: None, + duration: None, + }, + true, + ) .await .expect("send job completed END"); add_time!(bench, "sent job completed"); } else { - let token = create_token_for_owner_in_bg(&db, &job).await; - add_outstanding_wait_time(&job, db, OUTSTANDING_WAIT_TIME_THRESHOLD_MS); + add_outstanding_wait_time(&conn, &job, OUTSTANDING_WAIT_TIME_THRESHOLD_MS); #[cfg(feature = "prometheus")] register_metric( @@ -1532,32 +1667,24 @@ pub async fn run_worker( .await; let job_root = job - .root_job + .flow_innermost_root_job .map(|x| x.to_string()) .unwrap_or_else(|| "none".to_string()); if job.id == Uuid::nil() { tracing::info!("running warmup job"); } else { - tracing::info!(workspace_id = %job.workspace_id, job_id = %job.id, root_id = %job_root, "fetched job {}, root job: {}", job.id, job_root); + tracing::info!(workspace_id = %job.workspace_id, job_id = %job.id, root_id = %job_root, "fetched job {} (root job: {}, scheduled for: {})", job.id, job_root, job.scheduled_for); } // Here we can't remove the job id, but maybe with the // fields macro we can make a job id that only appears when // the job is defined? - let job_dir = format!("{worker_dir}/{}", job.id); - - DirBuilder::new() - .recursive(true) - .create(&job_dir) - .expect("could not create job dir"); + let job_dir = create_job_dir(&worker_dir, job.id).await; let same_worker = job.same_worker; - let folder = if job.language == Some(ScriptLang::Go) { - DirBuilder::new() - .recursive(true) - .create(&format!("{job_dir}/go")) - .expect("could not create go dir"); + let folder = if job.script_lang == Some(ScriptLang::Go) { + create_directory_async(&format!("{job_dir}/go")).await; "/go" } else { "" @@ -1569,103 +1696,84 @@ pub async fn run_worker( if tokio::fs::metadata(target).await.is_err() { let parent_flow = job.parent_job.unwrap(); let parent_shared_dir = format!("{worker_dir}/{parent_flow}/shared"); - DirBuilder::new() - .recursive(true) - .create(&parent_shared_dir) - .expect("could not create parent shared dir"); - + create_directory_async(&parent_shared_dir).await; symlink(&parent_shared_dir, target) .await .expect("could not symlink target"); } } else { - DirBuilder::new() - .recursive(true) - .create(target) - .expect("could not create shared dir"); + create_directory_async(target).await; } - let authed_client = AuthedClientBackgroundTask { - base_internal_url: base_internal_url.to_string(), - token, - workspace: job.workspace_id.to_string(), - }; - #[cfg(feature = "prometheus")] let tag = job.tag.clone(); let is_init_script: bool = job.tag.as_str() == INIT_SCRIPT_TAG; - let PulledJob { job, raw_code, raw_lock, raw_flow } = job; + let is_flow = job.is_flow(); + let job_id = job.id; + + let JobAndPerms { + job, + raw_code, + raw_lock, + raw_flow, + parent_runnable_path, + token, + precomputed_agent_info: precomputed_bundle, + } = extract_job_and_perms(job, &conn).await; + + let authed_client = AuthedClient::new( + base_internal_url.to_owned(), + job.workspace_id.clone(), + token, + None, + ); + let arc_job = Arc::new(job); - add_time!(bench, "handle_queued_job START"); - let span = tracing::span!(tracing::Level::INFO, "job", - job_id = %arc_job.id, root_job = field::Empty, workspace_id = %arc_job.workspace_id, worker = %worker_name, hostname = %hostname, tag = %arc_job.tag, - language = field::Empty, - script_path = field::Empty, flow_step_id = field::Empty, parent_job = field::Empty, - otel.name = field::Empty); - let rj = if let Some(root_job) = arc_job.root_job { - root_job - } else { - arc_job.id - }; - if let Some(lg) = arc_job.language.as_ref() { - span.record("language", lg.as_str()); - } - if let Some(step_id) = arc_job.flow_step_id.as_ref() { - span.record("otel.name", format!("job {}", step_id).as_str()); - span.record("flow_step_id", step_id.as_str()); - } else { - span.record("otel.name", "job"); - } - if let Some(parent_job) = arc_job.parent_job.as_ref() { - span.record("parent_job", parent_job.to_string().as_str()); - } - if let Some(script_path) = arc_job.script_path.as_ref() { - span.record("script_path", script_path.as_str()); - } - if let Some(root_job) = arc_job.root_job.as_ref() { - span.record("root_job", root_job.to_string().as_str()); - } + let span = create_span(&arc_job, &worker_name, hostname); - windmill_common::otel_ee::set_span_parent(&span, &rj); - // span.context().span().add_event_with_timestamp("job created".to_string(), arc_job.created_at.into(), vec![]); - - match handle_queued_job( + let job_result = handle_queued_job( arc_job.clone(), raw_code, raw_lock, raw_flow, - db, + parent_runnable_path, + &conn, &authed_client, - &hostname, + hostname, &worker_name, &worker_dir, &job_dir, - same_worker_tx.clone(), + Some(same_worker_tx.clone()), base_internal_url, job_completed_tx.clone(), &mut occupancy_metrics, &mut killpill_rx2, + precomputed_bundle, #[cfg(feature = "benchmark")] &mut bench, ) .instrument(span) - .await - { + .await; + + match job_result { + Ok(false) if is_init_script => { + tracing::error!("init script job failed, exiting"); + update_worker_ping_for_failed_init_script(conn, &worker_name, job_id) + .await; + break; + } Err(err) => { - handle_job_error( - db, - &authed_client.get_authed().await, - arc_job.as_ref(), - 0, - None, + handle_all_job_kind_error( + &conn, + &authed_client, + arc_job.clone(), err, - false, - same_worker_tx.clone(), + Some(&same_worker_tx), &worker_dir, &worker_name, - (&job_completed_tx.0).clone(), + job_completed_tx.clone(), #[cfg(feature = "benchmark")] &mut bench, ) @@ -1673,20 +1781,14 @@ pub async fn run_worker( if is_init_script { tracing::error!("init script job failed (in handler), exiting"); update_worker_ping_for_failed_init_script( - db, + conn, &worker_name, - arc_job.id, + job_id, ) .await; break; } } - Ok(false) if is_init_script => { - tracing::error!("init script job failed, exiting"); - update_worker_ping_for_failed_init_script(db, &worker_name, arc_job.id) - .await; - break; - } _ => {} } @@ -1711,8 +1813,7 @@ pub async fn run_worker( .await; } - if !KEEP_JOB_DIR.load(Ordering::Relaxed) && !(arc_job.is_flow() && same_worker) - { + if !KEEP_JOB_DIR.load(Ordering::Relaxed) && !(is_flow && same_worker) { let _ = tokio::fs::remove_dir_all(job_dir).await; } } @@ -1761,6 +1862,7 @@ pub async fn run_worker( } Err(err) => { tracing::error!(worker = %worker_name, hostname = %hostname, "Failed to pull jobs: {}", err); + tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 5)).await; } }; } @@ -1789,58 +1891,35 @@ pub async fn run_worker( drop(job_completed_tx); tracing::info!(worker = %worker_name, hostname = %hostname, "waiting for job_completed_processor to finish processing remaining jobs"); - if let Err(e) = send_result.await { - tracing::error!("error in awaiting send_result process: {e:?}") + if let Some(send_result) = send_result { + if let Err(e) = send_result.await { + tracing::error!("error in awaiting send_result process: {e:?}") + } + } + if let Some(interactive_shell) = interactive_shell { + if let Err(e) = interactive_shell.await { + tracing::error!("error in awaiting interactive_shell process: {e:?}") + } } tracing::info!(worker = %worker_name, hostname = %hostname, "worker {} exited", worker_name); tracing::info!(worker = %worker_name, hostname = %hostname, "number of jobs executed: {}", jobs_executed); } async fn queue_init_bash_maybe<'c>( - db: &Pool, + conn: &Connection, same_worker_tx: SameWorkerSender, worker_name: &str, -) -> error::Result { - if let Some(content) = WORKER_CONFIG.read().await.init_bash.clone() { - let tx = PushIsolationLevel::IsolatedRoot(db.clone()); - let ehm = HashMap::new(); - let (uuid, inner_tx) = push( - &db, - tx, - "admins", - windmill_common::jobs::JobPayload::Code(windmill_common::jobs::RawCode { - hash: None, - content: content.clone(), - path: Some(format!("init_script_{worker_name}")), - language: ScriptLang::Bash, - lock: None, - custom_concurrency_key: None, - concurrent_limit: None, - concurrency_time_window_s: None, - cache_ttl: None, - dedicated_worker: None, - }), - PushArgs::from(&ehm), - worker_name, - "worker@windmill.dev", - SUPERADMIN_SECRET_EMAIL.to_string(), - None, - None, - None, - None, - None, - false, - true, - None, - true, - Some("init_script".to_string()), - None, - None, - None, - None, - ) - .await?; - inner_tx.commit().await?; +) -> anyhow::Result { + let uuid_content = if let Some(content) = WORKER_CONFIG.read().await.init_bash.clone() { + let uuid = match conn { + Connection::Sql(db) => push_init_job(db, content.clone(), worker_name).await?, + Connection::Http(client) => queue_init_job(client, &content).await?, + }; + Some((uuid, content)) + } else { + None + }; + if let Some((uuid, content)) = uuid_content { same_worker_tx .send(SameWorkerPayload { job_id: uuid, recoverable: false }) .await @@ -1854,43 +1933,31 @@ async fn queue_init_bash_maybe<'c>( pub enum SendResult { JobCompleted(JobCompleted), - UpdateFlow { - flow: Uuid, - w_id: String, - success: bool, - result: Box, - worker_dir: String, - stop_early_override: Option, - token: String, - }, - Kill, + UpdateFlow(UpdateFlow), } -#[derive(Debug, Clone)] -pub struct JobCompleted { - pub job: Arc, - pub result: Arc>, - pub result_columns: Option>, - pub mem_peak: i32, +pub struct UpdateFlow { + pub flow: Uuid, + pub w_id: String, pub success: bool, - pub cached_res_path: Option, + pub result: Box, + pub worker_dir: String, + pub stop_early_override: Option, pub token: String, - pub canceled_by: Option, - pub duration: Option, } async fn do_nativets( - job: &QueuedJob, - client: &AuthedClientBackgroundTask, + job: &MiniPulledJob, + client: &AuthedClient, env_code: String, code: String, - db: &Pool, + conn: &Connection, mem_peak: &mut i32, canceled_by: &mut Option, worker_name: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let args = build_args_map(job, client, db).await?.map(Json); + let args = build_args_map(job, client, conn).await?.map(Json); let job_args = if args.is_some() { args.as_ref() } else { @@ -1902,9 +1969,10 @@ async fn do_nativets( code.clone(), transpile_ts(code)?, job_args, + None, job.id, job.timeout, - db, + conn, mem_peak, canceled_by, worker_name, @@ -1921,27 +1989,29 @@ pub struct PreviousResult<'a> { pub previous_result: Option<&'a RawValue>, } -async fn handle_queued_job( - job: Arc, +pub async fn handle_queued_job( + job: Arc, raw_code: Option, raw_lock: Option, raw_flow: Option>>, - db: &DB, - client: &AuthedClientBackgroundTask, + parent_runnable_path: Option, + conn: &Connection, + client: &AuthedClient, hostname: &str, worker_name: &str, worker_dir: &str, job_dir: &str, - same_worker_tx: SameWorkerSender, + same_worker_tx: Option, base_internal_url: &str, job_completed_tx: JobCompletedSender, occupancy_metrics: &mut OccupancyMetrics, killpill_rx: &mut tokio::sync::broadcast::Receiver<()>, + precomputed_agent_info: Option, #[cfg(feature = "benchmark")] _bench: &mut BenchmarkIter, ) -> windmill_common::error::Result { // Extract the active span from the context - if job.canceled { + if job.canceled_by.is_some() { return Err(Error::JsonErr(canceled_job_to_result(&job))); } if let Some(e) = &job.pre_run_error { @@ -1949,79 +2019,57 @@ async fn handle_queued_job( } #[cfg(any(not(feature = "enterprise"), feature = "sqlx"))] - if job.parent_job.is_none() && job.created_by.starts_with("email-") { - let daily_count = sqlx::query!( + match conn { + Connection::Sql(db) => { + if job.parent_job.is_none() && job.created_by.starts_with("email-") { + let daily_count = sqlx::query!( "SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1" ).fetch_optional(db) .warn_after_seconds(5) .await?.map(|x| serde_json::from_value::(x.value).unwrap_or(1)); - if let Some(count) = daily_count { - if count >= 100 { - return Err(error::Error::QuotaExceeded(format!( - "Email trigger usage limit of 100 per day has been reached." - ))); - } else { - sqlx::query!( + if let Some(count) = daily_count { + if count >= 100 { + return Err(error::Error::QuotaExceeded(format!( + "Email trigger usage limit of 100 per day has been reached." + ))); + } else { + sqlx::query!( "UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'", serde_json::json!(count + 1) ) .execute(db) .warn_after_seconds(5) .await?; - } - } else { - sqlx::query!( + } + } else { + sqlx::query!( "INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))" ) - .execute(db) - .warn_after_seconds(5) - .await?; + .execute(db) + .warn_after_seconds(5) + .await?; + } + } + } + Connection::Http(_) => { + return Err(Error::internal_err(format!( + "Could not check email trigger usage for job with agent worker {}", + job.id + ))) } } - if job.is_flow_step { - let _ = update_flow_status_in_progress( - db, - &job.workspace_id, - job.parent_job - .ok_or_else(|| Error::internal_err(format!("expected parent job")))?, - job.id, - ) - .warn_after_seconds(5) - .await?; - } else if let Some(parent_job) = job.parent_job { - let _ = sqlx::query_scalar!( - "UPDATE v2_job_status SET - workflow_as_code_status = jsonb_set( - jsonb_set( - COALESCE(workflow_as_code_status, '{}'::jsonb), - array[$1], - COALESCE(workflow_as_code_status->$1, '{}'::jsonb) - ), - array[$1, 'started_at'], - to_jsonb(now()::text) - ) - WHERE id = $2", - &job.id.to_string(), - parent_job - ) - .execute(db) - .warn_after_seconds(5) - .await - .inspect_err(|e| { - tracing::error!( - "Could not update parent job `started_at` in workflow as code status: {}", - e - ) - }); + // no need to mark job as started if http conn, it's done by the server when pulled + if let Connection::Sql(db) = conn { + job.mark_as_started_if_step(db).await?; } let started = Instant::now(); // Pre-fetch preview jobs raw values if necessary. // The `raw_*` values passed to this function are the original raw values from `queue` tables, // they are kept for backward compatibility as they have been moved to the `job` table. - let preview_data = match (job.job_kind, job.script_hash) { + let preview_data = match (job.kind, job.runnable_id) { ( JobKind::Preview | JobKind::Dependencies @@ -2031,7 +2079,7 @@ async fn handle_queued_job( x, ) => match x.map(|x| x.0) { None | Some(PREVIEW_IS_CODEBASE_HASH) | Some(PREVIEW_IS_TAR_CODEBASE_HASH) => { - Some(cache::job::fetch_preview(db, &job.id, raw_lock, raw_code, raw_flow).await?) + Some(cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow).await?) } _ => None, }, @@ -2039,67 +2087,90 @@ async fn handle_queued_job( }; let cached_res_path = if job.cache_ttl.is_some() { - Some(cached_result_path(db, &client.get_authed().await, &job, preview_data.as_ref()).await) + match conn { + Connection::Sql(db) => { + Some(cached_result_path(db, &client, &job, preview_data.as_ref()).await) + } + Connection::Http(_) => None, + } } else { None }; - if let Some(cached_res_path) = cached_res_path.as_ref() { - let authed_client = client.get_authed().await; + if let Some(db) = conn.as_sql() { + if let Some(cached_res_path) = cached_res_path.as_ref() { + let cached_result_maybe = get_cached_resource_value_if_valid( + db, + &client, + &job.id, + &job.workspace_id, + &cached_res_path, + ) + .warn_after_seconds(5) + .await; + if let Some(result) = cached_result_maybe { + { + let logs = "Job skipped because args & path found in cache and not expired" + .to_string(); + append_logs(&job.id, &job.workspace_id, logs, conn).await; + } + let result = job_completed_tx + .send_job( + JobCompleted { + preprocessed_args: None, + job, + result, + result_columns: None, + mem_peak: 0, + canceled_by: None, + success: true, + cached_res_path: None, + token: client.token.clone(), + duration: None, + }, + true, + ) + .await; - let cached_result_maybe = get_cached_resource_value_if_valid( - db, - &authed_client, - &job.id, - &job.workspace_id, - &cached_res_path, - ) - .warn_after_seconds(5) - .await; - if let Some(result) = cached_result_maybe { - { - let logs = - "Job skipped because args & path found in cache and not expired".to_string(); - append_logs(&job.id, &job.workspace_id, logs, db).await; + match result { + Ok(_) => { + tracing::debug!("Send job completed") + } + Err(err) => { + tracing::error!("An error occurred while sending job completed: {:#?}", err) + } + } + + return Ok(true); } - job_completed_tx - .send(JobCompleted { - job, - result, - result_columns: None, - mem_peak: 0, - canceled_by: None, - success: true, - cached_res_path: None, - token: authed_client.token, - duration: None, - }) - .await - .expect("send job completed"); - - return Ok(true); - } - }; - if job.is_flow() { - let flow_data = match preview_data { - Some(RawData::Flow(data)) => data, - // Not a preview: fetch from the cache or the database. - _ => cache::job::fetch_flow(db, job.job_kind, job.script_hash).await?, }; - handle_flow( - job, - &flow_data, - db, - &client.get_authed().await, - None, - same_worker_tx, - worker_dir, - job_completed_tx.0.clone(), - worker_name, - ) - .warn_after_seconds(10) - .await?; - Ok(true) + } + if job.is_flow() { + if let Some(db) = conn.as_sql() { + let flow_data = match preview_data { + Some(RawData::Flow(data)) => data, + // Not a preview: fetch from the cache or the database. + _ => cache::job::fetch_flow(db, job.kind, job.runnable_id).await?, + }; + handle_flow( + job, + &flow_data, + db, + &client, + None, + &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS), + worker_dir, + job_completed_tx.clone(), + worker_name, + ) + .warn_after_seconds(10) + .await?; + Ok(true) + } else { + return Err(Error::internal_err( + "Could not handle flow job with agent worker".to_string(), + )); + } } else { let mut logs = "".to_string(); let mut mem_peak: i32 = 0; @@ -2135,15 +2206,60 @@ async fn handle_queued_job( "handling job {}", job.id ); - append_logs(&job.id, &job.workspace_id, logs, db).await; + append_logs(&job.id, &job.workspace_id, logs, conn).await; let mut column_order: Option> = None; let mut new_args: Option>> = None; - let result = match job.job_kind { - JobKind::Dependencies => { - handle_dependency_job( + let result = match job.kind { + JobKind::Dependencies => match conn { + Connection::Sql(db) => { + handle_dependency_job( + &job, + preview_data.as_ref(), + &mut mem_peak, + &mut canceled_by, + job_dir, + db, + worker_name, + worker_dir, + base_internal_url, + &client.token, + occupancy_metrics, + ) + .await + } + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle dependency job with agent worker".to_string(), + )); + } + }, + JobKind::FlowDependencies => match conn { + Connection::Sql(db) => { + handle_flow_dependency_job( + &job, + preview_data.as_ref(), + &mut mem_peak, + &mut canceled_by, + job_dir, + db, + worker_name, + worker_dir, + base_internal_url, + &client.token, + occupancy_metrics, + ) + .await + } + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle flow dependency job with agent worker".to_string(), + )); + } + }, + JobKind::AppDependencies => match conn { + Connection::Sql(db) => handle_app_dependency_job( &job, - preview_data.as_ref(), &mut mem_peak, &mut canceled_by, job_dir, @@ -2151,41 +2267,17 @@ async fn handle_queued_job( worker_name, worker_dir, base_internal_url, - &client.get_token().await, + &client.token, occupancy_metrics, ) .await - } - JobKind::FlowDependencies => { - handle_flow_dependency_job( - &job, - preview_data.as_ref(), - &mut mem_peak, - &mut canceled_by, - job_dir, - db, - worker_name, - worker_dir, - base_internal_url, - &client.get_token().await, - occupancy_metrics, - ) - .await - } - JobKind::AppDependencies => handle_app_dependency_job( - &job, - &mut mem_peak, - &mut canceled_by, - job_dir, - db, - worker_name, - worker_dir, - base_internal_url, - &client.get_token().await, - occupancy_metrics, - ) - .await - .map(|()| serde_json::from_str("{}").unwrap()), + .map(|()| serde_json::from_str("{}").unwrap()), + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle app dependency job with agent worker".to_string(), + )); + } + }, JobKind::Identity => Ok(job .args .as_ref() @@ -2202,8 +2294,9 @@ async fn handle_queued_job( let r = handle_code_execution_job( job.as_ref(), preview_data, - db, + conn, client, + parent_runnable_path, job_dir, worker_dir, &mut mem_peak, @@ -2214,6 +2307,7 @@ async fn handle_queued_job( &mut new_args, occupancy_metrics, killpill_rx, + precomputed_agent_info, ) .await; occupancy_metrics.total_duration_of_running_jobs += @@ -2233,7 +2327,6 @@ async fn handle_queued_job( { return Ok(false); } - process_result( job, result.map(|x| Arc::new(x)), @@ -2242,10 +2335,10 @@ async fn handle_queued_job( mem_peak, canceled_by, cached_res_path, - client.get_token().await, + &client.token, column_order, new_args, - db, + conn, Some(started.elapsed().as_millis() as i64), ) .await @@ -2284,6 +2377,7 @@ pub struct ContentReqLangEnvs { pub language: Option, pub envs: Option>, pub codebase: Option, + pub schema: Option, } pub async fn get_hub_script_content_and_requirements( @@ -2302,15 +2396,16 @@ pub async fn get_hub_script_content_and_requirements( language: Some(script.language), envs: None, codebase: None, + schema: Some(script.schema.get().to_string()), }) } pub async fn get_script_content_by_hash( script_hash: &ScriptHash, _w_id: &str, - db: &DB, + conn: &Connection, ) -> error::Result { - let (data, metadata) = cache::script::fetch(db, *script_hash).await?; + let (data, metadata) = cache::script::fetch(conn, *script_hash).await?; Ok(ContentReqLangEnvs { content: data.code.clone(), lockfile: data.lock.clone(), @@ -2321,15 +2416,105 @@ pub async fn get_script_content_by_hash( Some(x) if x.ends_with(".tar") => Some(format!("{}.tar", script_hash)), Some(_) => Some(script_hash.to_string()), }, + schema: None, }) } +async fn try_validate_schema( + job: &MiniPulledJob, + conn: &Connection, + schema_validator: Option<&SchemaValidator>, + code: &str, + language: Option<&ScriptLang>, + schema: Option<&String>, +) -> Result<(), Error> { + if let Some(args) = job.args.as_ref() { + if let Some(sv) = schema_validator { + sv.validate(args)?; + } else { + let validators_cache = cache::anon!({ (u8, ScriptHash) => Arc> } in "schemavalidators" <= 1000); + + let sv_fut = async move { + if language.map(|l| should_validate_schema(code, l)).unwrap_or(false) { + if let Some(schema) = schema { + Ok(Some(SchemaValidator::from_schema(schema)?)) + } else { + if let Some(sig) = parse_sig_of_lang( + code, + language, + job.script_entrypoint_override.clone(), + )? { + Ok(Some(schema_validator_from_main_arg_sig(&sig))) + } else { + Err(anyhow!("Job was expected to validate the arguments schema, but no schema was provided and couldn't be inferred from the script for language `{language:?}`. Try removing schema validation for this job").into()) + } + } + } else { Ok(None) } + } + .map_ok(Arc::new); + + let sub_key: u8 = match job.kind { + JobKind::Script => 0, + JobKind::FlowScript => 1, + JobKind::AppScript => 2, + JobKind::Script_Hub => 3, + JobKind::Preview => 4, + JobKind::DeploymentCallback => 5, + JobKind::SingleScriptFlow => 6, + JobKind::Dependencies => 7, + JobKind::Flow => 8, + JobKind::FlowPreview => 9, + JobKind::Identity => 10, + JobKind::FlowDependencies => 11, + JobKind::AppDependencies => 12, + JobKind::Noop => 13, + JobKind::FlowNode => 14, + }; + + let sv = match job.runnable_id { + Some(hash) if job.kind != JobKind::Preview && job.kind != JobKind::FlowPreview => { + sv_fut.cached(validators_cache, (sub_key, hash)).await? + } + _ => sv_fut.await?, + }; + + if sv.is_some() && job.kind == JobKind::Preview { + append_logs( + &job.id, + &job.workspace_id, + "\n--- ARGS VALIDATION ---\nScript contains `schema_validation` annotation, running schema validation for the script arguments...\n", + conn, + ) + .await; + } + + sv.as_ref() + .as_ref() + .map(|sv| sv.validate(args)) + .transpose()?; + + if sv.is_some() { + append_logs( + &job.id, + &job.workspace_id, + "Script arguments were validated!\n\n", + conn, + ) + .await; + } + } + } + + Ok(()) +} + #[tracing::instrument(level = "trace", skip_all)] async fn handle_code_execution_job( - job: &QueuedJob, + job: &MiniPulledJob, preview: Option>, - db: &sqlx::Pool, - client: &AuthedClientBackgroundTask, + conn: &Connection, + client: &AuthedClient, + parent_runnable_path: Option, job_dir: &str, #[allow(unused_variables)] worker_dir: &str, mem_peak: &mut i32, @@ -2340,9 +2525,10 @@ async fn handle_code_execution_job( new_args: &mut Option>>, occupancy_metrics: &mut OccupancyMetrics, killpill_rx: &mut tokio::sync::broadcast::Receiver<()>, + precomputed_agent_info: Option, ) -> error::Result> { let script_hash = || { - job.script_hash + job.runnable_id .ok_or_else(|| Error::internal_err("expected script hash")) }; let (arc_data, arc_metadata, data, metadata): ( @@ -2351,80 +2537,126 @@ async fn handle_code_execution_job( ScriptData, ScriptMetadata, ); - let (ScriptData { code, lock }, ScriptMetadata { language, envs, codebase }) = match job - .job_kind - { + let ( + ScriptData { code, lock }, + ScriptMetadata { language, envs, codebase, schema_validator, schema }, + ) = match job.kind { JobKind::Preview => { - let codebase = match job.script_hash.map(|x| x.0) { + let codebase = match job.runnable_id.map(|x| x.0) { Some(PREVIEW_IS_CODEBASE_HASH) => Some(job.id.to_string()), Some(PREVIEW_IS_TAR_CODEBASE_HASH) => Some(format!("{}.tar", job.id)), _ => None, }; - arc_data = - preview.ok_or_else(|| Error::internal_err("expected preview".to_string()))?; - metadata = ScriptMetadata { language: job.language, codebase, envs: None }; - (arc_data.as_ref(), &metadata) + if codebase.is_none() && job.runnable_id.is_some() { + (arc_data, arc_metadata) = + cache::script::fetch(conn, job.runnable_id.unwrap()).await?; + (arc_data.as_ref(), arc_metadata.as_ref()) + } else { + arc_data = + preview.ok_or_else(|| Error::internal_err("expected preview".to_string()))?; + metadata = ScriptMetadata { + language: job.script_lang, + codebase, + envs: None, + schema: None, + schema_validator: None, + }; + (arc_data.as_ref(), &metadata) + } } JobKind::Script_Hub => { - let ContentReqLangEnvs { content, lockfile, language, envs, codebase } = - get_hub_script_content_and_requirements(job.script_path.as_ref(), Some(db)).await?; + let ContentReqLangEnvs { content, lockfile, language, envs, codebase, schema } = + get_hub_script_content_and_requirements(job.runnable_path.as_ref(), conn.as_sql()) + .await?; + data = ScriptData { code: content, lock: lockfile }; - metadata = ScriptMetadata { language, envs, codebase }; + metadata = ScriptMetadata { language, envs, codebase, schema, schema_validator: None }; (&data, &metadata) } JobKind::Script => { - (arc_data, arc_metadata) = cache::script::fetch(db, script_hash()?).await?; + (arc_data, arc_metadata) = cache::script::fetch(conn, script_hash()?).await?; (arc_data.as_ref(), arc_metadata.as_ref()) } JobKind::FlowScript => { - arc_data = cache::flow::fetch_script(db, FlowNodeId(script_hash()?.0)).await?; - metadata = ScriptMetadata { language: job.language, envs: None, codebase: None }; + arc_data = cache::flow::fetch_script(conn, FlowNodeId(script_hash()?.0)).await?; + metadata = ScriptMetadata { + language: job.script_lang, + envs: None, + codebase: None, + schema: None, + schema_validator: None, + }; (arc_data.as_ref(), &metadata) } JobKind::AppScript => { - arc_data = cache::app::fetch_script(db, AppScriptId(script_hash()?.0)).await?; - metadata = ScriptMetadata { language: job.language, envs: None, codebase: None }; + arc_data = cache::app::fetch_script(conn, AppScriptId(script_hash()?.0)).await?; + metadata = ScriptMetadata { + language: job.script_lang, + envs: None, + codebase: None, + schema: None, + schema_validator: None, + }; (arc_data.as_ref(), &metadata) } - JobKind::DeploymentCallback => { - let script_path = job - .script_path - .as_ref() - .ok_or_else(|| Error::internal_err("expected script path".to_string()))?; - if script_path.starts_with("hub/") { - let ContentReqLangEnvs { content, lockfile, language, envs, codebase } = - get_hub_script_content_and_requirements(Some(script_path), Some(db)).await?; - data = ScriptData { code: content, lock: lockfile }; - metadata = ScriptMetadata { language, envs, codebase }; - (&data, &metadata) - } else { - let hash = sqlx::query_scalar!( - "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND + JobKind::DeploymentCallback => match conn { + Connection::Sql(db) => { + let script_path = job + .runnable_path + .as_ref() + .ok_or_else(|| Error::internal_err("expected script path".to_string()))?; + if script_path.starts_with("hub/") { + let ContentReqLangEnvs { content, lockfile, language, envs, codebase, schema } = + get_hub_script_content_and_requirements(Some(script_path), conn.as_sql()) + .await?; + data = ScriptData { code: content, lock: lockfile }; + metadata = + ScriptMetadata { language, envs, codebase, schema, schema_validator: None }; + (&data, &metadata) + } else { + let hash = sqlx::query_scalar!( + "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false AND lock IS not NULL AND lock_error_logs IS NULL", - script_path, - &job.workspace_id - ) - .fetch_optional(db) - .await? - .ok_or_else(|| Error::internal_err("expected script hash".to_string()))?; + script_path, + &job.workspace_id + ) + .fetch_optional(db) + .await? + .ok_or_else(|| Error::internal_err("expected script hash".to_string()))?; - (arc_data, arc_metadata) = cache::script::fetch(db, ScriptHash(hash)).await?; - (arc_data.as_ref(), arc_metadata.as_ref()) + (arc_data, arc_metadata) = cache::script::fetch(conn, ScriptHash(hash)).await?; + (arc_data.as_ref(), arc_metadata.as_ref()) + } } - } + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle deployment callback with agent worker".to_string(), + )); + } + }, _ => unreachable!( "handle_code_execution_job should never be reachable with a non-code execution job" ), }; - let language = *language; + try_validate_schema( + job, + conn, + schema_validator.as_ref(), + code, + language.as_ref(), + schema.as_ref(), + ) + .await?; + + let language = language.clone(); if language == Some(ScriptLang::Postgresql) { return do_postgresql( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, @@ -2443,7 +2675,7 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, @@ -2473,7 +2705,7 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, @@ -2496,7 +2728,7 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, @@ -2527,11 +2759,12 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, occupancy_metrics, + job_dir, ) .await; } @@ -2557,7 +2790,31 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, + mem_peak, + canceled_by, + worker_name, + column_order, + occupancy_metrics, + ) + .await; + } + } else if language == Some(ScriptLang::DuckDb) { + #[allow(unreachable_code)] + #[cfg(not(feature = "duckdb"))] + { + return Err(Error::internal_err( + "Duck DB requires the duckdb feature to be enabled".to_string(), + )); + } + + #[cfg(feature = "duckdb")] + { + return do_duckdb( + job, + &client, + &code, + conn, mem_peak, canceled_by, worker_name, @@ -2571,7 +2828,7 @@ async fn handle_code_execution_job( job, &client, &code, - db, + conn, mem_peak, canceled_by, worker_name, @@ -2583,11 +2840,12 @@ async fn handle_code_execution_job( &job.id, &job.workspace_id, "\n--- FETCH TS EXECUTION ---\n", - db, + conn, ) .await; - let reserved_variables = get_reserved_variables(job, &client.get_token().await, db).await?; + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; let env_code = format!( "const process = {{ env: {{}} }};\nconst BASE_URL = '{base_internal_url}';\nconst BASE_INTERNAL_URL = '{base_internal_url}';\nprocess.env['BASE_URL'] = BASE_URL;process.env['BASE_INTERNAL_URL'] = BASE_INTERNAL_URL;\n{}", @@ -2602,7 +2860,7 @@ async fn handle_code_execution_job( &client, env_code, code.clone(), - db, + conn, mem_peak, canceled_by, worker_name, @@ -2613,7 +2871,7 @@ async fn handle_code_execution_job( } let lang_str = job - .language + .script_lang .as_ref() .map(|x| format!("{x:?}")) .unwrap_or_else(|| "NO_LANG".to_string()); @@ -2625,8 +2883,8 @@ async fn handle_code_execution_job( job.id ); - let shared_mount = if job.same_worker && job.language != Some(ScriptLang::Deno) { - let folder = if job.language == Some(ScriptLang::Go) { + let shared_mount = if job.same_worker && job.script_lang != Some(ScriptLang::Deno) { + let folder = if job.script_lang == Some(ScriptLang::Go) { "/go" } else { "" @@ -2670,14 +2928,16 @@ mount {{ job, mem_peak, canceled_by, - db, + conn, client, + parent_runnable_path, &code, &shared_mount, base_internal_url, envs, new_args, occupancy_metrics, + precomputed_agent_info, ) .await } @@ -2687,8 +2947,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, job_dir, &code, base_internal_url, @@ -2706,8 +2967,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, job_dir, &code, base_internal_url, @@ -2716,6 +2978,7 @@ mount {{ &shared_mount, new_args, occupancy_metrics, + precomputed_agent_info, ) .await } @@ -2724,8 +2987,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, &code, job_dir, lock.as_ref(), @@ -2742,8 +3006,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, &code, job_dir, &shared_mount, @@ -2760,8 +3025,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, &code, job_dir, &shared_mount, @@ -2784,8 +3050,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, job_dir, &code, base_internal_url, @@ -2807,8 +3074,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, &code, job_dir, lock.as_ref(), @@ -2835,8 +3103,9 @@ mount {{ job, mem_peak, canceled_by, - db, + conn, client, + parent_runnable_path, &code, &shared_mount, base_internal_url, @@ -2850,8 +3119,9 @@ mount {{ mem_peak, canceled_by, job, - db, + conn, client, + parent_runnable_path, &code, job_dir, lock.as_ref(), @@ -2863,6 +3133,57 @@ mount {{ ) .await } + Some(ScriptLang::Nu) => { + #[cfg(not(feature = "nu"))] + return Err( + anyhow::anyhow!("Nu is not available because the feature is not enabled").into(), + ); + + #[cfg(feature = "nu")] + handle_nu_job(JobHandlerInputNu { + mem_peak, + canceled_by, + job, + conn, + client, + parent_runnable_path, + inner_content: &code, + job_dir, + requirements_o: lock.as_ref(), + shared_mount: &shared_mount, + base_internal_url, + worker_name, + envs, + occupancy_metrics, + }) + .await + } + Some(ScriptLang::Java) => { + #[cfg(not(feature = "java"))] + return Err(anyhow::anyhow!( + "Java is not available because the feature is not enabled" + ) + .into()); + + #[cfg(feature = "java")] + handle_java_job(JobHandlerInputJava { + mem_peak, + canceled_by, + job, + conn, + client, + parent_runnable_path, + inner_content: &code, + job_dir, + requirements_o: lock.as_ref(), + shared_mount: &shared_mount, + base_internal_url, + worker_name, + envs, + occupancy_metrics, + }) + .await + } _ => panic!("unreachable, language is not supported: {language:#?}"), }; tracing::info!( @@ -2876,3 +3197,68 @@ mount {{ result } + +fn parse_sig_of_lang( + code: &str, + language: Option<&ScriptLang>, + main_override: Option, +) -> Result> { + Ok(if let Some(lang) = language { + match lang { + ScriptLang::Nativets | ScriptLang::Deno | ScriptLang::Bun | ScriptLang::Bunnative => { + Some(windmill_parser_ts::parse_deno_signature( + code, + true, + false, + main_override, + )?) + } + #[cfg(feature = "python")] + ScriptLang::Python3 => Some(windmill_parser_py::parse_python_signature( + code, + main_override, + false, + )?), + #[cfg(not(feature = "python"))] + ScriptLang::Python3 => None, + ScriptLang::Go => Some(windmill_parser_go::parse_go_sig(code)?), + ScriptLang::Bash => Some(windmill_parser_bash::parse_bash_sig(code)?), + ScriptLang::Powershell => Some(windmill_parser_bash::parse_powershell_sig(code)?), + ScriptLang::Postgresql => Some(windmill_parser_sql::parse_pgsql_sig(code)?), + ScriptLang::Mysql => Some(windmill_parser_sql::parse_mysql_sig(code)?), + ScriptLang::Bigquery => Some(windmill_parser_sql::parse_bigquery_sig(code)?), + ScriptLang::Snowflake => Some(windmill_parser_sql::parse_snowflake_sig(code)?), + ScriptLang::Graphql => None, + ScriptLang::Mssql => Some(windmill_parser_sql::parse_mssql_sig(code)?), + ScriptLang::DuckDb => Some(windmill_parser_sql::parse_duckdb_sig(code)?), + ScriptLang::OracleDB => Some(windmill_parser_sql::parse_oracledb_sig(code)?), + #[cfg(feature = "php")] + ScriptLang::Php => Some(windmill_parser_php::parse_php_signature( + code, + main_override, + )?), + #[cfg(not(feature = "php"))] + ScriptLang::Php => None, + #[cfg(feature = "rust")] + ScriptLang::Rust => Some(windmill_parser_rust::parse_rust_signature(code)?), + #[cfg(not(feature = "rust"))] + ScriptLang::Rust => None, + ScriptLang::Ansible => Some(windmill_parser_yaml::parse_ansible_sig(code)?), + #[cfg(feature = "csharp")] + ScriptLang::CSharp => Some(windmill_parser_csharp::parse_csharp_signature(code)?), + #[cfg(not(feature = "csharp"))] + ScriptLang::CSharp => None, + #[cfg(feature = "nu")] + ScriptLang::Nu => Some(windmill_parser_nu::parse_nu_signature(code)?), + #[cfg(not(feature = "nu"))] + ScriptLang::Nu => None, + #[cfg(feature = "java")] + ScriptLang::Java => Some(windmill_parser_java::parse_java_signature(code)?), + #[cfg(not(feature = "java"))] + ScriptLang::Java => None, + // for related places search: ADD_NEW_LANG + } + } else { + None + }) +} diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 50f6ee8607..95ffc28062 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -11,13 +11,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; -#[cfg(feature = "benchmark")] -use crate::bench::BenchmarkIter; use crate::common::{cached_result_path, save_in_cache}; use crate::js_eval::{eval_timeout, IdContext}; +use crate::worker_utils::get_tag_and_concurrency; use crate::{ - AuthedClient, PreviousResult, SameWorkerPayload, SameWorkerSender, SendResult, JOB_TOKEN, - KEEP_JOB_DIR, + JobCompletedSender, PreviousResult, SameWorkerSender, SendResult, UpdateFlow, KEEP_JOB_DIR, }; use anyhow::Context; use futures::TryFutureExt; @@ -27,25 +25,29 @@ use serde_json::value::RawValue; use serde_json::{json, Value}; use sqlx::types::Json; use sqlx::{FromRow, Postgres, Transaction}; -use tokio::sync::mpsc::Sender; use tracing::instrument; use uuid::Uuid; -use windmill_common::add_time; use windmill_common::auth::JobPerms; +#[cfg(feature = "benchmark")] +use windmill_common::bench::BenchmarkIter; use windmill_common::cache::{self, RawData}; +use windmill_common::client::AuthedClient; use windmill_common::db::Authed; use windmill_common::flow_status::{ ApprovalConditions, FlowStatusModuleWParent, Iterator as FlowIterator, JobResult, }; -use windmill_common::flows::{add_virtual_items_if_necessary, Branch, FlowNodeId}; +use windmill_common::flows::{add_virtual_items_if_necessary, Branch, FlowNodeId, StopAfterIf}; use windmill_common::jobs::{ - script_hash_to_tag_and_limits, script_path_to_payload, JobKind, JobPayload, OnBehalfOf, - QueuedJob, RawCode, ENTRYPOINT_OVERRIDE, + script_path_to_payload, JobKind, JobPayload, OnBehalfOf, RawCode, ENTRYPOINT_OVERRIDE, }; use windmill_common::scripts::ScriptHash; use windmill_common::users::username_to_permissioned_as; use windmill_common::utils::WarnAfterExt; use windmill_common::worker::to_raw_value; +use windmill_common::{ + add_time, get_latest_flow_version_info_for_path, get_script_info_for_hash, FlowVersionInfo, + ScriptHashInfo, +}; use windmill_common::{ error::{self, to_anyhow, Error}, flow_status::{ @@ -54,15 +56,17 @@ use windmill_common::{ }, flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform, Retry, Suspend}, }; +use windmill_queue::flow_status::Step; use windmill_queue::schedule::get_schedule_opt; use windmill_queue::{ - add_completed_job, add_completed_job_error, append_logs, handle_maybe_scheduled_job, - CanceledBy, PushArgs, PushIsolationLevel, WrappedError, + add_completed_job, add_completed_job_error, append_logs, get_mini_pulled_job, + handle_maybe_scheduled_job, insert_concurrency_key, interpolate_args, CanceledBy, + MiniPulledJob, PushArgs, PushIsolationLevel, SameWorkerPayload, WrappedError, }; type DB = sqlx::Pool; -use windmill_audit::audit_ee::{audit_log, AuditAuthor}; +use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; use windmill_queue::{canceled_job_to_result, push}; @@ -76,13 +80,13 @@ pub async fn update_flow_status_after_job_completion( success: bool, result: Arc>, unrecoverable: bool, - same_worker_tx: SameWorkerSender, + same_worker_tx: &SameWorkerSender, worker_dir: &str, stop_early_override: Option, worker_name: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, -) -> error::Result>> { +) -> error::Result>> { // this is manual tailrecursion because async_recursion blows up the stack potentially_crash_for_testing(); @@ -106,7 +110,7 @@ pub async fn update_flow_status_after_job_completion( rec.success, rec.result, unrecoverable, - same_worker_tx.clone(), + same_worker_tx, worker_dir, rec.stop_early_override, rec.skip_error_handler, @@ -131,7 +135,7 @@ pub async fn update_flow_status_after_job_completion( error: json!(e.to_string()), }))), true, - same_worker_tx.clone(), + same_worker_tx, worker_dir, rec.stop_early_override, rec.skip_error_handler, @@ -144,6 +148,7 @@ pub async fn update_flow_status_after_job_completion( } }; unrecoverable = false; + match nrec { UpdateFlowStatusAfterJobCompletion::Done(job) => { add_time!(bench, "update flow status internal END"); @@ -160,15 +165,20 @@ pub async fn update_flow_status_after_job_completion( add_time!(bench, "update flow status internal END"); return Ok(None); } + UpdateFlowStatusAfterJobCompletion::PreprocessingStep => { + add_time!(bench, "update flow status preprocessing step END"); + return Ok(None); + } } } } pub enum UpdateFlowStatusAfterJobCompletion { Rec(RecUpdateFlowStatusAfterJobCompletion), - Done(Arc), + Done(Arc), NotDone, NonLastParallelBranch, + PreprocessingStep, } pub struct RecUpdateFlowStatusAfterJobCompletion { flow: uuid::Uuid, @@ -184,6 +194,30 @@ struct RecoveryObject { recover: Option, } +fn get_stop_after_if_data( + stop_early: bool, + stop_after_if: Option<&StopAfterIf>, +) -> (bool, Option) { + if let Some(stop_after_if) = stop_after_if { + let err_msg = stop_early + .then(|| { + let err_msg = stop_after_if.error_message.as_ref().and_then(|message| { + let err_start_msg = "Flow early stop"; + let s = if message.is_empty() { + format!("{}: {}", err_start_msg, &stop_after_if.expr) + } else { + format!("{}: {}", err_start_msg, message) + }; + Some(s) + }); + err_msg + }) + .flatten(); + return (stop_after_if.skip_if_stopped, err_msg); + } + return (false, None); +} + // #[instrument(level = "trace", skip_all)] pub async fn update_flow_status_after_job_completion_internal( db: &DB, @@ -194,12 +228,12 @@ pub async fn update_flow_status_after_job_completion_internal( mut success: bool, result: Arc>, unrecoverable: bool, - same_worker_tx: SameWorkerSender, + same_worker_tx: &SameWorkerSender, worker_dir: &str, stop_early_override: Option, skip_error_handler: bool, worker_name: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, ) -> error::Result { add_time!(bench, "update flow status internal START"); @@ -208,6 +242,7 @@ pub async fn update_flow_status_after_job_completion_internal( flow_job, flow_data, stop_early, + stop_early_err_msg, skip_if_stop_early, nresult, is_failure_step, @@ -216,34 +251,34 @@ pub async fn update_flow_status_after_job_completion_internal( // tracing::debug!("UPDATE FLOW STATUS: {flow:?} {success} {result:?} {w_id} {depth}"); let (job_kind, script_hash, old_status, raw_flow) = sqlx::query!( - "SELECT - job_kind AS \"job_kind!: JobKind\", - script_hash AS \"script_hash: ScriptHash\", - flow_status AS \"flow_status!: Json>\", - raw_flow AS \"raw_flow: Json>\" - FROM v2_as_queue WHERE id = $1 AND workspace_id = $2 LIMIT 1", - flow, - w_id - ) - .fetch_one(db) - .await - .map_err(|e| { - Error::internal_err(format!( - "fetching flow status {flow} while reporting {success} {result:?}: {e:#}" - )) - }) - .and_then(|record| { - Ok(( - record.job_kind, - record.script_hash, - serde_json::from_str::(record.flow_status.0.get()).map_err(|e| { - Error::internal_err(format!( - "requiring current module to be parsable as FlowStatus: {e:?}" - )) - })?, - record.raw_flow, - )) - })?; + "SELECT + kind AS \"job_kind!: JobKind\", + runnable_id AS \"script_hash: ScriptHash\", + flow_status AS \"flow_status!: Json>\", + raw_flow AS \"raw_flow: Json>\" + FROM v2_job INNER JOIN v2_job_status ON v2_job.id = v2_job_status.id WHERE v2_job.id = $1 AND v2_job.workspace_id = $2 LIMIT 1", + flow, + w_id + ) + .fetch_one(db) + .await + .map_err(|e| { + Error::internal_err(format!( + "fetching flow status {flow} while reporting {success} {result:?}: {e:#}" + )) + }) + .and_then(|record| { + Ok(( + record.job_kind, + record.script_hash, + serde_json::from_str::(record.flow_status.0.get()).map_err(|e| { + Error::internal_err(format!( + "requiring current module to be parsable as FlowStatus: {e:?}" + )) + })?, + record.raw_flow, + )) + })?; let flow_data = cache::job::fetch_flow(db, job_kind, script_hash) .or_else(|_| cache::job::fetch_preview_flow(db, &flow, raw_flow)) @@ -299,7 +334,7 @@ pub async fn update_flow_status_after_job_completion_internal( let is_failure_step = old_status.step >= old_status.modules.len() as i32 && old_status.modules.len() > 0; - let (mut stop_early, mut skip_if_stop_early, continue_on_error) = + let (mut stop_early, mut stop_early_err_msg, mut skip_if_stop_early, continue_on_error) = if let Some(se) = stop_early_override { //do not stop early if module is a flow step let step = match module_step { @@ -315,7 +350,6 @@ pub async fn update_flow_status_after_job_completion_internal( } current_module - .as_ref() .map(|module| { serde_json::from_str::(module.value.get()) .map(|v| v.r#type == "flow") @@ -327,19 +361,19 @@ pub async fn update_flow_status_after_job_completion_internal( }; if is_flow { - (false, false, false) + (false, None, false, false) } else { - (true, se, false) + (true, None, se, false) } } else if is_failure_step || matches!(module_step, Step::PreprocessorStep) { - (false, false, false) - } else if let Some(current_module) = current_module.as_ref() { + (false, None, false, false) + } else if let Some(current_module) = current_module { let stop_early = success && !is_branch_all - && if let Some(ref expr) = current_module + && if let Some(expr) = current_module .stop_after_if .as_ref() - .map(|x| x.expr.clone()) + .map(|x| x.expr.as_str()) { let all_iters = match &module_status { @@ -352,9 +386,9 @@ pub async fn update_flow_status_after_job_completion_internal( }; let args = sqlx::query_scalar!( "SELECT - args AS \"args: Json>>\" - FROM v2_job - WHERE id = $1", + args AS \"args: Json>>\" + FROM v2_job + WHERE id = $1", flow ) .fetch_one(db) @@ -376,71 +410,30 @@ pub async fn update_flow_status_after_job_completion_internal( } else { false }; + let (skip_if_stopped, stop_early_err_msg) = + get_stop_after_if_data(stop_early, current_module.stop_after_if.as_ref()); ( stop_early, - current_module - .stop_after_if - .as_ref() - .map(|x| x.skip_if_stopped) - .unwrap_or(false), + stop_early_err_msg.filter(|_| !(is_loop || is_branch_all)), + skip_if_stopped, current_module.continue_on_error.unwrap_or(false), ) } else { - (false, false, false) + (false, None, false, false) }; - let skip_branch_failure = match module_status { + let skip_seq_branch_failure = match module_status { FlowStatusModule::InProgress { branchall: Some(BranchAllStatus { branch, .. }), - parallel, + parallel: false, .. - } => compute_skip_branchall_failure( - job_id_for_status, - *branch, - *parallel, - db, - current_module, - ) - .await? - .unwrap_or(false), + } => { + compute_skip_branchall_failure(branch.to_owned(), false, current_module, &None) + .await? + } _ => false, }; - if matches!(module_step, Step::PreprocessorStep) { - sqlx::query!( - "WITH job_result AS ( - SELECT result - FROM v2_job_completed - WHERE id = $1 - ) - UPDATE v2_job - SET args = COALESCE( - CASE - WHEN job_result.result IS NULL THEN NULL - WHEN jsonb_typeof(job_result.result) = 'object' - THEN job_result.result - WHEN jsonb_typeof(job_result.result) = 'null' - THEN NULL - ELSE jsonb_build_object('value', job_result.result) - END, - '{}'::jsonb - ), - preprocessed = TRUE - FROM job_result - WHERE v2_job.id = $2; - ", - job_id_for_status, - flow - ) - .execute(db) - .await - .map_err(|e| { - Error::internal_err(format!( - "error while updating args in preprocessing step: {e:#}" - )) - })?; - } - let mut tx = db.begin().await?; add_time!(bench, "process module status START"); @@ -463,46 +456,46 @@ pub async fn update_flow_status_after_job_completion_internal( }; let nindex = if let Some(position) = position { - sqlx::query_scalar!( - "UPDATE v2_job_status SET - flow_status = JSONB_SET( - JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), - ARRAY['modules', $1::TEXT, 'iterator', 'index'], - ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb - ) - WHERE id = $2 - RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", - old_status.step, - flow, - position as i32, - json!(success) - ) - } else { - sqlx::query_scalar!( - "UPDATE v2_job_status SET - flow_status = JSONB_SET( - flow_status, - ARRAY['modules', $1::TEXT, 'iterator', 'index'], - ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb - ) - WHERE id = $2 - RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", - old_status.step, - flow - ) - } - .fetch_one(&mut *tx) - .await.map_err(|e| { - Error::internal_err(format!( - "error while fetching iterator index: {e:#}" - )) - })? - .ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress")))?; + sqlx::query_scalar!( + "UPDATE v2_job_status SET + flow_status = JSONB_SET( + JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), + ARRAY['modules', $1::TEXT, 'iterator', 'index'], + ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb + ) + WHERE id = $2 + RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", + old_status.step, + flow, + position as i32, + json!(success) + ) + } else { + sqlx::query_scalar!( + "UPDATE v2_job_status SET + flow_status = JSONB_SET( + flow_status, + ARRAY['modules', $1::TEXT, 'iterator', 'index'], + ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb + ) + WHERE id = $2 + RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int", + old_status.step, + flow + ) + } + .fetch_one(&mut *tx) + .await.map_err(|e| { + Error::internal_err(format!( + "error while fetching iterator index: {e:#}" + )) + })? + .ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress")))?; tracing::info!( - "parallel iteration {job_id_for_status} of flow {flow} update nindex: {nindex} len: {len}", - nindex = nindex, - len = itered.len() - ); + "parallel iteration {job_id_for_status} of flow {flow} update nindex: {nindex} len: {len}", + nindex = nindex, + len = itered.len() + ); (nindex, itered.len() as i32) } (_, Some(BranchAllStatus { len, .. })) => { @@ -513,42 +506,42 @@ pub async fn update_flow_status_after_job_completion_internal( }; let nindex = if let Some(position) = position { - sqlx::query_scalar!( - "UPDATE v2_job_status SET - flow_status = JSONB_SET( - JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), - ARRAY['modules', $1::TEXT, 'branchall', 'branch'], - ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb - ) - WHERE id = $2 - RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", - old_status.step, - flow, - position as i32, - json!(success) - ) - } else { - sqlx::query_scalar!( - "UPDATE v2_job_status SET - flow_status = JSONB_SET( - flow_status, - ARRAY['modules', $1::TEXT, 'branchall', 'branch'], - ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb - ) - WHERE id = $2 - RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", - old_status.step, - flow - ) - } - .fetch_one(&mut *tx) - .await - .map_err(|e| { - Error::internal_err(format!( - "error while fetching branchall index: {e:#}" - )) - })? - .ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress")))?; + sqlx::query_scalar!( + "UPDATE v2_job_status SET + flow_status = JSONB_SET( + JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), + ARRAY['modules', $1::TEXT, 'branchall', 'branch'], + ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb + ) + WHERE id = $2 + RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", + old_status.step, + flow, + position as i32, + json!(success) + ) + } else { + sqlx::query_scalar!( + "UPDATE v2_job_status SET + flow_status = JSONB_SET( + flow_status, + ARRAY['modules', $1::TEXT, 'branchall', 'branch'], + ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb + ) + WHERE id = $2 + RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int", + old_status.step, + flow + ) + } + .fetch_one(&mut *tx) + .await + .map_err(|e| { + Error::internal_err(format!( + "error while fetching branchall index: {e:#}" + )) + })? + .ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress")))?; (nindex, *len as i32) } _ => Err(Error::internal_err(format!( @@ -571,50 +564,50 @@ pub async fn update_flow_status_after_job_completion_internal( } let new_status = if skip_loop_failures - || sqlx::query_scalar!( - "SELECT success AS \"success!\" FROM v2_as_completed_job WHERE id = ANY($1)", - jobs.as_slice() - ) - .fetch_all(&mut *tx) - .await - .map_err(|e| { - Error::internal_err(format!( - "error while fetching sucess from completed_jobs: {e:#}" - )) - })? - .into_iter() - .all(|x| x) - { - success = true; - FlowStatusModule::Success { - id: module_status.id(), - job: job_id_for_status.clone(), - flow_jobs: Some(jobs.clone()), - flow_jobs_success: flow_jobs_success.clone(), - branch_chosen: None, - approvers: vec![], - failed_retries: vec![], - skipped: false, - } - } else { - success = false; - FlowStatusModule::Failure { - id: module_status.id(), - job: job_id_for_status.clone(), - flow_jobs: Some(jobs.clone()), - flow_jobs_success: flow_jobs_success.clone(), - branch_chosen: None, - failed_retries: vec![], - } - }; + || sqlx::query_scalar!( + "SELECT status = 'success' OR status = 'skipped' AS \"success!\" FROM v2_job_completed WHERE id = ANY($1)", + jobs.as_slice() + ) + .fetch_all(&mut *tx) + .await + .map_err(|e| { + Error::internal_err(format!( + "error while fetching sucess from completed_jobs: {e:#}" + )) + })? + .into_iter() + .all(|x| x) + { + success = true; + FlowStatusModule::Success { + id: module_status.id(), + job: job_id_for_status.clone(), + flow_jobs: Some(jobs.clone()), + flow_jobs_success: flow_jobs_success.clone(), + branch_chosen: None, + approvers: vec![], + failed_retries: vec![], + skipped: false, + } + } else { + success = false; + FlowStatusModule::Failure { + id: module_status.id(), + job: job_id_for_status.clone(), + flow_jobs: Some(jobs.clone()), + flow_jobs_success: flow_jobs_success.clone(), + branch_chosen: None, + failed_retries: vec![], + } + }; let r = sqlx::query_scalar!( - "DELETE FROM parallel_monitor_lock WHERE parent_flow_id = $1 RETURNING last_ping", - flow, - ).fetch_optional(db).await.map_err(|e| { - Error::internal_err(format!( - "error while deleting parallel_monitor_lock: {e:#}" - )) - })?; + "DELETE FROM parallel_monitor_lock WHERE parent_flow_id = $1 RETURNING last_ping", + flow, + ).fetch_optional(db).await.map_err(|e| { + Error::internal_err(format!( + "error while deleting parallel_monitor_lock: {e:#}" + )) + })?; if r.is_some() { tracing::info!( @@ -633,10 +626,11 @@ pub async fn update_flow_status_after_job_completion_internal( if parallelism.is_some() { sqlx::query!( "UPDATE v2_job_queue q SET suspend = 0 - FROM v2_job j, v2_job_status f - WHERE parent_job = $1 - AND f.id = j.id AND q.id = j.id - AND suspend = $2 AND (f.flow_status->'step')::int = 0", + FROM v2_job j, v2_job_status f + WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2 + AND f.id = j.id AND q.id = j.id + AND (f.flow_status->'step')::int = 0", + w_id, flow, nindex ) @@ -650,12 +644,12 @@ pub async fn update_flow_status_after_job_completion_internal( } let r = sqlx::query_scalar!( - "DELETE FROM parallel_monitor_lock WHERE parent_flow_id = $1 and job_id = $2 RETURNING last_ping", - flow, - job_id_for_status - ).fetch_optional(db).await.map_err(|e| { - Error::internal_err(format!("error while removing parallel_monitor_lock: {e:#}")) - })?; + "DELETE FROM parallel_monitor_lock WHERE parent_flow_id = $1 and job_id = $2 RETURNING last_ping", + flow, + job_id_for_status + ).fetch_optional(db).await.map_err(|e| { + Error::internal_err(format!("error while removing parallel_monitor_lock: {e:#}")) + })?; if r.is_some() { tracing::info!( "parallel flow has removed lock on its parent, last ping was {:?}", @@ -696,7 +690,7 @@ pub async fn update_flow_status_after_job_completion_internal( flow_jobs_success, flow_jobs, .. - } if branch.to_owned() < len - 1 && (success || skip_branch_failure) => { + } if branch.to_owned() < len - 1 && (success || skip_seq_branch_failure) => { if let Some(jobs) = flow_jobs { set_success_in_flow_job_success( flow_jobs_success, @@ -736,7 +730,9 @@ pub async fn update_flow_status_after_job_completion_internal( } } } - if success || (flow_jobs.is_some() && (skip_loop_failures || skip_branch_failure)) { + if success + || (flow_jobs.is_some() && (skip_loop_failures || skip_seq_branch_failure)) + { let is_skipped = if current_module.as_ref().is_some_and(|m| m.skip_if.is_some()) { sqlx::query_scalar!( @@ -793,11 +789,22 @@ pub async fn update_flow_status_after_job_completion_internal( } }; + let skip_parallel_branchall_failure = match (module_status, new_status.as_ref()) { + ( + FlowStatusModule::InProgress { branchall: Some(_), parallel: true, .. }, + Some(FlowStatusModule::Success { flow_jobs_success, .. }), + ) => compute_skip_branchall_failure(0, true, current_module, flow_jobs_success).await?, + ( + FlowStatusModule::InProgress { branchall: Some(_), parallel: true, .. }, + Some(FlowStatusModule::Failure { flow_jobs_success, .. }), + ) => compute_skip_branchall_failure(0, true, current_module, flow_jobs_success).await?, + _ => false, + }; let step_counter = if inc_step_counter { sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1) - WHERE id = $2", + SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1) + WHERE id = $2", json!(old_status.step + 1), flow ) @@ -811,6 +818,13 @@ pub async fn update_flow_status_after_job_completion_internal( old_status.step }; + // tracing::error!( + // "step_counter: {:?} {} {inc_step_counter} {flow}", + // step_counter, + // old_status.step, + // ); + // panic!("stop"); + /* is_last_step is true when the step_counter (the next step index) is an invalid index */ let is_last_step = usize::try_from(step_counter) .map(|i| !(..old_status.modules.len()).contains(&i)) @@ -819,20 +833,20 @@ pub async fn update_flow_status_after_job_completion_internal( if let Some(new_status) = new_status.as_ref() { if is_failure_step { let parent_module = sqlx::query_scalar!( - "SELECT flow_status->'failure_module'->>'parent_module' FROM v2_job_status WHERE id = $1", - flow - ) - .fetch_one(&mut *tx) - .await.map_err(|e| { - Error::internal_err(format!( - "error while fetching failure module: {e:#}" - )) - })?; + "SELECT flow_status->'failure_module'->>'parent_module' FROM v2_job_status WHERE id = $1", + flow + ) + .fetch_one(&mut *tx) + .await.map_err(|e| { + Error::internal_err(format!( + "error while fetching failure module: {e:#}" + )) + })?; sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1) - WHERE id = $2", + SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1) + WHERE id = $2", json!(FlowStatusModuleWParent { parent_module, module_status: new_status.clone() @@ -849,8 +863,8 @@ pub async fn update_flow_status_after_job_completion_internal( } else if matches!(module_step, Step::PreprocessorStep) { sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1) - WHERE id = $2", + SET flow_status = JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1) + WHERE id = $2", json!(new_status), flow ) @@ -864,8 +878,8 @@ pub async fn update_flow_status_after_job_completion_internal( } else { sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2) - WHERE id = $3", + SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2) + WHERE id = $3", old_status.step.to_string(), json!(new_status), flow @@ -878,40 +892,44 @@ pub async fn update_flow_status_after_job_completion_internal( if let Some(job_result) = new_status.job_result() { sqlx::query!( - "UPDATE v2_job_status - SET flow_leaf_jobs = JSONB_SET(coalesce(flow_leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2) - WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $3), $3) = id", - new_status.id(), - json!(job_result), - flow - ) - .execute(&mut *tx) - .await.map_err(|e| { - Error::internal_err(format!( - "error while setting leaf jobs: {e:#}" - )) - })?; + "UPDATE v2_job_status + SET flow_leaf_jobs = JSONB_SET(coalesce(flow_leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2) + WHERE COALESCE((SELECT flow_innermost_root_job FROM v2_job WHERE id = $3), $3) = id", + new_status.id(), + json!(job_result), + flow + ) + .execute(&mut *tx) + .await.map_err(|e| { + Error::internal_err(format!( + "error while setting leaf jobs: {e:#}" + )) + })?; } } } - let nresult = match &new_status { - Some(FlowStatusModule::Success { flow_jobs: Some(jobs), .. }) - | Some(FlowStatusModule::Failure { flow_jobs: Some(jobs), .. }) => { - Arc::new(retrieve_flow_jobs_results(db, w_id, jobs).await?) + let mut nresult = if let Some(stop_early_err_msg) = stop_early_err_msg.as_ref() { + Arc::new(to_raw_value(stop_early_err_msg)) + } else { + match &new_status { + Some(FlowStatusModule::Success { flow_jobs: Some(jobs), .. }) + | Some(FlowStatusModule::Failure { flow_jobs: Some(jobs), .. }) => { + Arc::new(retrieve_flow_jobs_results(db, w_id, jobs).await?) + } + _ => result.clone(), } - _ => result.clone(), }; match &new_status { Some(FlowStatusModule::Success { .. }) if is_loop || is_branch_all => { - if let Some(ref expr) = current_module + if let Some(stop_after_all_iters_if) = current_module .as_ref() - .and_then(|m| m.stop_after_all_iters_if.as_ref().map(|x| x.expr.clone())) + .and_then(|m| m.stop_after_all_iters_if.as_ref()) { let args = sqlx::query_scalar!( "SELECT args AS \"args: Json>>\" - FROM v2_job WHERE id = $1", + FROM v2_job WHERE id = $1", flow ) .fetch_one(db) @@ -921,7 +939,7 @@ pub async fn update_flow_status_after_job_completion_internal( })?; let should_stop = compute_bool_from_expr( - &expr, + &stop_after_all_iters_if.expr, Marc::new(args.unwrap_or_default().0), nresult.clone(), None, @@ -933,15 +951,14 @@ pub async fn update_flow_status_after_job_completion_internal( .await?; if should_stop { - stop_early = should_stop; - skip_if_stop_early = current_module - .as_ref() - .and_then(|m| { - m.stop_after_all_iters_if - .as_ref() - .map(|x| x.skip_if_stopped) - }) - .unwrap_or(false); + stop_early = true; + let (skip_if_stopped, err_msg_internal) = + get_stop_after_if_data(should_stop, Some(stop_after_all_iters_if)); + skip_if_stop_early = skip_if_stopped; + if err_msg_internal.is_some() { + stop_early_err_msg = err_msg_internal; + nresult = Arc::new(to_raw_value(&stop_early_err_msg)); + } } } } @@ -953,8 +970,8 @@ pub async fn update_flow_status_after_job_completion_internal( { sqlx::query!( "UPDATE v2_job_status - SET flow_status = flow_status - 'retry' - WHERE id = $1", + SET flow_status = flow_status - 'retry' + WHERE id = $1", flow ) .execute(&mut *tx) @@ -962,29 +979,150 @@ pub async fn update_flow_status_after_job_completion_internal( .context("remove flow status retry")?; } - let flow_job = sqlx::query_as::<_, QueuedJob>( - "SELECT * FROM v2_as_queue WHERE id = $1 AND workspace_id = $2", - ) - .bind(flow) - .bind(w_id) - .fetch_optional(&mut *tx) - .await - .map_err(Into::::into)? - .ok_or_else(|| Error::internal_err(format!("requiring flow to be in the queue")))?; + let flow_job = get_mini_pulled_job(&mut *tx, &flow) + .await? + .ok_or_else(|| Error::internal_err(format!("requiring flow to be in the queue")))?; tx.commit().await?; + if matches!(module_step, Step::PreprocessorStep) { + let tag_and_concurrency_key = get_tag_and_concurrency(&flow, db).await; + let require_args = tag_and_concurrency_key.as_ref().is_some_and(|x| { + x.tag.as_ref().is_some_and(|t| t.contains("$args")) + || x.concurrency_key + .as_ref() + .is_some_and(|ck| ck.contains("$args")) + }); + let mut tag = tag_and_concurrency_key + .as_ref() + .map(|x| x.tag.clone()) + .flatten(); + let concurrency_key = tag_and_concurrency_key + .as_ref() + .map(|x| x.concurrency_key.clone()) + .flatten(); + let concurrent_limit = tag_and_concurrency_key + .as_ref() + .map(|x| x.concurrent_limit) + .flatten(); + let concurrency_time_window_s = tag_and_concurrency_key + .as_ref() + .map(|x| x.concurrency_time_window_s) + .flatten(); + if require_args { + let args = sqlx::query_scalar!( + "SELECT result as \"result: Json>>\" + FROM v2_job_completed + WHERE id = $1", + job_id_for_status + ) + .fetch_one(db) + .await + .map_err(|e| { + Error::internal_err(format!("error while fetching preprocessing args: {e:#}")) + })?; + let args_hm = args.unwrap_or_default().0; + let args = PushArgs::from(&args_hm); + if let Some(ck) = concurrency_key { + let mut tx = db.begin().await?; + insert_concurrency_key( + &flow_job.workspace_id, + &args, + &flow_job.runnable_path, + JobKind::Flow, + Some(ck), + &mut tx, + flow, + ) + .await?; + tx.commit().await?; + } + if let Some(t) = tag { + tag = Some(interpolate_args(t, &args, &flow_job.workspace_id)); + } + } else if let Some(ck) = concurrency_key { + let mut tx = db.begin().await?; + insert_concurrency_key( + &flow_job.workspace_id, + &PushArgs::from(&HashMap::new()), + &flow_job.runnable_path, + JobKind::Flow, + Some(ck), + &mut tx, + flow, + ) + .await?; + tx.commit().await?; + } + + // let tag = tag_and_concurrency_key.and_then(|tc| tc.tag.map(|t| interpolate_args(t.clone(), &args, &workspace_id))); + // let concurrency_key = tag_and_concurrency_key.and_then(|tc| tc.concurrency_key.map(|ck| interpolate_args(&ck, &args, &workspace_id))); + sqlx::query!( + "WITH job_result AS ( + SELECT result + FROM v2_job_completed + WHERE id = $1 + ), + updated_queue AS ( + UPDATE v2_job_queue + SET running = false, + tag = COALESCE($3, tag) + WHERE id = $2 + ) + UPDATE v2_job + SET + tag = COALESCE($3, tag), + concurrent_limit = COALESCE($4, concurrent_limit), + concurrency_time_window_s = COALESCE($5, concurrency_time_window_s), + args = COALESCE( + CASE + WHEN job_result.result IS NULL THEN NULL + WHEN jsonb_typeof(job_result.result) = 'object' + THEN job_result.result + WHEN jsonb_typeof(job_result.result) = 'null' + THEN NULL + ELSE jsonb_build_object('value', job_result.result) + END, + '{}'::jsonb + ), + preprocessed = TRUE + FROM job_result + WHERE v2_job.id = $2; + ", + job_id_for_status, + flow, + tag, + concurrent_limit, + concurrency_time_window_s, + ) + .execute(db) + .await + .map_err(|e| { + Error::internal_err(format!( + "error while updating args in preprocessing step: {e:#}" + )) + })?; + if success { + return Ok(UpdateFlowStatusAfterJobCompletion::PreprocessingStep); + } + } + let job_root = flow_job - .root_job + .flow_innermost_root_job .map(|x| x.to_string()) .unwrap_or_else(|| "none".to_string()); tracing::info!(id = %flow_job.id, root_id = %job_root, "update flow status"); let should_continue_flow = match success { _ if stop_early => false, - _ if flow_job.canceled => false, + _ if flow_job.is_canceled() => false, true => !is_last_step, false if unrecoverable => false, - false if skip_branch_failure || skip_loop_failures || continue_on_error => { + false + if skip_seq_branch_failure + || skip_parallel_branchall_failure + || skip_loop_failures + || continue_on_error => + { !is_last_step } false @@ -1028,6 +1166,7 @@ pub async fn update_flow_status_after_job_completion_internal( flow_job, flow_data, stop_early, + stop_early_err_msg, skip_if_stop_early, nresult, is_failure_step, @@ -1039,7 +1178,7 @@ pub async fn update_flow_status_after_job_completion_internal( let done = if !should_continue_flow { { - let logs = if flow_job.canceled { + let logs = if flow_job.is_canceled() { "Flow job canceled\n".to_string() } else if stop_early { format!("Flow job stopped early because of a stop early predicate returning true\n") @@ -1048,16 +1187,16 @@ pub async fn update_flow_status_after_job_completion_internal( } else { "Flow job completed with error\n".to_string() }; - append_logs(&flow_job.id, w_id, logs, db).await; + append_logs(&flow_job.id, w_id, logs, &db.into()).await; } #[cfg(feature = "enterprise")] if flow_job.parent_job.is_none() { // run the cleanup step only when the root job is complete if !_cleanup_module.flow_jobs_to_clean.is_empty() { tracing::debug!( - "Cleaning up jobs arguments, result and logs as they were marked as delete_after_use {:?}", - _cleanup_module.flow_jobs_to_clean - ); + "Cleaning up jobs arguments, result and logs as they were marked as delete_after_use {:?}", + _cleanup_module.flow_jobs_to_clean + ); sqlx::query!( "UPDATE v2_job SET args = '{}'::jsonb WHERE id = ANY($1)", &_cleanup_module.flow_jobs_to_clean, @@ -1078,7 +1217,7 @@ pub async fn update_flow_status_after_job_completion_internal( })?; } } - if flow_job.canceled { + if flow_job.is_canceled() { add_completed_job_error( db, &flow_job, @@ -1106,14 +1245,15 @@ pub async fn update_flow_status_after_job_completion_internal( } let success = success && (!is_failure_step || result_has_recover_true(nresult.clone())) - && !skip_error_handler; + && !skip_error_handler + && stop_early_err_msg.is_none(); add_time!(bench, "flow status update 1"); if success { add_completed_job( db, &flow_job, - success, + true, stop_early && skip_if_stop_early, Json(&nresult), None, @@ -1127,7 +1267,7 @@ pub async fn update_flow_status_after_job_completion_internal( add_completed_job( db, &flow_job, - success, + false, stop_early && skip_if_stop_early, Json( &serde_json::from_str::(nresult.get()).unwrap_or_else( @@ -1152,7 +1292,7 @@ pub async fn update_flow_status_after_job_completion_internal( db, client, Some(nresult.clone()), - same_worker_tx.clone(), + same_worker_tx, worker_dir, job_completed_tx, worker_name, @@ -1166,7 +1306,7 @@ pub async fn update_flow_status_after_job_completion_internal( &flow_job.id, w_id, format!("Unexpected error during flow chaining:\n{:#?}", e), - db, + &db.into(), ) .await; let _ = add_completed_job_error(db, &flow_job, 0, None, e, worker_name, true, None) @@ -1182,7 +1322,7 @@ pub async fn update_flow_status_after_job_completion_internal( let _ = tokio::fs::remove_dir_all(format!("{worker_dir}/{}", flow_job.id)).await; } - if flow_job.is_flow_step { + if flow_job.is_flow_step() { if let Some(parent_job) = flow_job.parent_job { tracing::info!(subflow_id = %flow_job.id, parent_id = %parent_job, "subflow is finished, updating parent flow status"); @@ -1226,12 +1366,12 @@ async fn set_success_in_flow_job_success<'c>( if let Some(position) = position { sqlx::query!( "UPDATE v2_job_status SET - flow_status = JSONB_SET( - flow_status, - ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], - $4 - ) - WHERE id = $2", + flow_status = JSONB_SET( + flow_status, + ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], + $4 + ) + WHERE id = $2", old_status.step as i32, flow, position as i32, @@ -1254,8 +1394,8 @@ async fn retrieve_flow_jobs_results( ) -> error::Result> { let results = sqlx::query!( "SELECT result, id - FROM v2_job_completed - WHERE id = ANY($1) AND workspace_id = $2", + FROM v2_job_completed + WHERE id = ANY($1) AND workspace_id = $2", job_uuids.as_slice(), w_id ) @@ -1273,47 +1413,48 @@ async fn retrieve_flow_jobs_results( .ok_or_else(|| Error::internal_err(format!("missing job result for {}", j))) }) .collect::, _>>()?; - tracing::debug!("Retrieved results for flow jobs {:?}", results); Ok(to_raw_value(&results)) } async fn compute_skip_branchall_failure<'c>( - job: &Uuid, branch: usize, parallel: bool, - db: &DB, flow_module: Option<&FlowModule>, -) -> Result, Error> { - let branch = if parallel { - sqlx::query_scalar!("SELECT runnable_path FROM v2_job WHERE id = $1", job) - .fetch_one(db) - .await - .map_err(|e| { - Error::internal_err(format!("error during retrieval of branchall index: {e:#}")) - })? - .map(|p| { - BRANCHALL_INDEX_RE - .captures(&p) - .map(|x| x.get(1).unwrap().as_str().parse::().ok()) - .flatten() - .ok_or(Error::internal_err(format!( - "could not parse branchall index from path: {p}" - ))) - }) - .ok_or_else(|| { - Error::internal_err(format!("no branchall script path found for job {job}")) - })?? - } else { - branch as i32 - }; - Ok(flow_module + successes: &Option>>, +) -> windmill_common::error::Result { + let branches = flow_module .and_then(|x| x.get_branches_skip_failures().ok()) - .and_then(|x| { + .map(|x| { x.branches - .get(branch as usize) - .map(|x| x.skip_failure.unwrap_or(false)) - })) + .iter() + .map(|b| b.skip_failure.unwrap_or(false)) + .collect::>() + }); + if parallel { + if let Some(successes) = successes { + for (i, success) in successes.iter().enumerate() { + if branches + .as_ref() + .and_then(|x| x.get(i)) + .unwrap_or(&false) + .to_owned() + { + continue; + } + if !(success.unwrap_or(false)) { + return Ok(false); + } + } + Ok(true) + } else { + Ok(false) + } + } else { + Ok(branches + .and_then(|x| x.get(branch as usize).map(|b| b.to_owned())) + .unwrap_or(false)) + } } // async fn retrieve_cleanup_module<'c>(flow_uuid: Uuid, db: &DB) -> Result { @@ -1386,102 +1527,6 @@ async fn compute_bool_from_expr( } } -pub async fn update_flow_status_in_progress( - db: &DB, - _w_id: &str, - flow: Uuid, - job_in_progress: Uuid, -) -> error::Result { - let step = get_step_of_flow_status(db, flow).await?; - match step { - Step::Step(step) => { - sqlx::query!( - "UPDATE v2_job_status SET - flow_status = jsonb_set( - jsonb_set(flow_status, ARRAY['modules', $3::INTEGER::TEXT, 'job'], to_jsonb($1::UUID::TEXT)), - ARRAY['modules', $3::INTEGER::TEXT, 'type'], - to_jsonb('InProgress'::text) - ) - WHERE id = $2", - job_in_progress, - flow, - step as i32 - ) - .execute(db) - .await?; - } - Step::PreprocessorStep => { - sqlx::query!( - "UPDATE v2_job_status SET - flow_status = jsonb_set( - jsonb_set(flow_status, ARRAY['preprocessor_module', 'job'], to_jsonb($1::UUID::TEXT)), - ARRAY['preprocessor_module', 'type'], - to_jsonb('InProgress'::text) - ) - WHERE id = $2", - job_in_progress, - flow - ) - .execute(db) - .await?; - } - Step::FailureStep => { - sqlx::query!( - "UPDATE v2_job_status SET - flow_status = jsonb_set( - jsonb_set(flow_status, ARRAY['failure_module', 'job'], to_jsonb($1::UUID::TEXT)), - ARRAY['failure_module', 'type'], - to_jsonb('InProgress'::text) - ) - WHERE id = $2", - job_in_progress, - flow - ) - .execute(db) - .await?; - } - } - - Ok(step) -} - -#[derive(Debug, Copy, Clone)] -pub enum Step { - Step(usize), - PreprocessorStep, - FailureStep, -} - -impl Step { - fn from_i32_and_len(step: i32, len: usize) -> Self { - if step < 0 { - Step::PreprocessorStep - } else if (step as usize) < len { - Step::Step(step as usize) - } else { - Step::FailureStep - } - } -} - -#[instrument(level = "trace", skip_all)] -pub async fn get_step_of_flow_status(db: &DB, id: Uuid) -> error::Result { - let r = sqlx::query!( - "SELECT (flow_status->'step')::integer as step, jsonb_array_length(flow_status->'modules') as len - FROM v2_job_status WHERE id = $1", - id - ) - .fetch_one(db) - .await - .map_err(|e| Error::internal_err(format!("fetching step flow status: {e:#}")))?; - - if let Some(step) = r.step { - Ok(Step::from_i32_and_len(step, r.len.unwrap_or(0) as usize)) - } else { - Err(Error::internal_err("step is null".to_string())) - } -} - /// resumes should be in order of timestamp ascending, so that more recent are at the end #[instrument(level = "trace", skip_all)] async fn transform_input( @@ -1539,14 +1584,14 @@ async fn transform_input( #[instrument(level = "trace", skip_all)] pub async fn handle_flow( - flow_job: Arc, + flow_job: Arc, flow_data: &cache::FlowData, db: &sqlx::Pool, client: &AuthedClient, last_result: Option>>, - same_worker_tx: SameWorkerSender, + same_worker_tx: &SameWorkerSender, worker_dir: &str, - job_completed_tx: Sender, + job_completed_tx: JobCompletedSender, worker_name: &str, ) -> anyhow::Result<()> { let flow = flow_data.value(); @@ -1554,13 +1599,14 @@ pub async fn handle_flow( .parse_flow_status() .with_context(|| "Unable to parse flow status")?; - if !flow_job.is_flow_step + let schedule_path = flow_job.schedule_path(); + if !flow_job.is_flow_step() && status.retry.fail_count == 0 - && flow_job.schedule_path.is_some() - && flow_job.script_path.is_some() + && schedule_path.is_some() + && flow_job.runnable_path.is_some() && status.step == 0 { - let schedule_path = flow_job.schedule_path.as_ref().unwrap(); + let schedule_path = schedule_path.as_ref().unwrap(); let schedule = get_schedule_opt(db, &flow_job.workspace_id, schedule_path) .warn_after_seconds(5) @@ -1571,7 +1617,7 @@ pub async fn handle_flow( db, &flow_job, &schedule, - flow_job.script_path.as_ref().unwrap(), + flow_job.runnable_path.as_ref().unwrap(), &flow_job.workspace_id, ) .warn_after_seconds(5) @@ -1590,22 +1636,47 @@ pub async fn handle_flow( ); } } - let mut rec = Some(PushNextFlowJobRec { flow_job: flow_job, status: status }); - while let Some(nrec) = rec { - rec = push_next_flow_job( - nrec.flow_job, - nrec.status, + let mut rec = PushNextFlowJobRec { flow_job: flow_job, status: status }; + loop { + let PushNextFlowJobRec { flow_job, status } = rec; + let next = push_next_flow_job( + flow_job, + status, flow, db, client, last_result.clone(), - same_worker_tx.clone(), + same_worker_tx, worker_dir, - job_completed_tx.clone(), worker_name, ) .warn_after_seconds(10) .await?; + match next { + PushNextFlowJob::Rec(nrec) => { + tracing::info!("recursively pushing next flow job {}", nrec.flow_job.id); + rec = nrec; + } + PushNextFlowJob::Done(update_flow) => { + if let Some(update_flow) = update_flow { + tracing::info!( + "sending flow status update {} with success {} to job completed channel", + update_flow.flow, + update_flow.success + ); + job_completed_tx + .send(SendResult::UpdateFlow(update_flow), false) + .warn_after_seconds(3) + .await + .map_err(|e| { + Error::internal_err(format!( + "error sending update flow message to job completed channel: {e:#}" + )) + })?; + } + break; + } + } } Ok(()) @@ -1651,26 +1722,29 @@ lazy_static::lazy_static! { pub static ref EHM: HashMap> = HashMap::new(); } +enum PushNextFlowJob { + Rec(PushNextFlowJobRec), + Done(Option), +} struct PushNextFlowJobRec { - flow_job: Arc, + flow_job: Arc, status: FlowStatus, } // #[async_recursion] // #[instrument(level = "trace", skip_all)] async fn push_next_flow_job( - flow_job: Arc, + flow_job: Arc, mut status: FlowStatus, flow: &FlowValue, db: &sqlx::Pool, client: &AuthedClient, last_job_result: Option>>, - same_worker_tx: SameWorkerSender, + same_worker_tx: &SameWorkerSender, worker_dir: &str, - job_completed_tx: Sender, worker_name: &str, -) -> error::Result> { +) -> error::Result { let job_root = flow_job - .root_job + .flow_innermost_root_job .map(|x| x.to_string()) .unwrap_or_else(|| "none".to_string()); tracing::info!(id = %flow_job.id, root_id = %job_root, "pushing next flow job"); @@ -1690,7 +1764,7 @@ async fn push_next_flow_job( Step::FailureStep => status.failure_module.module_status.clone(), }; - let fj: mappable_rc::Marc = flow_job.clone().into(); + let fj: mappable_rc::Marc = flow_job.clone().into(); let arc_flow_job_args: Marc>> = Marc::map(fj, |x| { if let Some(args) = &x.args { &args.0 @@ -1701,68 +1775,63 @@ async fn push_next_flow_job( // if this is an empty module of if the module has already been completed, successfully, update the parent flow if flow.modules.is_empty() || matches!(status_module, FlowStatusModule::Success { .. }) { - job_completed_tx - .send(SendResult::UpdateFlow { - flow: flow_job.id, - success: true, - result: if flow.modules.is_empty() { - to_raw_value(arc_flow_job_args.as_ref()) - } else { - // it has to be an empty for loop event - serde_json::from_str("[]").unwrap() - }, - stop_early_override: None, - w_id: flow_job.workspace_id.clone(), - worker_dir: worker_dir.to_string(), - token: client.token.clone(), - }) - .await - .map_err(|e| { - Error::internal_err(format!( - "error sending update flow message to job completed channel: {e:#}" - )) - })?; - - return Ok(None); + return Ok(PushNextFlowJob::Done(Some(UpdateFlow { + flow: flow_job.id, + success: true, + result: if flow.modules.is_empty() { + to_raw_value(arc_flow_job_args.as_ref()) + } else { + // it has to be an empty for loop event + serde_json::from_str("[]").unwrap() + }, + stop_early_override: None, + w_id: flow_job.workspace_id.clone(), + worker_dir: worker_dir.to_string(), + token: client.token.clone(), + }))); } if matches!(step, Step::Step(0)) { - if !flow_job.is_flow_step && flow_job.schedule_path.is_some() { + if !flow_job.is_flow_step() && flow_job.schedule_path().is_some() { + let schedule_path = flow_job.schedule_path(); let no_flow_overlap = sqlx::query_scalar!( "SELECT no_flow_overlap FROM schedule WHERE path = $1 AND workspace_id = $2", - flow_job.schedule_path.as_ref().unwrap(), + schedule_path.as_ref().unwrap(), flow_job.workspace_id.as_str() ) .fetch_one(db) + .warn_after_seconds(3) .await?; if no_flow_overlap { let overlapping = sqlx::query_scalar!( - // Query plan: - // - use of the `ix_v2_job_root_by_path` index; hence the `parent_job IS NULL` - // clause. - // - select from `v2_job` first, then join with `v2_job_queue` to avoid a full - // table scan on `running = true`. - "SELECT id - FROM v2_job j JOIN v2_job_queue USING (id) - WHERE j.workspace_id = $2 AND trigger_kind = 'schedule' AND trigger = $1 AND runnable_path = $4 - AND parent_job IS NULL - AND j.id != $3 - AND running = true", - flow_job.schedule_path.as_ref().unwrap(), - flow_job.workspace_id.as_str(), - flow_job.id, - flow_job.script_path.as_ref().unwrap() - ) - .fetch_all(db) - .await?; + // Query plan: + // - use of the `ix_v2_job_root_by_path` index; hence the `parent_job IS NULL` + // clause. + // - select from `v2_job` first, then join with `v2_job_queue` to avoid a full + // table scan on `running = true`. + "SELECT id + FROM v2_job j JOIN v2_job_queue USING (id) + WHERE j.workspace_id = $2 AND trigger_kind = 'schedule' AND trigger = $1 AND runnable_path = $4 + AND parent_job IS NULL + AND j.id != $3 + AND running = true", + schedule_path.as_ref().unwrap(), + flow_job.workspace_id.as_str(), + flow_job.id, + flow_job.runnable_path() + ) + .fetch_all(db) + .warn_after_seconds(3) + .await?; if overlapping.len() > 0 { let overlapping_str = overlapping .iter() .map(|x| x.to_string()) .collect::>() .join(", "); - job_completed_tx - .send(SendResult::UpdateFlow { + + return Ok(PushNextFlowJob::Done(Some( + UpdateFlow { flow: flow_job.id, success: true, result: serde_json::from_str( @@ -1773,15 +1842,8 @@ async fn push_next_flow_job( w_id: flow_job.workspace_id.clone(), worker_dir: worker_dir.to_string(), token: client.token.clone(), - }) - .await - .map_err(|e| { - Error::internal_err(format!( - "error sending update flow message to job completed channel: {e:#}" - )) - })?; - - return Ok(None); + } + ))); } } } @@ -1799,26 +1861,18 @@ async fn push_next_flow_job( flow_job.scheduled_for.to_string(), )]), ) + .warn_after_seconds(3) .await?; if skip { - job_completed_tx - .send(SendResult::UpdateFlow { - flow: flow_job.id, - success: true, - result: serde_json::from_str("\"stopped early\"").unwrap(), - stop_early_override: Some(true), - w_id: flow_job.workspace_id.clone(), - worker_dir: worker_dir.to_string(), - token: client.token.clone(), - }) - .await - .map_err(|e| { - Error::internal_err(format!( - "error sending update flow message to job completed channel: {e:#}" - )) - })?; - - return Ok(None); + return Ok(PushNextFlowJob::Done(Some(UpdateFlow { + flow: flow_job.id, + success: true, + result: serde_json::from_str("\"stopped early\"").unwrap(), + stop_early_override: Some(true), + w_id: flow_job.workspace_id.clone(), + worker_dir: worker_dir.to_string(), + token: client.token.clone(), + }))); } } } @@ -1837,7 +1891,10 @@ async fn push_next_flow_job( if last_job_result.is_some() { last_job_result.unwrap() } else { - match get_previous_job_result(db, flow_job.workspace_id.as_str(), &status).await? { + match get_previous_job_result(db, flow_job.workspace_id.as_str(), &status) + .warn_after_seconds(3) + .await? + { None => Arc::new(to_raw_value(&json!("{}"))), Some(previous_job_result) => Arc::new(previous_job_result), } @@ -1856,7 +1913,7 @@ async fn push_next_flow_job( FlowStatusModule::WaitingForPriorSteps { .. } | FlowStatusModule::WaitingForEvents { .. } ) { if let Some((suspend, last)) = needs_resume(&flow, &status) { - let mut tx = db.begin().await?; + let mut tx = db.begin().warn_after_seconds(3).await?; /* Lock this row to prevent the suspend column getting out out of sync * if a resume message arrives after we fetch and count them here. @@ -1867,17 +1924,20 @@ async fn push_next_flow_job( flow_job.id ) .fetch_one(&mut *tx) + .warn_after_seconds(3) .await .context("lock flow in queue")?; let resumes = sqlx::query_as::<_, ResumeRow>( - "SELECT value, approver, resume_id, approved FROM resume_job WHERE job = $1 ORDER BY created_at ASC", - ) - .bind(last) - .fetch_all(&mut *tx) - .await? - .into_iter() - .collect::>(); + "SELECT value, approver, resume_id, approved FROM resume_job WHERE job = $1 ORDER BY created_at ASC", + ) + .bind(last) + .fetch_all(&mut *tx) + .warn_after_seconds(3) + .await + ? + .into_iter() + .collect::>(); resume_messages.extend(resumes.iter().map(|r| to_raw_value(&r.value))); approvers.extend(resumes.iter().map(|r| { @@ -1907,22 +1967,23 @@ async fn push_next_flow_job( .insert("previous_result".to_string(), arc_last_job_result.clone()); let eval_result = serde_json::from_str::>( - eval_timeout( - expr.to_string(), - context, - Some(arc_flow_job_args.clone()), - None, - None, - None - ) - .await - .map_err(|e| { - Error::ExecutionErr(format!( - "Error during isolated evaluation of expression `{expr}`:\n{e:#}" - )) - })? - .get(), - ); + eval_timeout( + expr.to_string(), + context, + Some(arc_flow_job_args.clone()), + None, + None, + None + ) + .warn_after_seconds(3) + .await + .map_err(|e| { + Error::ExecutionErr(format!( + "Error during isolated evaluation of expression `{expr}`:\n{e:#}" + )) + })? + .get(), + ); if eval_result.is_ok() { user_groups_required = eval_result.ok().unwrap_or(Vec::new()) } else { @@ -1941,12 +2002,13 @@ async fn push_next_flow_job( }; sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['approval_conditions'], $1) - WHERE id = $2", + SET flow_status = JSONB_SET(flow_status, ARRAY['approval_conditions'], $1) + WHERE id = $2", json!(approval_conditions), flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; } @@ -1966,7 +2028,7 @@ async fn push_next_flow_job( .permissioned_as .trim_start_matches("u/") .to_string(), - email: flow_job.email.clone(), + email: flow_job.permissioned_as_email.clone(), username_override: None, }; @@ -1980,49 +2042,52 @@ async fn push_next_flow_job( resume_messages.push(to_raw_value(&js)); audit_log( - &mut *tx, - &audit_author, - "jobs.suspend_resume", - ActionKind::Update, - &flow_job.workspace_id, - Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval but can continue".to_string()}).to_string()), - None, - ) - .await?; + &mut *tx, + &audit_author, + "jobs.suspend_resume", + ActionKind::Update, + &flow_job.workspace_id, + Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval but can continue".to_string()}).to_string()), + None, + ) + .warn_after_seconds(3) + .await?; } sqlx::query!( - "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2) - WHERE id = $3", - (status.step - 1).to_string(), - json!(resumes - .into_iter() - .map(|r| Approval { - resume_id: r.resume_id as u16, - approver: r - .approver.clone() - .unwrap_or_else(|| "unknown".to_string()) - }) - .collect::>() - ), - flow_job.id - ) - .execute(&mut *tx) - .await?; + "UPDATE v2_job_status + SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2) + WHERE id = $3", + (status.step - 1).to_string(), + json!(resumes + .into_iter() + .map(|r| Approval { + resume_id: r.resume_id as u16, + approver: r + .approver.clone() + .unwrap_or_else(|| "unknown".to_string()) + }) + .collect::>() + ), + flow_job.id + ) + .execute(&mut *tx) + .warn_after_seconds(3) + .await?; // Remove the approval conditions from the flow status sqlx::query!( "UPDATE v2_job_status - SET flow_status = flow_status - 'approval_conditions' - WHERE id = $1", + SET flow_status = flow_status - 'approval_conditions' + WHERE id = $1", flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; /* continue on and run this job! */ - tx.commit().await?; + tx.commit().warn_after_seconds(3).await?; /* not enough messages to do this job, "park"/suspend until there are */ } else if matches!( @@ -2032,14 +2097,14 @@ async fn push_next_flow_job( { sqlx::query!( "WITH suspend AS ( - UPDATE v2_job_queue SET suspend = $2, suspend_until = now() + $3 - WHERE id = $4 - RETURNING id - ) UPDATE v2_job_status SET flow_status = JSONB_SET( - flow_status, - ARRAY['modules', flow_status->>'step'::TEXT], - $1 - ) WHERE id = (SELECT id FROM suspend)", + UPDATE v2_job_queue SET suspend = $2, suspend_until = now() + $3 + WHERE id = $4 + RETURNING id + ) UPDATE v2_job_status SET flow_status = JSONB_SET( + flow_status, + ARRAY['modules', flow_status->>'step'::TEXT], + $1 + ) WHERE id = (SELECT id FROM suspend)", json!(FlowStatusModule::WaitingForEvents { id: status_module.id(), count: required_events, @@ -2052,35 +2117,37 @@ async fn push_next_flow_job( flow_job.id, ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; sqlx::query!( "UPDATE v2_job_runtime SET ping = NULL - WHERE id = $1 AND ping = $2", + WHERE id = $1", flow_job.id, - flow_job.last_ping ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; - tx.commit().await?; - return Ok(None); + tx.commit().warn_after_seconds(3).await?; + return Ok(PushNextFlowJob::Done(None)); /* cancelled or we're WaitingForEvents but we don't have enough messages (timed out) */ } else { if is_disapproved.is_none() { audit_log( - &mut *tx, - &audit_author, - "jobs.suspend_resume", - ActionKind::Update, - &flow_job.workspace_id, - Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval and is cancelled".to_string()}).to_string()), - None, - ) - .await?; + &mut *tx, + &audit_author, + "jobs.suspend_resume", + ActionKind::Update, + &flow_job.workspace_id, + Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval and is cancelled".to_string()}).to_string()), + None, + ) + .warn_after_seconds(3) + .await?; } - tx.commit().await?; + tx.commit().warn_after_seconds(3).await?; let (logs, error_name) = if let Some(disapprover) = is_disapproved { ( @@ -2102,26 +2169,24 @@ async fn push_next_flow_job( let result: Value = json!({ "error": {"message": logs, "name": error_name}}); - append_logs(&flow_job.id, &flow_job.workspace_id, logs.clone(), db).await; + append_logs( + &flow_job.id, + &flow_job.workspace_id, + logs.clone(), + &db.into(), + ) + .warn_after_seconds(3) + .await; - job_completed_tx - .send(SendResult::UpdateFlow { - flow: flow_job.id, - success: false, - result: to_raw_value(&result), - stop_early_override: None, - w_id: flow_job.workspace_id.clone(), - worker_dir: worker_dir.to_string(), - token: client.token.clone(), - }) - .await - .map_err(|e| { - Error::internal_err(format!( - "error sending update flow message to job completed channel: {e:#}" - )) - })?; - - return Ok(None); + return Ok(PushNextFlowJob::Done(Some(UpdateFlow { + flow: flow_job.id, + success: false, + result: to_raw_value(&result), + stop_early_override: None, + w_id: flow_job.workspace_id.clone(), + worker_dir: worker_dir.to_string(), + token: client.token.clone(), + }))); } } } @@ -2174,22 +2239,23 @@ async fn push_next_flow_job( context.insert("previous_result".to_string(), arc_last_job_result.clone()); serde_json::from_str( - eval_timeout( - expr.to_string(), - context, - Some(arc_flow_job_args.clone()), - None, - None, - None, - ) - .await - .map_err(|e| { - Error::ExecutionErr(format!( - "Error during isolated evaluation of expression `{expr}`:\n{e:#}" - )) - })? - .get(), - ) + eval_timeout( + expr.to_string(), + context, + Some(arc_flow_job_args.clone()), + None, + None, + None, + ) + .warn_after_seconds(3) + .await + .map_err(|e| { + Error::ExecutionErr(format!( + "Error during isolated evaluation of expression `{expr}`:\n{e:#}" + )) + })? + .get(), + ) } }; match json_value.and_then(|x| serde_json::from_str::(x.get())) { @@ -2238,18 +2304,18 @@ async fn push_next_flow_job( scheduled_for_o = Some(from_now(retry_in)); status.retry.failed_jobs.push(job.clone()); sqlx::query!( - "UPDATE v2_job_status - SET flow_status = JSONB_SET(JSONB_SET(flow_status, ARRAY['retry'], $1), ARRAY['modules', $3::TEXT, 'failed_retries'], $4) - WHERE id = $2", - json!(RetryStatus { fail_count, ..status.retry.clone() }), - flow_job.id, - status.step.to_string(), - json!(status.retry.failed_jobs) - ) - .execute(db) - .warn_after_seconds(2) - .await - .context("update flow retry")?; + "UPDATE v2_job_status + SET flow_status = JSONB_SET(JSONB_SET(flow_status, ARRAY['retry'], $1), ARRAY['modules', $3::TEXT, 'failed_retries'], $4) + WHERE id = $2", + json!(RetryStatus { fail_count, ..status.retry.clone() }), + flow_job.id, + status.step.to_string(), + json!(status.retry.failed_jobs) + ) + .execute(db) + .warn_after_seconds(2) + .await + .context("update flow retry")?; status_module = FlowStatusModule::WaitingForPriorSteps { id: status_module.id() }; // we get the args from the last failed job @@ -2276,12 +2342,13 @@ async fn push_next_flow_job( if module.retry.as_ref().is_some_and(|x| x.has_attempts()) { sqlx::query!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['retry'], $1) - WHERE id = $2", + SET flow_status = JSONB_SET(flow_status, ARRAY['retry'], $1) + WHERE id = $2", json!(RetryStatus { fail_count: 0, failed_jobs: vec![] }), flow_job.id ) .execute(db) + .warn_after_seconds(3) .await .context("update flow retry")?; }; @@ -2300,7 +2367,9 @@ async fn push_next_flow_job( drop(resume_messages); let is_skipped = if let Some(skip_if) = &module.skip_if { - let idcontext = get_transform_context(&flow_job, previous_id.as_str(), &status).await?; + let idcontext = get_transform_context(&flow_job, previous_id.as_str(), &status) + .warn_after_seconds(3) + .await?; compute_bool_from_expr( &skip_if.expr, arc_flow_job_args.clone(), @@ -2311,6 +2380,7 @@ async fn push_next_flow_job( Some((resumes.clone(), resume.clone(), approvers.clone())), None, ) + .warn_after_seconds(3) .await? } else { false @@ -2335,11 +2405,12 @@ async fn push_next_flow_job( } else if let Some(id) = get_args_from_id { let args = sqlx::query_scalar!( "SELECT args AS \"args: Json>>\" - FROM v2_job WHERE id = $1 AND workspace_id = $2", + FROM v2_job WHERE id = $1 AND workspace_id = $2", id, &flow_job.workspace_id ) .fetch_optional(db) + .warn_after_seconds(3) .await?; if let Some(args) = args { Ok(Marc::new(args.map(|x| x.0).unwrap_or_else(HashMap::new))) @@ -2372,7 +2443,9 @@ async fn push_next_flow_job( | FlowModuleValue::FlowScript { input_transforms, .. } | FlowModuleValue::Flow { input_transforms, .. }, ) => { - let ctx = get_transform_context(&flow_job, &previous_id, &status).await?; + let ctx = get_transform_context(&flow_job, &previous_id, &status) + .warn_after_seconds(3) + .await?; transform_context = Some(ctx); let by_id = transform_context.as_ref().unwrap(); transform_input( @@ -2385,6 +2458,7 @@ async fn push_next_flow_job( by_id, client, ) + .warn_after_seconds(3) .await .map(Marc::new) } @@ -2415,6 +2489,7 @@ async fn push_next_flow_job( approvers.clone(), is_skipped, ) + .warn_after_seconds(3) .await?; tracing::info!(id = %flow_job.id, root_id = %job_root, "next flow transform computed"); @@ -2423,9 +2498,9 @@ async fn push_next_flow_job( NextFlowTransform::EmptyInnerFlows { branch_chosen } => { let raw_status = sqlx::query_scalar!( "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2) - WHERE id = $3 - RETURNING flow_status AS \"flow_status: Json>\"", + SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2) + WHERE id = $3 + RETURNING flow_status AS \"flow_status: Json>\"", status.step.to_string(), json!(FlowStatusModule::Success { id: status_module.id(), @@ -2440,6 +2515,7 @@ async fn push_next_flow_job( flow_job.id ) .fetch_optional(db) + .warn_after_seconds(3) .await? .flatten(); @@ -2449,13 +2525,13 @@ async fn push_next_flow_job( if let Some(status) = status { // // flow is reprocessed by the worker in a state where the module has completed successfully. - return Ok(Some(PushNextFlowJobRec { + return Ok(PushNextFlowJob::Rec(PushNextFlowJobRec { flow_job: flow_job, status: status, })); } else { return Err(Error::BadRequest( - "impossible to parse new flow status after applying innr flows".to_string(), + "impossible to parse new flow status after applying inner flows".to_string(), )); } } @@ -2464,8 +2540,8 @@ async fn push_next_flow_job( // Also check `flow_job.same_worker` for [`JobKind::Flow`] jobs as it's no // more reflected to the flow value on push. let job_same_worker = flow_job.same_worker - && matches!(flow_job.job_kind, JobKind::Flow) - && flow_job.script_hash.is_some(); + && matches!(flow_job.kind, JobKind::Flow) + && flow_job.runnable_id.is_some(); let continue_on_same_worker = (flow.same_worker || job_same_worker) && module.suspend.is_none() && module.sleep.is_none(); @@ -2478,7 +2554,7 @@ async fn push_next_flow_job( }; let len = job_payloads.len(); - let mut tx = db.begin().await?; + let mut tx = db.begin().warn_after_seconds(3).await?; let nargs = args.as_ref(); for (i, payload_tag) in job_payloads.into_iter().enumerate() { if i % 100 == 0 && i != 0 { @@ -2488,6 +2564,7 @@ async fn push_next_flow_job( flow_job.id, ) .execute(db) + .warn_after_seconds(3) .await?; } tracing::debug!(id = %flow_job.id, root_id = %job_root, "pushing job {i} of {len}"); @@ -2526,7 +2603,9 @@ async fn push_next_flow_job( args.insert("iter".to_string(), to_raw_value(new_args)); if let Some(input_transforms) = simple_input_transforms { //previous id is none because we do not want to use previous id if we are in a for loop - let ctx = get_transform_context(&flow_job, "", &status).await?; + let ctx = get_transform_context(&flow_job, "", &status) + .warn_after_seconds(3) + .await?; let ti = transform_input( Marc::new(args), arc_last_job_result.clone(), @@ -2537,6 +2616,7 @@ async fn push_next_flow_job( &ctx, client, ) + .warn_after_seconds(3) .await .map_err(|e| { Error::ExecutionErr( @@ -2572,7 +2652,9 @@ async fn push_next_flow_job( to_raw_value(&json!({ "index": i as i32, "value": itered[i]})), ); if let Some(input_transforms) = simple_input_transforms { - let ctx = get_transform_context(&flow_job, &previous_id, &status).await?; + let ctx = get_transform_context(&flow_job, &previous_id, &status) + .warn_after_seconds(3) + .await?; let ti = transform_input( Marc::new(hm), arc_last_job_result.clone(), @@ -2583,6 +2665,7 @@ async fn push_next_flow_job( &ctx, client, ) + .warn_after_seconds(3) .await .map_err(|e| { Error::ExecutionErr(format!( @@ -2635,31 +2718,35 @@ async fn push_next_flow_job( } { None } else { - flow_job.root_job.or_else(|| Some(flow_job.id)) + flow_job + .flow_innermost_root_job + .or_else(|| Some(flow_job.id)) }; // forward root job permissions to the new job - let job_perms: Option = if JOB_TOKEN.is_none() { - if let Some(root_job) = &flow_job.root_job.or_else(|| Some(flow_job.id)) { + let job_perms: Option = { + if let Some(root_job) = &flow_job + .flow_innermost_root_job + .or_else(|| Some(flow_job.id)) + { sqlx::query_as!( - JobPerms, - "SELECT * FROM job_perms WHERE job_id = $1 AND workspace_id = $2", - root_job, - flow_job.workspace_id, - ) - .fetch_optional(&mut *tx) - .await? - .map(|x| x.into()) + JobPerms, + "SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2", + root_job, + flow_job.workspace_id, + ) + .fetch_optional(&mut *tx) + .warn_after_seconds(3) + .await? + .map(|x| x.into()) } else { None } - } else { - None }; tracing::debug!(id = %flow_job.id, root_id = %job_root, "computed perms for job {i} of {len}"); - let tag = if flow_job.tag == "flow" - || flow_job.tag == format!("flow-{}", flow_job.workspace_id) + let tag = if !matches!(step, Step::PreprocessorStep) + && (flow_job.tag == "flow" || flow_job.tag == format!("flow-{}", flow_job.workspace_id)) { payload_tag.tag.clone() } else { @@ -2670,7 +2757,10 @@ async fn push_next_flow_job( { (&on_behalf_of.email, on_behalf_of.permissioned_as.clone()) } else { - (&flow_job.email, flow_job.permissioned_as.to_owned()) + ( + &flow_job.permissioned_as_email, + flow_job.permissioned_as.to_owned(), + ) }; let tx2 = PushIsolationLevel::Transaction(tx); let (uuid, mut inner_tx) = push( @@ -2683,7 +2773,7 @@ async fn push_next_flow_job( email, permissioned_as, scheduled_for_o, - flow_job.schedule_path.clone(), + flow_job.schedule_path(), Some(flow_job.id), root_job, None, @@ -2707,6 +2797,7 @@ async fn push_next_flow_job( worker_name ) .execute(&mut *inner_tx) + .warn_after_seconds(3) .await; } @@ -2719,14 +2810,15 @@ async fn push_next_flow_job( if i as u16 >= p { sqlx::query!( "UPDATE v2_job_queue SET - suspend = $1, - suspend_until = now() + interval '14 day', - running = true - WHERE id = $2", + suspend = $1, + suspend_until = now() + interval '14 day', + running = true + WHERE id = $2", (i as u16 - p + 1) as i32, uuid, ) .execute(&mut *inner_tx) + .warn_after_seconds(3) .await?; } tracing::debug!(id = %flow_job.id, root_id = %job_root, "updated suspend for {uuid}"); @@ -2739,14 +2831,15 @@ async fn push_next_flow_job( })?; sqlx::query!( - "UPDATE v2_job_status - SET flow_status = JSONB_SET(flow_status, ARRAY['cleanup_module', 'flow_jobs_to_clean'], COALESCE(flow_status->'cleanup_module'->'flow_jobs_to_clean', '[]'::jsonb) || $1) - WHERE id = $2", - uuid_singleton_json, - root_job.unwrap_or(flow_job.id) - ) - .execute(&mut *inner_tx) - .await?; + "UPDATE v2_job_status + SET flow_status = JSONB_SET(flow_status, ARRAY['cleanup_module', 'flow_jobs_to_clean'], COALESCE(flow_status->'cleanup_module'->'flow_jobs_to_clean', '[]'::jsonb) || $1) + WHERE id = $2", + uuid_singleton_json, + root_job.unwrap_or(flow_job.id) + ) + .execute(&mut *inner_tx) + .warn_after_seconds(3) + .await?; } tx = inner_tx; @@ -2765,11 +2858,12 @@ async fn push_next_flow_job( for uuid in &uuids { sqlx::query!( "INSERT INTO parallel_monitor_lock (parent_flow_id, job_id) - VALUES ($1, $2)", + VALUES ($1, $2)", flow_job.id, uuid ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; tracing::debug!(id = %flow_job.id, root_id = %job_root, "updated parallel monitor lock for {uuid}"); } @@ -2869,12 +2963,12 @@ async fn push_next_flow_job( Step::FailureStep => { sqlx::query!( "UPDATE v2_job_status SET - flow_status = JSONB_SET( - JSONB_SET(flow_status, ARRAY['failure_module'], $1), - ARRAY['step'], - $2 - ) - WHERE id = $3", + flow_status = JSONB_SET( + JSONB_SET(flow_status, ARRAY['failure_module'], $1), + ARRAY['step'], + $2 + ) + WHERE id = $3", json!(FlowStatusModuleWParent { parent_module: Some(current_id.clone()), module_status: new_status @@ -2883,39 +2977,42 @@ async fn push_next_flow_job( flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; } Step::PreprocessorStep => { sqlx::query!( "UPDATE v2_job_status SET - flow_status = JSONB_SET( - JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1), - ARRAY['step'], - $2 - ) - WHERE id = $3", + flow_status = JSONB_SET( + JSONB_SET(flow_status, ARRAY['preprocessor_module'], $1), + ARRAY['step'], + $2 + ) + WHERE id = $3", json!(new_status), json!(-1), flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; } Step::Step(i) => { sqlx::query!( "UPDATE v2_job_status SET - flow_status = JSONB_SET( - JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2), - ARRAY['step'], - $3 - ) - WHERE id = $4", + flow_status = JSONB_SET( + JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2), + ARRAY['step'], + $3 + ) + WHERE id = $4", i as i32, json!(new_status), json!(i), flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; } }; @@ -2927,23 +3024,27 @@ async fn push_next_flow_job( flow_job.id ) .execute(&mut *tx) + .warn_after_seconds(3) .await?; - tx.commit().warn_after_seconds(3).await?; - tracing::info!(id = %flow_job.id, root_id = %job_root, "all next flow jobs pushed: {uuids:?}"); - if continue_on_same_worker { if !is_one_uuid { return Err(Error::BadRequest( - "Cannot continue on same worker with multiple jobs, parallel cannot be used in conjunction with same_worker".to_string(), - )); + "Cannot continue on same worker with multiple jobs, parallel cannot be used in conjunction with same_worker".to_string(), + )); } + } + tx.commit().warn_after_seconds(3).await?; + tracing::info!(id = %flow_job.id, root_id = %job_root, "all next flow jobs pushed: {uuids:?}"); + + if continue_on_same_worker { same_worker_tx .send(SameWorkerPayload { job_id: first_uuid, recoverable: true }) + .warn_after_seconds(3) .await .map_err(to_anyhow)?; } - return Ok(None); + return Ok(PushNextFlowJob::Done(None)); } // async fn jump_to_next_step( @@ -3119,22 +3220,22 @@ fn payload_from_modules<'a>( }) } -fn get_path(flow_job: &QueuedJob, status: &FlowStatus, module: &FlowModule) -> String { +fn get_path(flow_job: &MiniPulledJob, status: &FlowStatus, module: &FlowModule) -> String { if status .preprocessor_module .as_ref() .is_some_and(|x| x.id() == module.id) { - format!("{}/preprocessor", flow_job.script_path()) + format!("{}/preprocessor", flow_job.runnable_path()) } else { - format!("{}/{}", flow_job.script_path(), module.id) + format!("{}/{}", flow_job.runnable_path(), module.id) } } async fn compute_next_flow_transform( arc_flow_job_args: Marc>>, arc_last_job_result: Arc>, - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, flow: &FlowValue, by_id: Option, db: &DB, @@ -3299,7 +3400,7 @@ async fn compute_next_flow_transform( /* forloop modules are expected set `iter: { value: Value, index: usize }` as job arguments */ FlowModuleValue::ForloopFlow { modules, modules_node, iterator, parallel, .. } => { // if it's a simple single step flow, we will collapse it as an optimization and need to pass flow_input as an arg - let is_simple = !matches!(flow_job.job_kind, JobKind::FlowPreview) + let is_simple = !matches!(flow_job.kind, JobKind::FlowPreview) && !parallel && is_simple_modules(&modules, flow.failure_module.as_ref()); @@ -3370,7 +3471,7 @@ async fn compute_next_flow_transform( flow.failure_module.as_ref(), flow.same_worker, || format!("{}-{i}", status.step), - || format!("{}/forloop-{i}", flow_job.script_path()), + || format!("{}/forloop-{i}", flow_job.runnable_path()), true, ) else { return None; @@ -3460,7 +3561,7 @@ async fn compute_next_flow_transform( flow.failure_module.as_ref(), flow.same_worker, || status.step.to_string(), - || format!("{}/branchone-{}", flow_job.script_path(), branch_idx), + || format!("{}/branchone-{}", flow_job.runnable_path(), branch_idx), true, ) else { return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: Some(branch) }); @@ -3496,7 +3597,7 @@ async fn compute_next_flow_transform( flow.failure_module.as_ref(), flow.same_worker, || format!("{}-{i}", status.step), - || format!("{}/branchall-{}", flow_job.script_path(), i), + || format!("{}/branchall-{}", flow_job.runnable_path(), i), false, ) else { return None; @@ -3563,7 +3664,7 @@ async fn compute_next_flow_transform( || { format!( "{}/branchall-{}", - flow_job.script_path(), + flow_job.runnable_path(), branch_status.branch ) }, @@ -3598,13 +3699,13 @@ async fn next_loop_iteration( ns: ForloopNextIteration, modules: Vec, modules_node: Option, - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, is_simple: bool, db: &sqlx::Pool, module: &FlowModule, delete_after_use: bool, ) -> Result { - let inner_path = || format!("{}/loop-{}", flow_job.script_path(), ns.index); + let inner_path = || format!("{}/loop-{}", flow_job.runnable_path(), ns.index); if is_simple { let mut value = modules[0].get_value()?; let simple_input_transforms = match &mut value { @@ -3669,7 +3770,7 @@ pub(super) fn is_simple_modules( async fn next_forloop_status( status_module: &FlowStatusModule, by_id: Option, - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, previous_id: &str, status: &FlowStatus, iterator: &InputTransform, @@ -3782,11 +3883,11 @@ async fn next_forloop_status( itered.clone() }; let (index, next) = index - .checked_add(1) - .and_then(|i| itered_new.get(i).map(|next| (i, next))) - .with_context(|| { - format!("Could not find iteration number {index} restarting inside the for-loop flow. It's possible the itered-array has changed and this value isn't available anymore.") - })?; + .checked_add(1) + .and_then(|i| itered_new.get(i).map(|next| (i, next))) + .with_context(|| { + format!("Could not find iteration number {index} restarting inside the for-loop flow. It's possible the itered-array has changed and this value isn't available anymore.") + })?; ForLoopStatus::NextIteration(ForloopNextIteration { index, @@ -3808,7 +3909,7 @@ async fn next_forloop_status( async fn payload_from_simple_module( value: FlowModuleValue, db: &sqlx::Pool, - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, module: &FlowModule, inner_path: String, ) -> Result { @@ -3908,28 +4009,23 @@ async fn flow_to_payload( w_id: &str, db: &DB, ) -> Result { - let record = sqlx::query!( - "SELECT on_behalf_of_email, edited_by FROM flow WHERE path = $1 AND workspace_id = $2", - path, - w_id, - ) - .fetch_one(db) - .await - .map_err(|e| Error::NotFound(format!("fetching flow: {e:#}")))?; - let on_behalf_of = if let Some(email) = record.on_behalf_of_email { - Some(OnBehalfOf { email, permissioned_as: username_to_permissioned_as(&record.edited_by) }) + let FlowVersionInfo { version, on_behalf_of_email, edited_by, tag, .. } = + get_latest_flow_version_info_for_path(db, w_id, &path, true).await?; + let on_behalf_of = if let Some(email) = on_behalf_of_email { + Some(OnBehalfOf { email, permissioned_as: username_to_permissioned_as(&edited_by) }) } else { None }; - let payload = JobPayload::Flow { path, dedicated_worker: None, apply_preprocessor: false }; - Ok(JobPayloadWithTag { payload, tag: None, delete_after_use, timeout: None, on_behalf_of }) + let payload = + JobPayload::Flow { path, dedicated_worker: None, apply_preprocessor: false, version }; + Ok(JobPayloadWithTag { payload, tag, delete_after_use, timeout: None, on_behalf_of }) } async fn script_to_payload( script_hash: Option, script_path: String, db: &sqlx::Pool, - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, module: &FlowModule, tag_override: Option, ) -> Result { @@ -3951,9 +4047,9 @@ async fn script_to_payload( } else { let hash = script_hash.unwrap(); let mut tx: sqlx::Transaction<'_, sqlx::Postgres> = db.begin().await?; - let ( + let ScriptHashInfo { tag, - custom_concurrency_key, + concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl, @@ -3961,10 +4057,11 @@ async fn script_to_payload( dedicated_worker, priority, delete_after_use, - script_timeout, + timeout, on_behalf_of_email, created_by, - ) = script_hash_to_tag_and_limits(&hash, &mut tx, &flow_job.workspace_id).await?; + .. + } = get_script_info_for_hash(&mut *tx, &flow_job.workspace_id, hash.0).await?; let on_behalf_of = if let Some(email) = on_behalf_of_email { Some(OnBehalfOf { email, permissioned_as: username_to_permissioned_as(&created_by) }) } else { @@ -3974,7 +4071,7 @@ async fn script_to_payload( JobPayload::ScriptHash { hash, path: script_path, - custom_concurrency_key, + custom_concurrency_key: concurrency_key, concurrent_limit, concurrency_time_window_s, cache_ttl: module.cache_ttl.map(|x| x as i32).ok_or(cache_ttl).ok(), @@ -3985,7 +4082,7 @@ async fn script_to_payload( }, tag_override.to_owned().or(tag), delete_after_use, - script_timeout, + timeout, on_behalf_of, ) }; @@ -4003,7 +4100,7 @@ async fn script_to_payload( } async fn get_transform_context( - flow_job: &QueuedJob, + flow_job: &MiniPulledJob, previous_id: &str, status: &FlowStatus, ) -> error::Result { @@ -4086,7 +4183,7 @@ async fn get_previous_job_result( Some(FlowStatusModule::Success { job, .. }) => Ok(Some( sqlx::query_scalar!( "SELECT result AS \"result!: Json>\" - FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", + FROM v2_job_completed WHERE id = $1 AND workspace_id = $2", job, w_id ) diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 7bf0929448..7fee5932e4 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -1,6 +1,10 @@ +use std::borrow::Cow; use std::collections::HashMap; +use std::fs::{create_dir_all, remove_dir_all}; use std::path::{Component, Path, PathBuf}; +#[cfg(feature = "python")] +use crate::ansible_executor::{get_git_repos_lock, AnsibleDependencyLocks}; use async_recursion::async_recursion; use serde_json::value::RawValue; use serde_json::{json, Value}; @@ -15,14 +19,15 @@ use windmill_common::jobs::JobPayload; use windmill_common::scripts::ScriptHash; #[cfg(feature = "python")] use windmill_common::worker::PythonAnnotations; -use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file}; +use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file, Connection}; +#[cfg(feature = "python")] +use windmill_parser_yaml::AnsibleRequirements; use windmill_common::{ apps::AppScriptId, cache::{self, RawData}, error::{self, to_anyhow}, flows::{add_virtual_items_if_necessary, FlowValue}, - jobs::QueuedJob, scripts::ScriptLang, DB, }; @@ -30,16 +35,19 @@ use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; #[cfg(feature = "python")] use windmill_parser_py_imports::parse_relative_imports; use windmill_parser_ts::parse_expr_for_imports; -use windmill_queue::{append_logs, CanceledBy, PushIsolationLevel}; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PushIsolationLevel}; use crate::common::OccupancyMetrics; use crate::csharp_executor::generate_nuget_lockfile; +#[cfg(feature = "java")] +use crate::java_executor::resolve; + #[cfg(feature = "php")] use crate::php_executor::{composer_install, parse_php_imports}; #[cfg(feature = "python")] use crate::python_executor::{ - create_dependencies_dir, handle_python_reqs, uv_pip_compile, PyVersion, + create_dependencies_dir, handle_python_reqs, split_requirements, uv_pip_compile, }; #[cfg(feature = "rust")] use crate::rust_executor::generate_cargo_lockfile; @@ -57,17 +65,17 @@ pub async fn update_script_dependency_map( relative_imports: Vec, ) -> error::Result<()> { let importer_kind = "script"; + + let mut tx = db.begin().await?; + tx = clear_dependency_parent_path(parent_path, script_path, w_id, importer_kind, tx).await?; + + tx = clear_dependency_map_for_item(script_path, w_id, importer_kind, tx, &None).await?; + if !relative_imports.is_empty() { let mut logs = "".to_string(); logs.push_str("\n--- RELATIVE IMPORTS ---\n\n"); logs.push_str(&relative_imports.join("\n")); - let mut tx = db.begin().await?; - tx = - clear_dependency_parent_path(parent_path, script_path, w_id, importer_kind, tx).await?; - - tx = clear_dependency_map_for_item(script_path, w_id, importer_kind, tx, &None).await?; - tx = add_relative_imports_to_dependency_map( script_path, w_id, @@ -78,9 +86,10 @@ pub async fn update_script_dependency_map( None, ) .await?; - tx.commit().await?; - append_logs(job_id, w_id, logs, db).await; + append_logs(job_id, w_id, logs, &db.into()).await; } + tx.commit().await?; + Ok(()) } @@ -177,9 +186,9 @@ fn try_normalize(path: &Path) -> Option { Some(ret) } -fn parse_bun_relative_imports(raw_code: &str, script_path: &str) -> error::Result> { +fn parse_ts_relative_imports(raw_code: &str, script_path: &str) -> error::Result> { let mut relative_imports = vec![]; - let r = parse_expr_for_imports(raw_code)?; + let r = parse_expr_for_imports(raw_code, true)?; for import in r { let import = import.trim_end_matches(".ts"); if import.starts_with("/") { @@ -209,27 +218,27 @@ pub fn extract_relative_imports( match language { #[cfg(feature = "python")] Some(ScriptLang::Python3) => parse_relative_imports(&raw_code, script_path).ok(), - Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => { - parse_bun_relative_imports(&raw_code, script_path).ok() + Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) | Some(ScriptLang::Deno) => { + parse_ts_relative_imports(&raw_code, script_path).ok() } _ => None, } } #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_dependency_job( - job: &QueuedJob, + job: &MiniPulledJob, preview_data: Option<&RawData>, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, - db: &sqlx::Pool, + db: &DB, worker_name: &str, worker_dir: &str, base_internal_url: &str, token: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result> { - let script_path = job.script_path(); + let script_path = job.runnable_path(); let raw_deps = job .args .as_ref() @@ -239,7 +248,7 @@ pub async fn handle_dependency_job( }) .unwrap_or(false); let npm_mode = if job - .language + .script_lang .as_ref() .map(|v| v == &ScriptLang::Bun) .unwrap_or(false) @@ -260,16 +269,41 @@ pub async fn handle_dependency_job( // `JobKind::Dependencies` job store either: // - A saved script `hash` in the `script_hash` column. // - Preview raw lock and code in the `queue` or `job` table. - let script_data = match job.script_hash { - Some(hash) => &cache::script::fetch(db, hash).await?.0, + let script_data = &match job.runnable_id { + Some(hash) => match cache::script::fetch(&Connection::from(db.clone()), hash).await { + Ok(d) => Cow::Owned(d.0), + Err(e) => { + let logs2 = sqlx::query_scalar!( + "SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = $2", + &job.id, + &job.workspace_id + ) + .fetch_optional(db) + .await? + .flatten() + .unwrap_or_else(|| "no logs".to_string()); + sqlx::query!( + "UPDATE script SET lock_error_logs = $1 WHERE hash = $2 AND workspace_id = $3", + &format!("{logs2}\n{e}"), + &job.runnable_id.unwrap_or(ScriptHash(0)).0, + &job.workspace_id + ) + .execute(db) + .await?; + return Err(Error::ExecutionErr(format!( + "Error creating schema validator: {e}" + ))); + } + }, _ => match preview_data { - Some(RawData::Script(data)) => data, + Some(RawData::Script(data)) => Cow::Borrowed(data), _ => return Err(Error::internal_err("expected script hash")), }, }; + let content = capture_dependency_job( &job.id, - job.language.as_ref().map(|v| Ok(v)).unwrap_or_else(|| { + job.script_lang.as_ref().map(|v| Ok(v)).unwrap_or_else(|| { Err(Error::internal_err( "Job Language required for dependency jobs".to_owned(), )) @@ -293,14 +327,14 @@ pub async fn handle_dependency_job( match content { Ok(content) => { - if job.script_hash.is_none() { + if job.runnable_id.is_none() { // it a one-off raw script dependency job, no need to update the db return Ok(to_raw_value_owned( json!({ "status": "Successful lock file generation", "lock": content }), )); } - let hash = job.script_hash.unwrap_or(ScriptHash(0)); + let hash = job.runnable_id.unwrap_or(ScriptHash(0)); let w_id = &job.workspace_id; sqlx::query!( "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", @@ -318,7 +352,7 @@ pub async fn handle_dependency_job( get_deployment_msg_and_parent_path_from_args(job.args.clone()); if let Err(e) = handle_deployment_metadata( - &job.email, + &job.permissioned_as_email, &job.created_by, &db, &w_id, @@ -335,44 +369,22 @@ pub async fn handle_dependency_job( tracing::error!(%e, "error handling deployment metadata"); } - let relative_imports = - extract_relative_imports(&script_data.code, script_path, &job.language); - if let Some(relative_imports) = relative_imports { - update_script_dependency_map( - &job.id, - db, - w_id, - &parent_path, - script_path, - relative_imports, - ) - .await?; - let already_visited = job - .args - .as_ref() - .map(|x| { - x.get("already_visited") - .map(|v| serde_json::from_str::>(v.get()).ok()) - .flatten() - }) - .flatten() - .unwrap_or_default(); - if let Err(e) = trigger_dependents_to_recompute_dependencies( - w_id, - script_path, - deployment_message, - parent_path, - &job.email, - &job.created_by, - &job.permissioned_as, - db, - already_visited, - ) - .await - { - tracing::error!(%e, "error triggering dependents to recompute dependencies"); - } - } + process_relative_imports( + db, + Some(job.id), + job.args.as_ref(), + &job.workspace_id, + script_path, + parent_path, + deployment_message, + &script_data.code, + &job.script_lang, + &job.permissioned_as_email, + &job.created_by, + &job.permissioned_as, + None, + ) + .await?; Ok(to_raw_value_owned( json!({ "status": "Successful lock file generation", "lock": content }), @@ -391,7 +403,7 @@ pub async fn handle_dependency_job( sqlx::query!( "UPDATE script SET lock_error_logs = $1 WHERE hash = $2 AND workspace_id = $3", &format!("{logs2}\n{error}"), - &job.script_hash.unwrap_or(ScriptHash(0)).0, + &job.runnable_id.unwrap_or(ScriptHash(0)).0, &job.workspace_id ) .execute(db) @@ -410,6 +422,82 @@ fn remove_ansi_codes(s: &str) -> String { ANSI_REGEX.replace_all(s, "").to_string() } +pub async fn process_relative_imports( + db: &sqlx::Pool, + job_id: Option, + args: Option<&Json>>>, + w_id: &str, + script_path: &str, + parent_path: Option, + deployment_message: Option, + code: &str, + script_lang: &Option, + permissioned_as_email: &str, + created_by: &str, + permissioned_as: &str, + lock: Option, +) -> error::Result<()> { + let relative_imports = extract_relative_imports(&code, script_path, script_lang); + if let Some(relative_imports) = relative_imports { + if (script_lang.is_some_and(|v| v == ScriptLang::Bun) + && lock + .as_ref() + .is_some_and(|v| v.contains("generatedFromPackageJson"))) + || (script_lang.is_some_and(|v| v == ScriptLang::Python3) + && lock + .as_ref() + .is_some_and(|v| v.starts_with(LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT))) + { + // if the lock file is generated from a package.json/requirements.txt, we need to clear the dependency map + // because we do not want to have dependencies be recomputed automatically. Empty relative imports passed + // to update_script_dependency_map will clear the dependency map. + update_script_dependency_map( + &job_id.unwrap_or_else(|| Uuid::nil()), + db, + w_id, + &parent_path, + script_path, + vec![], + ) + .await?; + } else { + update_script_dependency_map( + &job_id.unwrap_or_else(|| Uuid::nil()), + db, + w_id, + &parent_path, + script_path, + relative_imports, + ) + .await?; + } + let already_visited = args + .map(|x| { + x.get("already_visited") + .map(|v| serde_json::from_str::>(v.get()).ok()) + .flatten() + }) + .flatten() + .unwrap_or_default(); + if let Err(e) = trigger_dependents_to_recompute_dependencies( + w_id, + script_path, + deployment_message, + parent_path, + permissioned_as_email, + created_by, + permissioned_as, + db, + already_visited, + ) + .await + { + tracing::error!(%e, "error triggering dependents to recompute dependencies"); + } + } + Ok(()) +} + async fn trigger_dependents_to_recompute_dependencies( w_id: &str, script_path: &str, @@ -456,9 +544,9 @@ async fn trigger_dependents_to_recompute_dependencies( match r { Ok(r) => JobPayload::Dependencies { path: s.importer_path.clone(), - hash: r.0, - language: r.6, - dedicated_worker: r.7, + hash: ScriptHash(r.hash), + language: r.language, + dedicated_worker: r.dedicated_worker, }, Err(err) => { tracing::error!( @@ -546,7 +634,7 @@ async fn trigger_dependents_to_recompute_dependencies( } pub async fn handle_flow_dependency_job( - job: &QueuedJob, + job: &MiniPulledJob, preview_data: Option<&RawData>, mem_peak: &mut i32, canceled_by: &mut Option, @@ -558,7 +646,7 @@ pub async fn handle_flow_dependency_job( token: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result> { - let job_path = job.script_path.clone().ok_or_else(|| { + let job_path = job.runnable_path.clone().ok_or_else(|| { error::Error::internal_err( "Cannot resolve flow dependencies for flow without path".to_string(), ) @@ -579,7 +667,7 @@ pub async fn handle_flow_dependency_job( None } else { Some( - job.script_hash + job.runnable_id .clone() .ok_or_else(|| { Error::internal_err( @@ -606,7 +694,7 @@ pub async fn handle_flow_dependency_job( // `JobKind::FlowDependencies` job store either: // - A saved flow version `id` in the `script_hash` column. // - Preview raw flow in the `queue` or `job` table. - let mut flow = match job.script_hash { + let mut flow = match job.runnable_id { Some(ScriptHash(id)) => cache::flow::fetch_version(db, id).await?, _ => match preview_data { Some(RawData::Flow(data)) => data.clone(), @@ -620,13 +708,15 @@ pub async fn handle_flow_dependency_job( tx = clear_dependency_parent_path(&parent_path, &job_path, &job.workspace_id, "flow", tx) .await?; - sqlx::query!( - "DELETE FROM flow_workspace_runnables WHERE flow_path = $1 AND workspace_id = $2", + if !skip_flow_update { + sqlx::query!( + "DELETE FROM workspace_runnable_dependencies WHERE flow_path = $1 AND workspace_id = $2", job_path, job.workspace_id ) - .execute(&mut *tx) - .await?; + .execute(&mut *tx) + .await?; + } let modified_ids; let errors; (flow.modules, tx, modified_ids, errors) = lock_modules( @@ -644,6 +734,7 @@ pub async fn handle_flow_dependency_job( token, &nodes_to_relock, occupancy_metrics, + skip_flow_update, ) .await?; if !errors.is_empty() { @@ -664,7 +755,7 @@ pub async fn handle_flow_dependency_job( sqlx::query!( "UPDATE flow SET lock_error_logs = $1 WHERE path = $2 AND workspace_id = $3", &format!("{logs2}\n{error_message}"), - &job.script_path(), + &job.runnable_path(), &job.workspace_id ) .execute(db) @@ -677,7 +768,7 @@ pub async fn handle_flow_dependency_job( } else { sqlx::query!( "UPDATE flow SET lock_error_logs = NULL WHERE path = $1 AND workspace_id = $2", - &job.script_path(), + &job.runnable_path(), &job.workspace_id ) .execute(db) @@ -746,7 +837,7 @@ pub async fn handle_flow_dependency_job( tx.commit().await?; if let Err(e) = handle_deployment_metadata( - &job.email, + &job.permissioned_as_email, &job.created_by, &db, &job.workspace_id, @@ -799,7 +890,7 @@ struct LockModuleError { async fn lock_modules<'c>( modules: Vec, - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, @@ -812,6 +903,7 @@ async fn lock_modules<'c>( token: &str, locks_to_reload: &Option>, occupancy_metrics: &mut OccupancyMetrics, + skip_flow_update: bool, // (modules to replace old seq (even unmmodified ones), new transaction, modified ids) ) ) -> Result<( Vec, @@ -863,6 +955,7 @@ async fn lock_modules<'c>( token, locks_to_reload, occupancy_metrics, + skip_flow_update, )) .await?; e.value = FlowModuleValue::ForloopFlow { @@ -897,6 +990,7 @@ async fn lock_modules<'c>( token, locks_to_reload, occupancy_metrics, + skip_flow_update, )) .await?; nmodified_ids.extend(inner_modified_ids); @@ -923,6 +1017,7 @@ async fn lock_modules<'c>( token, locks_to_reload, occupancy_metrics, + skip_flow_update, )) .await?; e.value = FlowModuleValue::WhileloopFlow { @@ -954,6 +1049,7 @@ async fn lock_modules<'c>( token, locks_to_reload, occupancy_metrics, + skip_flow_update, )) .await?; nmodified_ids.extend(inner_modified_ids); @@ -978,6 +1074,7 @@ async fn lock_modules<'c>( token, locks_to_reload, occupancy_metrics, + skip_flow_update, )) .await?; errors.extend(ninner_errors); @@ -988,9 +1085,11 @@ async fn lock_modules<'c>( } .into(); } - FlowModuleValue::Script { path, hash, .. } if !path.starts_with("hub/") => { + FlowModuleValue::Script { path, hash, .. } + if !path.starts_with("hub/") && !skip_flow_update => + { sqlx::query!( - "INSERT INTO flow_workspace_runnables (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING", + "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING", job_path, path, hash.map(|h| h.0), @@ -999,12 +1098,12 @@ async fn lock_modules<'c>( .execute(&mut *tx) .await?; } - FlowModuleValue::Flow { path, .. } => { + FlowModuleValue::Flow { path, .. } if !skip_flow_update => { sqlx::query!( - "INSERT INTO flow_workspace_runnables (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING", + "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING", job_path, path, - job.workspace_id + job.workspace_id, ) .execute(&mut *tx) .await?; @@ -1033,6 +1132,12 @@ async fn lock_modules<'c>( modified_ids.push(e.id.clone()); + remove_dir_all(job_dir).map_err(|e| { + Error::ExecutionErr(format!("Error removing job dir for flow step lock: {e}")) + })?; + create_dir_all(job_dir).map_err(|e| { + Error::ExecutionErr(format!("Error creating job dir for flow step lock: {e}")) + })?; let new_lock = capture_dependency_job( &job.id, &language, @@ -1086,7 +1191,7 @@ async fn lock_modules<'c>( Some(e.id.clone()), ) .await?; - append_logs(&job.id, &job.workspace_id, logs, db).await; + append_logs(&job.id, &job.workspace_id, logs, &db.into()).await; } if language == ScriptLang::Bun || language == ScriptLang::Bunnative { @@ -1402,7 +1507,7 @@ fn skip_creating_new_lock(language: &ScriptLang, content: &str) -> bool { #[async_recursion] async fn lock_modules_app( value: Value, - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, @@ -1416,6 +1521,22 @@ async fn lock_modules_app( ) -> Result { match value { Value::Object(mut m) => { + if let (Some(Value::String(ref run_type)), Some(path), Some("runnableByPath")) = ( + m.get("runType"), + m.get("path").and_then(|s| s.as_str()), + m.get("type").and_then(|s| s.as_str()), + ) { + // No script_hash because apps don't supports script version locks yet + sqlx::query!( + "INSERT INTO workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING", + job_path, + path, + run_type == "flow", + job.workspace_id + ) + .execute(db) + .await?; + } if m.contains_key("inlineScript") { let v = m.get_mut("inlineScript").unwrap(); if let Some(v) = v.as_object_mut() { @@ -1454,7 +1575,7 @@ async fn lock_modules_app( worker_dir, base_internal_url, token, - &format!("{}/app", job.script_path()), + &format!("{}/app", job.runnable_path()), false, None, occupancy_metrics, @@ -1462,7 +1583,7 @@ async fn lock_modules_app( .await; match new_lock { Ok(new_lock) => { - append_logs(&job.id, &job.workspace_id, logs, db).await; + append_logs(&job.id, &job.workspace_id, logs, &db.into()).await; let anns = windmill_common::worker::TypeScriptAnnotations::parse( &content, @@ -1550,7 +1671,7 @@ async fn lock_modules_app( } pub async fn handle_app_dependency_job( - job: &QueuedJob, + job: &MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, @@ -1561,17 +1682,26 @@ pub async fn handle_app_dependency_job( token: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result<()> { - let job_path = job.script_path.clone().ok_or_else(|| { + let job_path = job.runnable_path.clone().ok_or_else(|| { error::Error::internal_err( "Cannot resolve app dependencies for app without path".to_string(), ) })?; let id = job - .script_hash + .runnable_id .clone() .ok_or_else(|| Error::internal_err("App Dependency requires script hash".to_owned()))? .0; + + sqlx::query!( + "DELETE FROM workspace_runnable_dependencies WHERE app_path = $1 AND workspace_id = $2", + job_path, + job.workspace_id + ) + .execute(db) + .await?; + let record = sqlx::query!("SELECT app_id, value FROM app_version WHERE id = $1", id) .fetch_optional(db) .await? @@ -1632,7 +1762,7 @@ pub async fn handle_app_dependency_job( get_deployment_msg_and_parent_path_from_args(job.args.clone()); if let Err(e) = handle_deployment_metadata( - &job.email, + &job.permissioned_as_email, &job.created_by, &db, &job.workspace_id, @@ -1671,6 +1801,90 @@ pub async fn handle_app_dependency_job( } } +// async fn upload_raw_app( +// app_value: &RawAppValue, +// job: &QueuedJob, +// mem_peak: &mut i32, +// canceled_by: &mut Option, +// job_dir: &str, +// db: &sqlx::Pool, +// worker_name: &str, +// occupancy_metrics: &mut Option<&mut OccupancyMetrics>, +// version: i64, +// ) -> Result<()> { +// let mut entrypoint = "index.ts"; +// for file in app_value.files.iter() { +// if file.0 == "/index.tsx" { +// entrypoint = "index.tsx"; +// } else if file.0 == "/index.js" { +// entrypoint = "index.js"; +// } +// write_file(&job_dir, file.0, &file.1)?; +// } +// let common_bun_proc_envs: HashMap = get_common_bun_proc_envs(None).await; + +// install_bun_lockfile( +// mem_peak, +// canceled_by, +// &job.id, +// &job.workspace_id, +// Some(db), +// job_dir, +// worker_name, +// common_bun_proc_envs, +// false, +// occupancy_metrics, +// ) +// .await?; +// let mut cmd = tokio::process::Command::new("esbuild"); +// let mut args = "--bundle --minify --outdir=dist/" +// .split(' ') +// .collect::>(); +// args.push(entrypoint); +// cmd.current_dir(job_dir) +// .env_clear() +// .args(args) +// .stdout(Stdio::piped()) +// .stderr(Stdio::piped()); +// let child = start_child_process(cmd, "esbuild").await?; + +// crate::handle_child::handle_child( +// &job.id, +// db, +// mem_peak, +// canceled_by, +// child, +// false, +// worker_name, +// &job.workspace_id, +// "esbuild", +// Some(30), +// false, +// occupancy_metrics, +// ) +// .await?; +// let output_dir = format!("{}/dist", job_dir); +// let target_dir = format!("/home/rfiszel/wmill/{}/{}", job.workspace_id, version); + +// tokio::fs::create_dir_all(&target_dir).await?; + +// tracing::info!("Copying files from {} to {}", output_dir, target_dir); + +// let index_ts = format!("{}/index.js", output_dir); +// let index_css = format!("{}/index.css", output_dir); + +// if tokio::fs::metadata(&index_ts).await.is_ok() { +// tokio::fs::copy(&index_ts, format!("{}/index.js", target_dir)).await?; +// } + +// if tokio::fs::metadata(&index_css).await.is_ok() { +// tokio::fs::copy(&index_css, format!("{}/index.css", target_dir)).await?; +// } +// // let file_path = format!("/home/rfiszel/wmill/{}/{}", job.workspace_id, version); + +// Ok(()) +// } + #[cfg(feature = "python")] async fn python_dep( reqs: String, @@ -1683,54 +1897,43 @@ async fn python_dep( w_id: &str, worker_dir: &str, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, - annotated_pyv_numeric: Option, + py_version: crate::PyV, annotations: PythonAnnotations, ) -> std::result::Result { + use crate::python_executor::split_requirements; + create_dependencies_dir(job_dir).await; - /* - Unlike `handle_python_deps` which we use for running scripts (deployed and drafts) - This one used specifically for deploying scripts - So we can get final_version right away and include in lockfile - And the precendence is following: - - 1. Annotation version - 2. Instance version - 3. Latest Stable - */ - - let final_version = annotated_pyv_numeric - .and_then(|pyv| PyVersion::from_numeric(pyv)) - .unwrap_or(PyVersion::from_instance_version(job_id, w_id, db).await); - let req: std::result::Result = uv_pip_compile( job_id, &reqs, mem_peak, canceled_by, job_dir, - db, + &db.into(), worker_name, w_id, occupancy_metrics, - final_version, + py_version, annotations.no_cache, ) .await; // install the dependencies to pre-fill the cache if let Ok(req) = req.as_ref() { let r = handle_python_reqs( - req.split("\n").filter(|x| !x.starts_with("--")).collect(), + split_requirements(req), + // req.split("\n").filter(|x| !x.starts_with("--")).collect(), job_id, w_id, mem_peak, canceled_by, - db, + &Connection::Sql(db.clone()), worker_name, job_dir, worker_dir, occupancy_metrics, - final_version, + // final_version, + crate::PyVAlias::default().into(), ) .await; @@ -1744,6 +1947,124 @@ async fn python_dep( req } +#[cfg(feature = "python")] +async fn ansible_dep( + reqs: AnsibleRequirements, + job_id: &Uuid, + mem_peak: &mut i32, + canceled_by: &mut Option, + job_dir: &str, + db: &sqlx::Pool, + worker_name: &str, + w_id: &str, + worker_dir: &str, + occupancy_metrics: &mut OccupancyMetrics, + token: &str, + base_internal_url: &str, +) -> std::result::Result { + use windmill_parser_yaml::add_versions_to_requirements_yaml; + + use crate::ansible_executor::{ + create_ansible_cfg, get_collection_locks, get_git_ssh_cmd, get_role_locks, + install_galaxy_collections, + }; + use windmill_common::client::AuthedClient; + + let python_lockfile = python_dep( + reqs.python_reqs.join("\n").to_string(), + job_id, + mem_peak, + canceled_by, + job_dir, + db, + worker_name, + w_id, + worker_dir, + &mut Some(occupancy_metrics), + crate::PyV::gravitational_version(job_id, w_id, Some(db.clone().into())).await, + PythonAnnotations::default(), + ) + .await?; + + let conn = &Connection::Sql(db.clone()); + + let authed_client = AuthedClient::new( + base_internal_url.to_string(), + w_id.to_string(), + token.to_string(), + None, + ); + + let git_ssh_cmd = get_git_ssh_cmd(&reqs, job_dir, &authed_client).await?; + + let git_repos = get_git_repos_lock( + &reqs.git_repos, + job_dir, + job_id, + worker_name, + conn, + mem_peak, + canceled_by, + w_id, + occupancy_metrics, + &git_ssh_cmd, + ) + .await?; + + let ansible_lockfile; + + create_ansible_cfg(Some(&reqs), job_dir, false)?; + + if let Some(collections) = reqs.roles_and_collections.as_ref() { + install_galaxy_collections( + collections, + job_dir, + job_id, + worker_name, + w_id, + mem_peak, + canceled_by, + conn, + occupancy_metrics, + &git_ssh_cmd, + ) + .await?; + + let (collection_versions, logs1) = get_collection_locks(job_dir).await?; + + let (role_versions, logs2) = if collections.contains("roles:") { + get_role_locks(job_dir).await? + } else { + (HashMap::new(), String::new()) + }; + + let (reqs_yaml, logs3) = + add_versions_to_requirements_yaml(&collections, &role_versions, &collection_versions)?; + + let logs = format!("\n{logs1}\n{logs2}\n{logs3}\n"); + + append_logs(job_id, w_id, &logs, conn).await; + + ansible_lockfile = AnsibleDependencyLocks { + python_lockfile, + git_repos, + collections_and_roles: reqs_yaml, + collections_and_roles_logs: logs, + }; + } else { + ansible_lockfile = AnsibleDependencyLocks { + python_lockfile, + git_repos, + collections_and_roles: String::new(), + collections_and_roles_logs: String::new(), + }; + } + + serde_json::to_string(&ansible_lockfile).map_err(|e| e.into()) +} + +pub const LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT: &str = "# from requirements.txt"; + async fn capture_dependency_job( job_id: &Uuid, job_language: &ScriptLang, @@ -1770,30 +2091,49 @@ async fn capture_dependency_job( )); #[cfg(feature = "python")] { - let anns = PythonAnnotations::parse(job_raw_code); - let mut annotated_pyv_numeric = None; - - let reqs = if raw_deps { + // Manually assigned version from requirements.txt + // let assigned_py_version; + let (reqs, py_version) = if raw_deps { // `wmill script generate-metadata` // should also respect annotated pyversion // can be annotated in script itself // or in requirements.txt if present - annotated_pyv_numeric = - PyVersion::from_py_annotations(anns).map(|v| v.to_numeric()); - job_raw_code.to_string() - } else { - let mut already_visited = vec![]; - windmill_parser_py_imports::parse_python_imports( - job_raw_code, - &w_id, - script_path, - &db, - &mut already_visited, - &mut annotated_pyv_numeric, + ( + job_raw_code.to_owned(), + match crate::PyV::try_parse_from_requirements(&split_requirements( + job_raw_code, + )) { + Some(pyv) => pyv, + None => crate::PyV::gravitational_version(job_id, w_id, None).await, + }, + ) + } else { + let mut version_specifiers = vec![]; + let PythonAnnotations { py_select_latest, .. } = + PythonAnnotations::parse(job_raw_code); + ( + windmill_parser_py_imports::parse_python_imports( + job_raw_code, + &w_id, + script_path, + &db, + &mut version_specifiers, + ) + .await? + .0 + .join("\n"), + crate::PyV::resolve( + version_specifiers, + job_id, + w_id, + py_select_latest, + Some(db.clone().into()), + None, + None, + ) + .await?, ) - .await? - .join("\n") }; python_dep( @@ -1807,10 +2147,17 @@ async fn capture_dependency_job( w_id, worker_dir, &mut Some(occupancy_metrics), - annotated_pyv_numeric, - anns, + py_version, + PythonAnnotations::parse(job_raw_code), ) .await + .map(|res| { + if raw_deps { + format!("{}\n{}", LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT, res) + } else { + res + } + }) } } ScriptLang::Ansible => { @@ -1827,10 +2174,9 @@ async fn capture_dependency_job( )); } let (_logs, reqs, _) = windmill_parser_yaml::parse_ansible_reqs(job_raw_code)?; - let reqs = reqs.map(|r| r.python_reqs.join("\n")).unwrap_or_default(); - python_dep( - reqs, + ansible_dep( + reqs.unwrap_or_default(), job_id, mem_peak, canceled_by, @@ -1839,29 +2185,25 @@ async fn capture_dependency_job( worker_name, w_id, worker_dir, - &mut Some(occupancy_metrics), - None, - PythonAnnotations::default(), + occupancy_metrics, + token, + base_internal_url, ) .await } } ScriptLang::Go => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for go".to_string(), - )); - } install_go_dependencies( job_id, job_raw_code, mem_peak, canceled_by, job_dir, - db, + &db.into(), false, false, false, + raw_deps, worker_name, w_id, occupancy_metrics, @@ -1880,7 +2222,7 @@ async fn capture_dependency_job( mem_peak, canceled_by, job_dir, - Some(db), + Some(&db.into()), w_id, worker_name, base_internal_url, @@ -1900,7 +2242,7 @@ async fn capture_dependency_job( canceled_by, job_id, w_id, - Some(db), + Some(&db.into()), token, script_path, job_dir, @@ -1923,7 +2265,7 @@ async fn capture_dependency_job( script_path, job_id, w_id, - Some(db.clone()), + Some(&db), &job_dir, base_internal_url, worker_name, @@ -1960,7 +2302,7 @@ async fn capture_dependency_job( canceled_by, job_id, w_id, - db, + &Connection::Sql(db.clone()), job_dir, worker_name, reqs, @@ -1989,7 +2331,7 @@ async fn capture_dependency_job( mem_peak, canceled_by, job_dir, - db, + &Connection::Sql(db.clone()), worker_name, w_id, occupancy_metrics, @@ -2012,22 +2354,31 @@ async fn capture_dependency_job( mem_peak, canceled_by, job_dir, - db, + &Connection::Sql(db.clone()), worker_name, w_id, occupancy_metrics, ) .await } - ScriptLang::Postgresql => Ok("".to_owned()), - ScriptLang::Mysql => Ok("".to_owned()), - ScriptLang::Bigquery => Ok("".to_owned()), - ScriptLang::Snowflake => Ok("".to_owned()), - ScriptLang::Mssql => Ok("".to_owned()), - ScriptLang::Graphql => Ok("".to_owned()), - ScriptLang::OracleDB => Ok("".to_owned()), - ScriptLang::Bash => Ok("".to_owned()), - ScriptLang::Powershell => Ok("".to_owned()), - ScriptLang::Nativets => Ok("".to_owned()), + #[cfg(feature = "java")] + ScriptLang::Java => { + if raw_deps { + return Err(Error::ExecutionErr( + "Raw dependencies not supported for Java".to_string(), + )); + } + + resolve( + job_id, + job_raw_code, + job_dir, + &Connection::Sql(db.clone()), + w_id, + ) + .await + } + // for related places search: ADD_NEW_LANG + _ => Ok("".to_owned()), } } diff --git a/backend/windmill-worker/src/worker_utils.rs b/backend/windmill-worker/src/worker_utils.rs new file mode 100644 index 0000000000..4da318d37b --- /dev/null +++ b/backend/windmill-worker/src/worker_utils.rs @@ -0,0 +1,395 @@ +use backon::{BackoffBuilder, ConstantBuilder, Retryable}; +use tracing::Instrument; +use uuid::Uuid; +use windmill_common::{ + agent_workers::{PingJobStatus, PingJobStatusResponse}, + cache, + worker::{ + get_memory, get_vcpus, get_windmill_memory_usage, get_worker_memory_usage, + insert_ping_query, update_job_ping_query, update_worker_ping_from_job_query, + update_worker_ping_main_loop_query, Connection, Ping, PingType, WORKER_CONFIG, + WORKER_GROUP, + }, + KillpillSender, DB, +}; + +use crate::{ + agent_workers::UPDATE_PING_URL, + common::{OccupancyMetrics, OccupancyResult}, +}; + +pub(crate) async fn update_worker_ping_full( + conn: &Connection, + read_cgroups: bool, + jobs_executed: i32, + worker_name: &str, + hostname: &str, + occupancy_metrics: &mut OccupancyMetrics, + killpill_tx: &KillpillSender, +) { + let tags = WORKER_CONFIG.read().await.worker_tags.clone(); + + let memory_usage = get_worker_memory_usage(); + let wm_memory_usage = get_windmill_memory_usage(); + + let (vcpus, memory) = if read_cgroups { + (get_vcpus(), get_memory()) + } else { + (None, None) + }; + + let OccupancyResult { + occupancy_rate, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, + } = occupancy_metrics.update_occupancy_metrics(); + + if let Err(e) = (|| { + update_worker_ping_full_inner( + conn, + jobs_executed, + &worker_name, + &tags, + memory_usage, + wm_memory_usage, + vcpus, + memory, + occupancy_rate, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, + ) + }) + .retry( + ConstantBuilder::default() + .with_delay(std::time::Duration::from_secs(2)) + .with_max_times(10) + .build(), + ) + .notify(|err, dur| { + tracing::error!( + worker = %worker_name, hostname = %hostname, + "retrying updating worker ping in {dur:#?}, err: {err:#?}" + ); + }) + .sleep(tokio::time::sleep) + .await + { + tracing::error!( + worker = %worker_name, hostname = %hostname, + "failed to update worker ping, exiting: {}", e); + killpill_tx.send(); + } + tracing::info!( + worker = %worker_name, hostname = %hostname, + "ping update, memory: container={}MB, windmill={}MB", + memory_usage.unwrap_or_default() / (1024 * 1024), + wm_memory_usage.unwrap_or_default() / (1024 * 1024) + ); +} + +async fn update_worker_ping_full_inner( + conn: &Connection, + jobs_executed: i32, + worker_name: &str, + tags: &[String], + memory_usage: Option, + wm_memory_usage: Option, + vcpus: Option, + memory: Option, + occupancy_rate: f32, + occupancy_rate_15s: Option, + occupancy_rate_5m: Option, + occupancy_rate_30m: Option, +) -> anyhow::Result<()> { + match conn { + Connection::Sql(db) => { + update_worker_ping_main_loop_query( + worker_name, + tags, + vcpus, + memory, + Some(jobs_executed), + Some(occupancy_rate), + memory_usage, + wm_memory_usage, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, + db, + ) + .await?; + } + Connection::Http(client) => { + client + .post::<_, ()>( + UPDATE_PING_URL, + None, + &Ping { + last_job_executed: None, + last_job_workspace_id: None, + worker_instance: None, + ip: None, + tags: Some(tags.to_vec()), + dw: None, + jobs_executed: Some(jobs_executed), + occupancy_rate: Some(occupancy_rate), + occupancy_rate_15s: Some(occupancy_rate_15s.unwrap_or(0.0)), + occupancy_rate_5m: Some(occupancy_rate_5m.unwrap_or(0.0)), + occupancy_rate_30m: Some(occupancy_rate_30m.unwrap_or(0.0)), + version: None, + vcpus: vcpus, + memory: memory, + memory_usage: get_worker_memory_usage(), + wm_memory_usage: get_windmill_memory_usage(), + ping_type: PingType::MainLoop, + }, + ) + .await?; + } + } + Ok(()) +} + +pub async fn insert_ping( + worker_instance: &str, + worker_name: &str, + ip: &str, + db: &Connection, +) -> anyhow::Result<()> { + let (tags, dw) = { + let wc = WORKER_CONFIG.read().await.clone(); + ( + wc.worker_tags, + wc.dedicated_worker + .as_ref() + .map(|x| format!("{}:{}", x.workspace_id, x.path)), + ) + }; + + let vcpus = get_vcpus(); + let memory = get_memory(); + + match db { + Connection::Sql(db) => { + insert_ping_query( + worker_instance, + worker_name, + WORKER_GROUP.as_str(), + ip, + tags.as_slice(), + dw, + windmill_common::utils::GIT_VERSION, + vcpus, + memory, + db, + ) + .await?; + } + Connection::Http(client) => { + client + .post::<_, ()>( + UPDATE_PING_URL, + None, + &Ping { + last_job_executed: None, + last_job_workspace_id: None, + worker_instance: Some(worker_instance.to_string()), + ip: Some(ip.to_string()), + tags: Some(tags.to_vec()), + dw: dw, + jobs_executed: None, + occupancy_rate: None, + occupancy_rate_15s: None, + occupancy_rate_5m: None, + occupancy_rate_30m: None, + version: Some(windmill_common::utils::GIT_VERSION.to_string()), + vcpus: vcpus, + memory: memory, + memory_usage: get_worker_memory_usage(), + wm_memory_usage: get_windmill_memory_usage(), + ping_type: PingType::Initial, + }, + ) + .await?; + } + } + Ok(()) +} + +pub async fn update_worker_ping_from_job( + conn: &Connection, + job_id: &Uuid, + w_id: &str, + worker_name: &str, + memory_usage: Option, + wm_memory_usage: Option, + occupancy: Option, +) -> anyhow::Result<()> { + let occupancy_rate = occupancy.as_ref().map(|x| x.occupancy_rate); + let occupancy_rate_15s = occupancy.as_ref().and_then(|x| x.occupancy_rate_15s); + let occupancy_rate_5m = occupancy.as_ref().and_then(|x| x.occupancy_rate_5m); + let occupancy_rate_30m = occupancy.as_ref().and_then(|x| x.occupancy_rate_30m); + match conn.clone() { + Connection::Sql(ref db) => { + update_worker_ping_from_job_query( + job_id, + w_id, + worker_name, + memory_usage, + wm_memory_usage, + occupancy_rate, + occupancy_rate_15s, + occupancy_rate_5m, + occupancy_rate_30m, + db, + ) + .await?; + } + Connection::Http(client) => { + client + .post::( + UPDATE_PING_URL, + None, + &Ping { + last_job_executed: Some(job_id.clone()), + last_job_workspace_id: Some(w_id.to_string()), + ping_type: PingType::Job, + worker_instance: None, + ip: None, + tags: None, + dw: None, + version: None, + vcpus: None, + memory: None, + memory_usage: memory_usage, + wm_memory_usage: wm_memory_usage, + jobs_executed: None, + occupancy_rate: occupancy_rate, + occupancy_rate_15s: occupancy_rate_15s, + occupancy_rate_5m: occupancy_rate_5m, + occupancy_rate_30m: occupancy_rate_30m, + }, + ) + .await?; + } + } + Ok(()) +} + +pub async fn ping_job_status( + conn: &Connection, + job_id: &Uuid, + mem_peak: Option, + current_mem: Option, +) -> anyhow::Result { + match conn { + Connection::Sql(ref db) => update_job_ping_query(job_id, db, mem_peak).await, + Connection::Http(client) => { + client + .post( + &format!("/api/agent_workers/ping_job_status/{}", job_id), + None, + &PingJobStatus { mem_peak, current_mem }, + ) + .await + } + } +} + +pub(crate) async fn queue_vacuum(conn: &Connection, worker_name: &str, hostname: &str) { + match conn { + Connection::Sql(db) => { + let db2 = db.clone(); + let current_span = tracing::Span::current(); + let worker_name = worker_name.to_string(); + let hostname = hostname.to_string(); + tokio::task::spawn( + (async move { + tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue"); + if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status") + .execute(&db2) + .await + { + tracing::error!(worker = %worker_name, hostname = %hostname, "failed to vacuum queue: {}", e); + } + tracing::info!(worker = %worker_name, hostname = %hostname, "vacuumed queue"); + }) + .instrument(current_span), + ); + } + Connection::Http(_) => { + // do nothing in http mode + () + } + } +} + +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct TagAndConcurrencyKey { + pub tag: Option, + pub concurrency_key: Option, + pub concurrent_limit: Option, + pub concurrency_time_window_s: Option, + pub version: Option, +} + +pub async fn get_tag_and_concurrency(job_id: &Uuid, db: &DB) -> Option { + let r = sqlx::query_as!( + TagAndConcurrencyKey, + " + WITH j AS ( + SELECT + raw_flow->>'concurrency_key' as concurrency_key, + raw_flow->>'concurrency_time_window_s' as concurrency_time_window_s, + raw_flow->>'concurrency_limit' as concurrent_limit, + runnable_path, + runnable_id as version FROM v2_job + WHERE id = $1 + ) + SELECT tag, j.concurrency_key, j.concurrency_time_window_s::int, j.concurrent_limit::int, j.version + FROM flow, j + WHERE path = j.runnable_path + ", + job_id + ) + .fetch_optional(db) + .await + .ok() + .flatten(); + if let Some(tag_and_concurrency_key) = r { + if tag_and_concurrency_key.concurrency_key.as_ref().is_some() + || tag_and_concurrency_key.version.as_ref().is_none() + { + return Some(tag_and_concurrency_key); + } else { + let version = tag_and_concurrency_key.version.unwrap(); + + let r = cache::flow::fetch_version_lite(db, version).await; + let flow = match r { + Ok(data) => Ok(data), + Err(_) => cache::flow::fetch_version(db, version).await, + }; + let flow_value = flow.map(|f| f.value().clone()).ok(); + let concurrency_key = flow_value + .as_ref() + .map(|fv| fv.concurrency_key.clone()) + .flatten(); + let concurrent_limit = flow_value.as_ref().map(|fv| fv.concurrent_limit).flatten(); + let concurrent_time_window_s = flow_value + .as_ref() + .map(|fv| fv.concurrency_time_window_s) + .flatten(); + Some(TagAndConcurrencyKey { + tag: tag_and_concurrency_key.tag, + concurrency_key, + concurrent_limit, + concurrency_time_window_s: concurrent_time_window_s, + version: None, + }) + } + } else { + None + } +} diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 21768c4827..b965a83b6c 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.475.0"; +export const VERSION = "v1.501.4"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/.gitignore b/cli/.gitignore index 409ee189ef..d0b1f2c514 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1 +1,2 @@ npm/ +gen/ \ No newline at end of file diff --git a/cli/bootstrap/script_bootstrap.ts b/cli/bootstrap/script_bootstrap.ts index 79ca7d92b7..e9e8a12db5 100644 --- a/cli/bootstrap/script_bootstrap.ts +++ b/cli/bootstrap/script_bootstrap.ts @@ -76,6 +76,7 @@ func main() (interface{}, error) { bash: `echo "Hello world" `, + duckdb: `SELECT 'Hello world' AS message`, oracledb: `SELECT 'Hello world' AS message`, powershell: `Write-Output "Hello world"`, @@ -94,6 +95,11 @@ function main() { } } `, + nu: ` +def main [] { + print "Hello World" +} + `, rust: `fn main() -> Result<(), String> { println!("Hello World"); @@ -114,4 +120,12 @@ inventory: debug: msg: "Hello, world!" `, + java: ` +public class Main { + public static void main() { + System.out.println("Hello World"); + } +} +`, + // for related places search: ADD_NEW_LANG }; diff --git a/cli/build.sh b/cli/build.sh index bf00b99a57..e228750b51 100755 --- a/cli/build.sh +++ b/cli/build.sh @@ -1,3 +1,4 @@ #!/bin/bash +# Note for mac OS users: you need to install gnu-sed with `brew install gnu-sed` and use `gsed` instead of `sed`. ./gen_wm_client.sh deno run -A dnt.ts diff --git a/cli/codebase.ts b/cli/codebase.ts index 844a51f52d..8af46ff284 100644 --- a/cli/codebase.ts +++ b/cli/codebase.ts @@ -2,25 +2,31 @@ import { Codebase, SyncOptions } from "./conf.ts"; import { log } from "./deps.ts"; import { digestDir } from "./utils.ts"; -export type SyncCodebase = Codebase & { digest: string }; -export async function listSyncCodebases( +export type SyncCodebase = Codebase & { getDigest: () => Promise }; +export function listSyncCodebases( options: SyncOptions -): Promise { +): SyncCodebase[] { const res: SyncCodebase[] = []; const nb_codebase = options?.codebases?.length ?? 0; if (nb_codebase > 0) { - log.info(`Found ${nb_codebase} codebases:`); + log.info(`Found ${nb_codebase} codebases: ${options?.codebases?.map((c) => c.relative_path).join(", ")}`); } for (const codebase of options?.codebases ?? []) { - let digest = await digestDir( - codebase.relative_path, - JSON.stringify(codebase) - ); - if (Array.isArray(codebase.assets) && codebase.assets.length > 0) { - digest += ".tar"; - } - log.info(`Codebase ${codebase.relative_path}, digest: ${digest}`); - res.push({ ...codebase, digest }); + let _digest: string | undefined = undefined; + const getDigest: () => Promise = async () => { + if (_digest == undefined) { + _digest = await digestDir( + codebase.relative_path, + JSON.stringify(codebase) + ); + if (Array.isArray(codebase.assets) && codebase.assets.length > 0) { + _digest += ".tar"; + } + log.info(`Codebase ${codebase.relative_path}, digest: ${_digest}`); + } + return _digest; + }; + res.push({ ...codebase, getDigest }); } return res; diff --git a/cli/conf.ts b/cli/conf.ts index a8598d5d0b..e2a4c1cdbf 100644 --- a/cli/conf.ts +++ b/cli/conf.ts @@ -4,12 +4,14 @@ export interface SyncOptions { stateful?: boolean; raw?: boolean; yes?: boolean; + dryRun?: boolean; skipPull?: boolean; failConflicts?: boolean; plainSecrets?: boolean; json?: boolean; skipVariables?: boolean; skipResources?: boolean; + skipResourceTypes?: boolean; skipSecrets?: boolean; includeSchedules?: boolean; includeTriggers?: boolean; @@ -61,5 +63,5 @@ export async function mergeConfigWithConfigFile( opts: T ): Promise { const configFile = await readConfigFile(); - return Object.assign(configFile, opts); + return Object.assign(configFile ?? {}, opts); } diff --git a/cli/deno.json b/cli/deno.json index 6276c18bbc..fae7e8f7dd 100644 --- a/cli/deno.json +++ b/cli/deno.json @@ -15,4 +15,4 @@ "@std/yaml": "jsr:@std/yaml@^1.0.5", "@types/diff": "npm:@types/diff@^5.2.2" } -} +} \ No newline at end of file diff --git a/cli/deno.lock b/cli/deno.lock index 4d4111261c..a0a4c6ce2e 100644 --- a/cli/deno.lock +++ b/cli/deno.lock @@ -1,2319 +1,2196 @@ { - "version": "3", - "packages": { - "specifiers": { - "jsr:@david/code-block-writer@^13.0.2": "jsr:@david/code-block-writer@13.0.2", - "jsr:@deno/cache-dir@^0.10.3": "jsr:@deno/cache-dir@0.10.3", - "jsr:@deno/dnt@0.41.3": "jsr:@deno/dnt@0.41.3", - "jsr:@deno/dnt@^0.41.3": "jsr:@deno/dnt@0.41.3", - "jsr:@std/assert@1.0.0-rc.2": "jsr:@std/assert@1.0.0-rc.2", - "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", - "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", - "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", - "jsr:@std/bytes@^1.0.2": "jsr:@std/bytes@1.0.2", - "jsr:@std/cli@1.0.0-rc.2": "jsr:@std/cli@1.0.0-rc.2", - "jsr:@std/encoding": "jsr:@std/encoding@1.0.4", - "jsr:@std/encoding@1.0.0-rc.2": "jsr:@std/encoding@1.0.0-rc.2", - "jsr:@std/encoding@^1.0.4": "jsr:@std/encoding@1.0.4", - "jsr:@std/fmt@1": "jsr:@std/fmt@1.0.2", - "jsr:@std/fmt@^0.223": "jsr:@std/fmt@0.223.0", - "jsr:@std/fmt@^1.0.2": "jsr:@std/fmt@1.0.2", - "jsr:@std/fmt@~0.225.4": "jsr:@std/fmt@0.225.6", - "jsr:@std/fs": "jsr:@std/fs@1.0.3", - "jsr:@std/fs@1": "jsr:@std/fs@1.0.3", - "jsr:@std/fs@^0.223": "jsr:@std/fs@0.223.0", - "jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3", - "jsr:@std/fs@^1.0.3": "jsr:@std/fs@1.0.3", - "jsr:@std/io": "jsr:@std/io@0.224.7", - "jsr:@std/io@^0.223": "jsr:@std/io@0.223.0", - "jsr:@std/io@^0.224.7": "jsr:@std/io@0.224.7", - "jsr:@std/io@~0.224.2": "jsr:@std/io@0.224.7", - "jsr:@std/log": "jsr:@std/log@0.224.7", - "jsr:@std/log@^0.224.7": "jsr:@std/log@0.224.7", - "jsr:@std/net": "jsr:@std/net@1.0.2", - "jsr:@std/net@^1.0.2": "jsr:@std/net@1.0.2", - "jsr:@std/path": "jsr:@std/path@1.0.4", - "jsr:@std/path@1": "jsr:@std/path@1.0.4", - "jsr:@std/path@1.0.0-rc.1": "jsr:@std/path@1.0.0-rc.1", - "jsr:@std/path@1.0.0-rc.2": "jsr:@std/path@1.0.0-rc.2", - "jsr:@std/path@^0.223": "jsr:@std/path@0.223.0", - "jsr:@std/path@^0.225.2": "jsr:@std/path@0.225.2", - "jsr:@std/path@^1.0.4": "jsr:@std/path@1.0.4", - "jsr:@std/streams@^1.0.4": "jsr:@std/streams@1.0.4", - "jsr:@std/text@1.0.0-rc.1": "jsr:@std/text@1.0.0-rc.1", - "jsr:@std/yaml": "jsr:@std/yaml@1.0.5", - "jsr:@std/yaml@^1.0.5": "jsr:@std/yaml@1.0.5", - "jsr:@ts-morph/bootstrap@^0.24.0": "jsr:@ts-morph/bootstrap@0.24.0", - "jsr:@ts-morph/common@^0.24.0": "jsr:@ts-morph/common@0.24.0", - "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-ansi@^1.0.0-rc.5": "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-command@^1.0.0-rc.5": "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-flags@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-flags@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-keycode@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-keycode@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6": "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6", - "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5": "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5": "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5": "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5", - "npm:@ayonli/jsext": "npm:@ayonli/jsext@0.9.58", - "npm:@oakserver/oak@12": "npm:@oakserver/oak@12.6.2", - "npm:@types/diff@^5.2.2": "npm:@types/diff@5.2.2", - "npm:@types/node": "npm:@types/node@18.16.19", - "npm:diff": "npm:diff@5.2.0", - "npm:es-main": "npm:es-main@1.3.0", - "npm:esbuild": "npm:esbuild@0.23.0", - "npm:eslint-plugin-import@^2.30.0": "npm:eslint-plugin-import@2.30.0_eslint@8.57.1", - "npm:eslint@^9.10.0": "npm:eslint@9.10.0", - "npm:express": "npm:express@4.19.2", - "npm:get-port": "npm:get-port@7.1.0", - "npm:get-port@7.1.0": "npm:get-port@7.1.0", - "npm:gitignore-parser": "npm:gitignore-parser@0.0.2", - "npm:jszip@3.7.1": "npm:jszip@3.7.1", - "npm:minimatch": "npm:minimatch@10.0.1", - "npm:open": "npm:open@10.1.0", - "npm:windmill-client@1.364.0": "npm:windmill-client@1.364.0", - "npm:ws": "npm:ws@8.18.0" + "version": "4", + "specifiers": { + "jsr:@david/code-block-writer@^13.0.2": "13.0.2", + "jsr:@deno/cache-dir@~0.10.3": "0.10.3", + "jsr:@deno/dnt@0.41.3": "0.41.3", + "jsr:@deno/dnt@~0.41.3": "0.41.3", + "jsr:@deno/graph@~0.73.1": "0.73.1", + "jsr:@std/assert@0.223": "0.223.0", + "jsr:@std/assert@0.226": "0.226.0", + "jsr:@std/assert@1.0.0-rc.2": "1.0.0-rc.2", + "jsr:@std/bytes@0.223": "0.223.0", + "jsr:@std/bytes@^1.0.2": "1.0.2", + "jsr:@std/cli@1.0.0-rc.2": "1.0.0-rc.2", + "jsr:@std/encoding@*": "1.0.4", + "jsr:@std/encoding@1.0.0-rc.2": "1.0.0-rc.2", + "jsr:@std/encoding@1.0.4": "1.0.4", + "jsr:@std/encoding@^1.0.4": "1.0.4", + "jsr:@std/fmt@0.223": "0.223.0", + "jsr:@std/fmt@1": "1.0.2", + "jsr:@std/fmt@^1.0.2": "1.0.2", + "jsr:@std/fmt@~0.225.4": "0.225.6", + "jsr:@std/fs@*": "1.0.3", + "jsr:@std/fs@0.223": "0.223.0", + "jsr:@std/fs@1": "1.0.3", + "jsr:@std/fs@^1.0.3": "1.0.3", + "jsr:@std/fs@~0.229.3": "0.229.3", + "jsr:@std/io@*": "0.224.7", + "jsr:@std/io@0.223": "0.223.0", + "jsr:@std/io@~0.224.2": "0.224.7", + "jsr:@std/io@~0.224.7": "0.224.7", + "jsr:@std/log@*": "0.224.7", + "jsr:@std/log@~0.224.7": "0.224.7", + "jsr:@std/net@*": "1.0.2", + "jsr:@std/net@^1.0.2": "1.0.2", + "jsr:@std/path@*": "1.0.4", + "jsr:@std/path@0.223": "0.223.0", + "jsr:@std/path@1": "1.0.4", + "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", + "jsr:@std/path@1.0.0-rc.2": "1.0.0-rc.2", + "jsr:@std/path@^1.0.4": "1.0.4", + "jsr:@std/path@~0.225.2": "0.225.2", + "jsr:@std/streams@^1.0.4": "1.0.4", + "jsr:@std/text@1.0.0-rc.1": "1.0.0-rc.1", + "jsr:@std/yaml@*": "1.0.5", + "jsr:@std/yaml@^1.0.5": "1.0.5", + "jsr:@ts-morph/bootstrap@0.24": "0.24.0", + "jsr:@ts-morph/common@0.24": "0.24.0", + "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-ansi@^1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-command@^1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-flags@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-keycode@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6": "1.0.0-rc.6", + "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5": "1.0.0-rc.6", + "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5": "1.0.0-rc.5", + "npm:@ayonli/jsext@*": "0.9.58", + "npm:@oakserver/oak@12": "12.6.2", + "npm:@types/diff@^5.2.2": "5.2.2", + "npm:@types/node@*": "18.16.19", + "npm:diff@*": "5.2.0", + "npm:es-main@*": "1.3.0", + "npm:esbuild@*": "0.23.0", + "npm:eslint-plugin-import@^2.30.0": "2.30.0_eslint@8.57.1", + "npm:eslint@^9.10.0": "9.10.0", + "npm:express@*": "4.19.2", + "npm:get-port@*": "7.1.0", + "npm:get-port@7.1.0": "7.1.0", + "npm:gitignore-parser@*": "0.0.2", + "npm:jszip@3.7.1": "3.7.1", + "npm:minimatch@*": "10.0.1", + "npm:open@*": "10.1.0", + "npm:windmill-client@1.364.0": "1.364.0", + "npm:windmill-parser-wasm-csharp@*": "1.437.1", + "npm:windmill-parser-wasm-go@*": "1.429.0", + "npm:windmill-parser-wasm-php@*": "1.429.0", + "npm:windmill-parser-wasm-py@*": "1.477.1", + "npm:windmill-parser-wasm-regex@*": "1.439.0", + "npm:windmill-parser-wasm-rust@*": "1.429.0", + "npm:windmill-parser-wasm-ts@*": "1.438.2", + "npm:windmill-parser-wasm-yaml@*": "1.429.0", + "npm:ws@*": "8.18.0" + }, + "jsr": { + "@david/code-block-writer@13.0.2": { + "integrity": "14dd3baaafa3a2dea8bf7dfbcddeccaa13e583da2d21d666c01dc6d681cd74ad" }, - "jsr": { - "@david/code-block-writer@13.0.2": { - "integrity": "14dd3baaafa3a2dea8bf7dfbcddeccaa13e583da2d21d666c01dc6d681cd74ad" - }, - "@deno/cache-dir@0.10.3": { - "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", - "dependencies": [ - "jsr:@std/fmt@^0.223", - "jsr:@std/fs@^0.223", - "jsr:@std/io@^0.223", - "jsr:@std/path@^0.223" - ] - }, - "@deno/dnt@0.41.3": { - "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", - "dependencies": [ - "jsr:@david/code-block-writer@^13.0.2", - "jsr:@deno/cache-dir@^0.10.3", - "jsr:@std/fmt@1", - "jsr:@std/fs@1", - "jsr:@std/path@1", - "jsr:@ts-morph/bootstrap@^0.24.0" - ] - }, - "@std/assert@0.223.0": { - "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" - }, - "@std/assert@0.226.0": { - "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" - }, - "@std/assert@1.0.0-rc.2": { - "integrity": "0484eab1d76b55fca1c3beaff485a274e67dd3b9f065edcbe70030dfc0b964d3" - }, - "@std/bytes@0.223.0": { - "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" - }, - "@std/bytes@1.0.2": { - "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" - }, - "@std/cli@1.0.0-rc.2": { - "integrity": "97dfae82b9f0e189768ebfa7a5da53375955b94bad0a1804f8e3b73563b03787" - }, - "@std/encoding@1.0.0-rc.2": { - "integrity": "160d7674a20ebfbccdf610b3801fee91cf6e42d1c106dd46bbaf46e395cd35ef" - }, - "@std/encoding@1.0.4": { - "integrity": "2266cd516b32369e3dc5695717c96bf88343a1f761d6e6187a02a2bbe2af86ae" - }, - "@std/fmt@0.223.0": { - "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" - }, - "@std/fmt@0.225.6": { - "integrity": "aba6aea27f66813cecfd9484e074a9e9845782ab0685c030e453a8a70b37afc8" - }, - "@std/fmt@1.0.2": { - "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" - }, - "@std/fs@0.223.0": { - "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" - }, - "@std/fs@0.229.3": { - "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", - "dependencies": [ - "jsr:@std/path@1.0.0-rc.1" - ] - }, - "@std/fs@1.0.3": { - "integrity": "3cb839b1360b0a42d8b367c3093bfe4071798e6694fa44cf1963e04a8edba4fe", - "dependencies": [ - "jsr:@std/path@^1.0.4" - ] - }, - "@std/io@0.223.0": { - "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", - "dependencies": [ - "jsr:@std/assert@^0.223.0", - "jsr:@std/bytes@^0.223.0" - ] - }, - "@std/io@0.224.7": { - "integrity": "a70848793c44a7c100926571a8c9be68ba85487bfcd4d0540d86deabe1123dc9", - "dependencies": [ - "jsr:@std/bytes@^1.0.2" - ] - }, - "@std/log@0.224.7": { - "integrity": "021941e5cd16de60cb11599c9b36f892aea95987fe66c753922808da27909e18", - "dependencies": [ - "jsr:@std/fmt@^1.0.2", - "jsr:@std/fs@^1.0.3", - "jsr:@std/io@^0.224.7" - ] - }, - "@std/net@1.0.2": { - "integrity": "520c18ddb7f67d3830a1adfef03a155d496fe9683a9cb63bb823b5afb86484dc" - }, - "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", - "dependencies": [ - "jsr:@std/assert@^0.223.0" - ] - }, - "@std/path@0.225.2": { - "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", - "dependencies": [ - "jsr:@std/assert@^0.226.0" - ] - }, - "@std/path@1.0.0-rc.1": { - "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" - }, - "@std/path@1.0.0-rc.2": { - "integrity": "39f20d37a44d1867abac8d91c169359ea6e942237a45a99ee1e091b32b921c7d" - }, - "@std/path@1.0.4": { - "integrity": "48dd5d8389bcfcd619338a01bdf862cb7799933390146a54ae59356a0acc7105" - }, - "@std/streams@1.0.4": { - "integrity": "a1a5b01c74ca1d2dcaacfe1d4bbb91392e765946d82a3471bd95539adc6da83a", - "dependencies": [ - "jsr:@std/bytes@^1.0.2" - ] - }, - "@std/text@1.0.0-rc.1": { - "integrity": "34c722203e87ee12792c8d4a0cd2ee0e001341cbce75b860fc21be19d62232b0" - }, - "@std/yaml@1.0.5": { - "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" - }, - "@ts-morph/bootstrap@0.24.0": { - "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", - "dependencies": [ - "jsr:@ts-morph/common@^0.24.0" - ] - }, - "@ts-morph/common@0.24.0": { - "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", - "dependencies": [ - "jsr:@std/fs@^0.229.3", - "jsr:@std/path@^0.225.2" - ] - }, - "@windmill-labs/cliffy-ansi@1.0.0-rc.5": { - "integrity": "1109cbcb0c415b57779352f708f5969b8c645f56bc555cbafc6ea5e0c6a360a4", - "dependencies": [ - "jsr:@std/encoding@1.0.0-rc.2", - "jsr:@std/fmt@~0.225.4", - "jsr:@std/io@~0.224.2", - "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5" - ] - }, - "@windmill-labs/cliffy-command@1.0.0-rc.5": { - "integrity": "3eaa9def5f5afa1028f4a60ee4d9065ccc5f194d032824365c6ebcb9f46db66e", - "dependencies": [ - "jsr:@std/fmt@~0.225.4", - "jsr:@std/text@1.0.0-rc.1", - "jsr:@windmill-labs/cliffy-flags@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5" - ] - }, - "@windmill-labs/cliffy-flags@1.0.0-rc.5": { - "integrity": "0e4b5b53a02295f8bf27d93b3bcca5d5d001a0286aea17404e6ef6347f69363c", - "dependencies": [ - "jsr:@std/text@1.0.0-rc.1" - ] - }, - "@windmill-labs/cliffy-internal@1.0.0-rc.5": { - "integrity": "876b989ad2d1b739cc4a4f1386dbb80f819d2851ad583c1f486fc6ebe9beadf6" - }, - "@windmill-labs/cliffy-keycode@1.0.0-rc.5": { - "integrity": "2bc1b1af363528e38ed47bce525f417cab278b829a423353ffd589622f5a746e" - }, - "@windmill-labs/cliffy-prompt@1.0.0-rc.5": { - "integrity": "329a097911f219b15ea643ae83b6b360a11df7fc4cafdac1bf6869259475033a", - "dependencies": [ - "jsr:@std/assert@1.0.0-rc.2", - "jsr:@std/fmt@~0.225.4", - "jsr:@std/io@~0.224.2", - "jsr:@std/path@1.0.0-rc.2", - "jsr:@std/text@1.0.0-rc.1", - "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-keycode@1.0.0-rc.5" - ] - }, - "@windmill-labs/cliffy-prompt@1.0.0-rc.6": { - "integrity": "ffe09bee0e1e07bc12b2147be509d10b47eb6a36cbfea80fc206b4ae86693205", - "dependencies": [ - "jsr:@std/assert@1.0.0-rc.2", - "jsr:@std/fmt@~0.225.4", - "jsr:@std/io@~0.224.2", - "jsr:@std/path@1.0.0-rc.2", - "jsr:@std/text@1.0.0-rc.1", - "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-internal@1.0.0-rc.5", - "jsr:@windmill-labs/cliffy-keycode@1.0.0-rc.5" - ] - }, - "@windmill-labs/cliffy-table@1.0.0-rc.5": { - "integrity": "5f26cb6ccbc2fbf1b1f79f9062d166e32e11d34398c0deac7e6f9d8378970546", - "dependencies": [ - "jsr:@std/cli@1.0.0-rc.2", - "jsr:@std/fmt@~0.225.4" - ] - } + "@deno/cache-dir@0.10.3": { + "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", + "dependencies": [ + "jsr:@deno/graph", + "jsr:@std/fmt@0.223", + "jsr:@std/fs@0.223", + "jsr:@std/io@0.223", + "jsr:@std/path@0.223" + ] }, - "npm": { - "@ayonli/jsext@0.9.58": { - "integrity": "sha512-AwGf64K6VqGyYLFA6rgyuU6jBbwUNJctENpW1bZayvXcIprhdfs7cn7S+EXi0pnNLupT9ptODYkKneB3/YuWww==", - "dependencies": { - "iconv-lite": "iconv-lite@0.6.3", - "sudo-prompt": "sudo-prompt@9.2.1", - "ws": "ws@8.18.0" - } - }, - "@deno/shim-crypto@0.3.1": { - "integrity": "sha512-ed4pNnfur6UbASEgF34gVxR9p7Mc3qF+Ygbmjiil8ws5IhNFhPDFy5vE5hQAUA9JmVsSxXPcVLM5Rf8LOZqQ5Q==", - "dependencies": {} - }, - "@deno/shim-deno-test@0.5.0": { - "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", - "dependencies": {} - }, - "@deno/shim-deno@0.17.0": { - "integrity": "sha512-+FzsP65eehAgTQdzt1izLEV17ePCZqHxDQqRDbpRc1yJVYtDI2MvbRq5DvOj90uRt6zKn9qtWpEueDqG1QORhQ==", - "dependencies": { - "@deno/shim-deno-test": "@deno/shim-deno-test@0.5.0", - "which": "which@4.0.0" - } - }, - "@esbuild/aix-ppc64@0.23.0": { - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", - "dependencies": {} - }, - "@esbuild/android-arm64@0.23.0": { - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", - "dependencies": {} - }, - "@esbuild/android-arm@0.23.0": { - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", - "dependencies": {} - }, - "@esbuild/android-x64@0.23.0": { - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", - "dependencies": {} - }, - "@esbuild/darwin-arm64@0.23.0": { - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", - "dependencies": {} - }, - "@esbuild/darwin-x64@0.23.0": { - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", - "dependencies": {} - }, - "@esbuild/freebsd-arm64@0.23.0": { - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", - "dependencies": {} - }, - "@esbuild/freebsd-x64@0.23.0": { - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", - "dependencies": {} - }, - "@esbuild/linux-arm64@0.23.0": { - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", - "dependencies": {} - }, - "@esbuild/linux-arm@0.23.0": { - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", - "dependencies": {} - }, - "@esbuild/linux-ia32@0.23.0": { - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", - "dependencies": {} - }, - "@esbuild/linux-loong64@0.23.0": { - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", - "dependencies": {} - }, - "@esbuild/linux-mips64el@0.23.0": { - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", - "dependencies": {} - }, - "@esbuild/linux-ppc64@0.23.0": { - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", - "dependencies": {} - }, - "@esbuild/linux-riscv64@0.23.0": { - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", - "dependencies": {} - }, - "@esbuild/linux-s390x@0.23.0": { - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", - "dependencies": {} - }, - "@esbuild/linux-x64@0.23.0": { - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", - "dependencies": {} - }, - "@esbuild/netbsd-x64@0.23.0": { - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", - "dependencies": {} - }, - "@esbuild/openbsd-arm64@0.23.0": { - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", - "dependencies": {} - }, - "@esbuild/openbsd-x64@0.23.0": { - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", - "dependencies": {} - }, - "@esbuild/sunos-x64@0.23.0": { - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", - "dependencies": {} - }, - "@esbuild/win32-arm64@0.23.0": { - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", - "dependencies": {} - }, - "@esbuild/win32-ia32@0.23.0": { - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", - "dependencies": {} - }, - "@esbuild/win32-x64@0.23.0": { - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", - "dependencies": {} - }, - "@eslint-community/eslint-utils@4.4.0_eslint@8.57.1": { - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dependencies": { - "eslint": "eslint@8.57.1", - "eslint-visitor-keys": "eslint-visitor-keys@3.4.3" - } - }, - "@eslint-community/eslint-utils@4.4.0_eslint@9.10.0": { - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dependencies": { - "eslint": "eslint@9.10.0", - "eslint-visitor-keys": "eslint-visitor-keys@3.4.3" - } - }, - "@eslint-community/regexpp@4.11.1": { - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", - "dependencies": {} - }, - "@eslint/config-array@0.18.0": { - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", - "dependencies": { - "@eslint/object-schema": "@eslint/object-schema@2.1.4", - "debug": "debug@4.3.7", - "minimatch": "minimatch@3.1.2" - } - }, - "@eslint/eslintrc@2.1.4": { - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dependencies": { - "ajv": "ajv@6.12.6", - "debug": "debug@4.3.7", - "espree": "espree@9.6.1_acorn@8.12.1", - "globals": "globals@13.24.0", - "ignore": "ignore@5.3.2", - "import-fresh": "import-fresh@3.3.0", - "js-yaml": "js-yaml@4.1.0", - "minimatch": "minimatch@3.1.2", - "strip-json-comments": "strip-json-comments@3.1.1" - } - }, - "@eslint/eslintrc@3.1.0": { - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", - "dependencies": { - "ajv": "ajv@6.12.6", - "debug": "debug@4.3.7", - "espree": "espree@10.1.0_acorn@8.12.1", - "globals": "globals@14.0.0", - "ignore": "ignore@5.3.2", - "import-fresh": "import-fresh@3.3.0", - "js-yaml": "js-yaml@4.1.0", - "minimatch": "minimatch@3.1.2", - "strip-json-comments": "strip-json-comments@3.1.1" - } - }, - "@eslint/js@8.57.1": { - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dependencies": {} - }, - "@eslint/js@9.10.0": { - "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", - "dependencies": {} - }, - "@eslint/object-schema@2.1.4": { - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "dependencies": {} - }, - "@eslint/plugin-kit@0.1.0": { - "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", - "dependencies": { - "levn": "levn@0.4.1" - } - }, - "@fastify/busboy@2.1.1": { - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dependencies": {} - }, - "@humanwhocodes/config-array@0.13.0": { - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "dependencies": { - "@humanwhocodes/object-schema": "@humanwhocodes/object-schema@2.0.3", - "debug": "debug@4.3.7", - "minimatch": "minimatch@3.1.2" - } - }, - "@humanwhocodes/module-importer@1.0.1": { - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dependencies": {} - }, - "@humanwhocodes/object-schema@2.0.3": { - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dependencies": {} - }, - "@humanwhocodes/retry@0.3.0": { - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "dependencies": {} - }, - "@nodelib/fs.scandir@2.1.5": { - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", - "run-parallel": "run-parallel@1.2.0" - } - }, - "@nodelib/fs.stat@2.0.5": { - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dependencies": {} - }, - "@nodelib/fs.walk@1.2.8": { - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "@nodelib/fs.scandir@2.1.5", - "fastq": "fastq@1.17.1" - } - }, - "@oakserver/oak@12.6.2": { - "integrity": "sha512-q9LfyC9tWV68me0GEUuA66qbwH8ep0bBdq9V02fePlPmPVUBCAzQQkomyaI/L4Uur+YALVXgLoTaC2rviZ7I4w==", - "dependencies": { - "@deno/shim-crypto": "@deno/shim-crypto@0.3.1", - "@deno/shim-deno": "@deno/shim-deno@0.17.0", - "tslib": "tslib@2.3.1", - "undici": "undici@5.28.4" - } - }, - "@rtsao/scc@1.1.0": { - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dependencies": {} - }, - "@types/diff@5.2.2": { - "integrity": "sha512-qVqLpd49rmJA2nZzLVsmfS/aiiBpfVE95dHhPVwG0NmSBAt+riPxnj53wq2oBq5m4Q2RF1IWFEUpnZTgrQZfEQ==", - "dependencies": {} - }, - "@types/json5@0.0.29": { - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dependencies": {} - }, - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", - "dependencies": {} - }, - "@ungap/structured-clone@1.2.0": { - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dependencies": {} - }, - "accepts@1.3.8": { - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "mime-types@2.1.35", - "negotiator": "negotiator@0.6.3" - } - }, - "acorn-jsx@5.3.2_acorn@8.12.1": { - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dependencies": { - "acorn": "acorn@8.12.1" - } - }, - "acorn@8.12.1": { - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dependencies": {} - }, - "ajv@6.12.6": { - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "fast-deep-equal@3.1.3", - "fast-json-stable-stringify": "fast-json-stable-stringify@2.1.0", - "json-schema-traverse": "json-schema-traverse@0.4.1", - "uri-js": "uri-js@4.4.1" - } - }, - "ansi-regex@5.0.1": { - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dependencies": {} - }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "color-convert@2.0.1" - } - }, - "argparse@2.0.1": { - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dependencies": {} - }, - "array-buffer-byte-length@1.0.1": { - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "is-array-buffer": "is-array-buffer@3.0.4" - } - }, - "array-flatten@1.1.1": { - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dependencies": {} - }, - "array-includes@3.1.8": { - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-object-atoms": "es-object-atoms@1.0.0", - "get-intrinsic": "get-intrinsic@1.2.4", - "is-string": "is-string@1.0.7" - } - }, - "array.prototype.findlastindex@1.2.5": { - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-errors": "es-errors@1.3.0", - "es-object-atoms": "es-object-atoms@1.0.0", - "es-shim-unscopables": "es-shim-unscopables@1.0.2" - } - }, - "array.prototype.flat@1.3.2": { - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-shim-unscopables": "es-shim-unscopables@1.0.2" - } - }, - "array.prototype.flatmap@1.3.2": { - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-shim-unscopables": "es-shim-unscopables@1.0.2" - } - }, - "arraybuffer.prototype.slice@1.0.3": { - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dependencies": { - "array-buffer-byte-length": "array-buffer-byte-length@1.0.1", - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-errors": "es-errors@1.3.0", - "get-intrinsic": "get-intrinsic@1.2.4", - "is-array-buffer": "is-array-buffer@3.0.4", - "is-shared-array-buffer": "is-shared-array-buffer@1.0.3" - } - }, - "available-typed-arrays@1.0.7": { - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dependencies": { - "possible-typed-array-names": "possible-typed-array-names@1.0.0" - } - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dependencies": {} - }, - "body-parser@1.20.2": { - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "bytes@3.1.2", - "content-type": "content-type@1.0.5", - "debug": "debug@2.6.9", - "depd": "depd@2.0.0", - "destroy": "destroy@1.2.0", - "http-errors": "http-errors@2.0.0", - "iconv-lite": "iconv-lite@0.4.24", - "on-finished": "on-finished@2.4.1", - "qs": "qs@6.11.0", - "raw-body": "raw-body@2.5.2", - "type-is": "type-is@1.6.18", - "unpipe": "unpipe@1.0.0" - } - }, - "brace-expansion@1.1.11": { - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "balanced-match@1.0.2", - "concat-map": "concat-map@0.0.1" - } - }, - "brace-expansion@2.0.1": { - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "balanced-match@1.0.2" - } - }, - "bundle-name@4.1.0": { - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dependencies": { - "run-applescript": "run-applescript@7.0.0" - } - }, - "bytes@3.1.2": { - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dependencies": {} - }, - "call-bind@1.0.7": { - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0", - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "get-intrinsic": "get-intrinsic@1.2.4", - "set-function-length": "set-function-length@1.2.2" - } - }, - "callsites@3.1.0": { - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dependencies": {} - }, - "chalk@4.1.2": { - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "ansi-styles@4.3.0", - "supports-color": "supports-color@7.2.0" - } - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "color-name@1.1.4" - } - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dependencies": {} - }, - "concat-map@0.0.1": { - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dependencies": {} - }, - "content-disposition@0.5.4": { - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "content-type@1.0.5": { - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dependencies": {} - }, - "cookie-signature@1.0.6": { - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dependencies": {} - }, - "cookie@0.6.0": { - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dependencies": {} - }, - "core-util-is@1.0.3": { - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dependencies": {} - }, - "cross-spawn@7.0.3": { - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "path-key@3.1.1", - "shebang-command": "shebang-command@2.0.0", - "which": "which@2.0.2" - } - }, - "data-view-buffer@1.0.1": { - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "is-data-view": "is-data-view@1.0.1" - } - }, - "data-view-byte-length@1.0.1": { - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "is-data-view": "is-data-view@1.0.1" - } - }, - "data-view-byte-offset@1.0.0": { - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "is-data-view": "is-data-view@1.0.1" - } - }, - "debug@2.6.9": { - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "ms@2.0.0" - } - }, - "debug@3.2.7": { - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "ms@2.1.3" - } - }, - "debug@4.3.7": { - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "ms@2.1.3" - } - }, - "deep-is@0.1.4": { - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dependencies": {} - }, - "default-browser-id@5.0.0": { - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "dependencies": {} - }, - "default-browser@5.2.1": { - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dependencies": { - "bundle-name": "bundle-name@4.1.0", - "default-browser-id": "default-browser-id@5.0.0" - } - }, - "define-data-property@1.1.4": { - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0", - "es-errors": "es-errors@1.3.0", - "gopd": "gopd@1.0.1" - } - }, - "define-lazy-prop@3.0.0": { - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dependencies": {} - }, - "define-properties@1.2.1": { - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dependencies": { - "define-data-property": "define-data-property@1.1.4", - "has-property-descriptors": "has-property-descriptors@1.0.2", - "object-keys": "object-keys@1.1.1" - } - }, - "depd@2.0.0": { - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dependencies": {} - }, - "destroy@1.2.0": { - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dependencies": {} - }, - "diff@5.2.0": { - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dependencies": {} - }, - "doctrine@2.1.0": { - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "esutils@2.0.3" - } - }, - "doctrine@3.0.0": { - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "esutils@2.0.3" - } - }, - "ee-first@1.1.1": { - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dependencies": {} - }, - "encodeurl@1.0.2": { - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dependencies": {} - }, - "es-abstract@1.23.3": { - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dependencies": { - "array-buffer-byte-length": "array-buffer-byte-length@1.0.1", - "arraybuffer.prototype.slice": "arraybuffer.prototype.slice@1.0.3", - "available-typed-arrays": "available-typed-arrays@1.0.7", - "call-bind": "call-bind@1.0.7", - "data-view-buffer": "data-view-buffer@1.0.1", - "data-view-byte-length": "data-view-byte-length@1.0.1", - "data-view-byte-offset": "data-view-byte-offset@1.0.0", - "es-define-property": "es-define-property@1.0.0", - "es-errors": "es-errors@1.3.0", - "es-object-atoms": "es-object-atoms@1.0.0", - "es-set-tostringtag": "es-set-tostringtag@2.0.3", - "es-to-primitive": "es-to-primitive@1.2.1", - "function.prototype.name": "function.prototype.name@1.1.6", - "get-intrinsic": "get-intrinsic@1.2.4", - "get-symbol-description": "get-symbol-description@1.0.2", - "globalthis": "globalthis@1.0.4", - "gopd": "gopd@1.0.1", - "has-property-descriptors": "has-property-descriptors@1.0.2", - "has-proto": "has-proto@1.0.3", - "has-symbols": "has-symbols@1.0.3", - "hasown": "hasown@2.0.2", - "internal-slot": "internal-slot@1.0.7", - "is-array-buffer": "is-array-buffer@3.0.4", - "is-callable": "is-callable@1.2.7", - "is-data-view": "is-data-view@1.0.1", - "is-negative-zero": "is-negative-zero@2.0.3", - "is-regex": "is-regex@1.1.4", - "is-shared-array-buffer": "is-shared-array-buffer@1.0.3", - "is-string": "is-string@1.0.7", - "is-typed-array": "is-typed-array@1.1.13", - "is-weakref": "is-weakref@1.0.2", - "object-inspect": "object-inspect@1.13.2", - "object-keys": "object-keys@1.1.1", - "object.assign": "object.assign@4.1.5", - "regexp.prototype.flags": "regexp.prototype.flags@1.5.2", - "safe-array-concat": "safe-array-concat@1.1.2", - "safe-regex-test": "safe-regex-test@1.0.3", - "string.prototype.trim": "string.prototype.trim@1.2.9", - "string.prototype.trimend": "string.prototype.trimend@1.0.8", - "string.prototype.trimstart": "string.prototype.trimstart@1.0.8", - "typed-array-buffer": "typed-array-buffer@1.0.2", - "typed-array-byte-length": "typed-array-byte-length@1.0.1", - "typed-array-byte-offset": "typed-array-byte-offset@1.0.2", - "typed-array-length": "typed-array-length@1.0.6", - "unbox-primitive": "unbox-primitive@1.0.2", - "which-typed-array": "which-typed-array@1.1.15" - } - }, - "es-define-property@1.0.0": { - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "es-errors@1.3.0": { - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dependencies": {} - }, - "es-main@1.3.0": { - "integrity": "sha512-AzORKdz1Zt97TzbYQnIrI3ZiibWpRXUfpo/w0xOJ20GpNYd2bd3MU9m31zS/aJ1TJl6JfLTok83Y8HjNunYT0A==", - "dependencies": {} - }, - "es-object-atoms@1.0.0": { - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dependencies": { - "es-errors": "es-errors@1.3.0" - } - }, - "es-set-tostringtag@2.0.3": { - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dependencies": { - "get-intrinsic": "get-intrinsic@1.2.4", - "has-tostringtag": "has-tostringtag@1.0.2", - "hasown": "hasown@2.0.2" - } - }, - "es-shim-unscopables@1.0.2": { - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dependencies": { - "hasown": "hasown@2.0.2" - } - }, - "es-to-primitive@1.2.1": { - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "is-callable@1.2.7", - "is-date-object": "is-date-object@1.0.5", - "is-symbol": "is-symbol@1.0.4" - } - }, - "esbuild@0.23.0": { - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "dependencies": { - "@esbuild/aix-ppc64": "@esbuild/aix-ppc64@0.23.0", - "@esbuild/android-arm": "@esbuild/android-arm@0.23.0", - "@esbuild/android-arm64": "@esbuild/android-arm64@0.23.0", - "@esbuild/android-x64": "@esbuild/android-x64@0.23.0", - "@esbuild/darwin-arm64": "@esbuild/darwin-arm64@0.23.0", - "@esbuild/darwin-x64": "@esbuild/darwin-x64@0.23.0", - "@esbuild/freebsd-arm64": "@esbuild/freebsd-arm64@0.23.0", - "@esbuild/freebsd-x64": "@esbuild/freebsd-x64@0.23.0", - "@esbuild/linux-arm": "@esbuild/linux-arm@0.23.0", - "@esbuild/linux-arm64": "@esbuild/linux-arm64@0.23.0", - "@esbuild/linux-ia32": "@esbuild/linux-ia32@0.23.0", - "@esbuild/linux-loong64": "@esbuild/linux-loong64@0.23.0", - "@esbuild/linux-mips64el": "@esbuild/linux-mips64el@0.23.0", - "@esbuild/linux-ppc64": "@esbuild/linux-ppc64@0.23.0", - "@esbuild/linux-riscv64": "@esbuild/linux-riscv64@0.23.0", - "@esbuild/linux-s390x": "@esbuild/linux-s390x@0.23.0", - "@esbuild/linux-x64": "@esbuild/linux-x64@0.23.0", - "@esbuild/netbsd-x64": "@esbuild/netbsd-x64@0.23.0", - "@esbuild/openbsd-arm64": "@esbuild/openbsd-arm64@0.23.0", - "@esbuild/openbsd-x64": "@esbuild/openbsd-x64@0.23.0", - "@esbuild/sunos-x64": "@esbuild/sunos-x64@0.23.0", - "@esbuild/win32-arm64": "@esbuild/win32-arm64@0.23.0", - "@esbuild/win32-ia32": "@esbuild/win32-ia32@0.23.0", - "@esbuild/win32-x64": "@esbuild/win32-x64@0.23.0" - } - }, - "escape-html@1.0.3": { - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dependencies": {} - }, - "escape-string-regexp@4.0.0": { - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dependencies": {} - }, - "eslint-import-resolver-node@0.3.9": { - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dependencies": { - "debug": "debug@3.2.7", - "is-core-module": "is-core-module@2.15.1", - "resolve": "resolve@1.22.8" - } - }, - "eslint-module-utils@2.11.0": { - "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", - "dependencies": { - "debug": "debug@3.2.7" - } - }, - "eslint-plugin-import@2.30.0_eslint@8.57.1": { - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", - "dependencies": { - "@rtsao/scc": "@rtsao/scc@1.1.0", - "array-includes": "array-includes@3.1.8", - "array.prototype.findlastindex": "array.prototype.findlastindex@1.2.5", - "array.prototype.flat": "array.prototype.flat@1.3.2", - "array.prototype.flatmap": "array.prototype.flatmap@1.3.2", - "debug": "debug@3.2.7", - "doctrine": "doctrine@2.1.0", - "eslint": "eslint@8.57.1", - "eslint-import-resolver-node": "eslint-import-resolver-node@0.3.9", - "eslint-module-utils": "eslint-module-utils@2.11.0", - "hasown": "hasown@2.0.2", - "is-core-module": "is-core-module@2.15.1", - "is-glob": "is-glob@4.0.3", - "minimatch": "minimatch@3.1.2", - "object.fromentries": "object.fromentries@2.0.8", - "object.groupby": "object.groupby@1.0.3", - "object.values": "object.values@1.2.0", - "semver": "semver@6.3.1", - "tsconfig-paths": "tsconfig-paths@3.15.0" - } - }, - "eslint-scope@7.2.2": { - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dependencies": { - "esrecurse": "esrecurse@4.3.0", - "estraverse": "estraverse@5.3.0" - } - }, - "eslint-scope@8.0.2": { - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", - "dependencies": { - "esrecurse": "esrecurse@4.3.0", - "estraverse": "estraverse@5.3.0" - } - }, - "eslint-visitor-keys@3.4.3": { - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dependencies": {} - }, - "eslint-visitor-keys@4.0.0": { - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dependencies": {} - }, - "eslint@8.57.1": { - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dependencies": { - "@eslint-community/eslint-utils": "@eslint-community/eslint-utils@4.4.0_eslint@8.57.1", - "@eslint-community/regexpp": "@eslint-community/regexpp@4.11.1", - "@eslint/eslintrc": "@eslint/eslintrc@2.1.4", - "@eslint/js": "@eslint/js@8.57.1", - "@humanwhocodes/config-array": "@humanwhocodes/config-array@0.13.0", - "@humanwhocodes/module-importer": "@humanwhocodes/module-importer@1.0.1", - "@nodelib/fs.walk": "@nodelib/fs.walk@1.2.8", - "@ungap/structured-clone": "@ungap/structured-clone@1.2.0", - "ajv": "ajv@6.12.6", - "chalk": "chalk@4.1.2", - "cross-spawn": "cross-spawn@7.0.3", - "debug": "debug@4.3.7", - "doctrine": "doctrine@3.0.0", - "escape-string-regexp": "escape-string-regexp@4.0.0", - "eslint-scope": "eslint-scope@7.2.2", - "eslint-visitor-keys": "eslint-visitor-keys@3.4.3", - "espree": "espree@9.6.1_acorn@8.12.1", - "esquery": "esquery@1.6.0", - "esutils": "esutils@2.0.3", - "fast-deep-equal": "fast-deep-equal@3.1.3", - "file-entry-cache": "file-entry-cache@6.0.1", - "find-up": "find-up@5.0.0", - "glob-parent": "glob-parent@6.0.2", - "globals": "globals@13.24.0", - "graphemer": "graphemer@1.4.0", - "ignore": "ignore@5.3.2", - "imurmurhash": "imurmurhash@0.1.4", - "is-glob": "is-glob@4.0.3", - "is-path-inside": "is-path-inside@3.0.3", - "js-yaml": "js-yaml@4.1.0", - "json-stable-stringify-without-jsonify": "json-stable-stringify-without-jsonify@1.0.1", - "levn": "levn@0.4.1", - "lodash.merge": "lodash.merge@4.6.2", - "minimatch": "minimatch@3.1.2", - "natural-compare": "natural-compare@1.4.0", - "optionator": "optionator@0.9.4", - "strip-ansi": "strip-ansi@6.0.1", - "text-table": "text-table@0.2.0" - } - }, - "eslint@9.10.0": { - "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", - "dependencies": { - "@eslint-community/eslint-utils": "@eslint-community/eslint-utils@4.4.0_eslint@9.10.0", - "@eslint-community/regexpp": "@eslint-community/regexpp@4.11.1", - "@eslint/config-array": "@eslint/config-array@0.18.0", - "@eslint/eslintrc": "@eslint/eslintrc@3.1.0", - "@eslint/js": "@eslint/js@9.10.0", - "@eslint/plugin-kit": "@eslint/plugin-kit@0.1.0", - "@humanwhocodes/module-importer": "@humanwhocodes/module-importer@1.0.1", - "@humanwhocodes/retry": "@humanwhocodes/retry@0.3.0", - "@nodelib/fs.walk": "@nodelib/fs.walk@1.2.8", - "ajv": "ajv@6.12.6", - "chalk": "chalk@4.1.2", - "cross-spawn": "cross-spawn@7.0.3", - "debug": "debug@4.3.7", - "escape-string-regexp": "escape-string-regexp@4.0.0", - "eslint-scope": "eslint-scope@8.0.2", - "eslint-visitor-keys": "eslint-visitor-keys@4.0.0", - "espree": "espree@10.1.0_acorn@8.12.1", - "esquery": "esquery@1.6.0", - "esutils": "esutils@2.0.3", - "fast-deep-equal": "fast-deep-equal@3.1.3", - "file-entry-cache": "file-entry-cache@8.0.0", - "find-up": "find-up@5.0.0", - "glob-parent": "glob-parent@6.0.2", - "ignore": "ignore@5.3.2", - "imurmurhash": "imurmurhash@0.1.4", - "is-glob": "is-glob@4.0.3", - "is-path-inside": "is-path-inside@3.0.3", - "json-stable-stringify-without-jsonify": "json-stable-stringify-without-jsonify@1.0.1", - "lodash.merge": "lodash.merge@4.6.2", - "minimatch": "minimatch@3.1.2", - "natural-compare": "natural-compare@1.4.0", - "optionator": "optionator@0.9.4", - "strip-ansi": "strip-ansi@6.0.1", - "text-table": "text-table@0.2.0" - } - }, - "espree@10.1.0_acorn@8.12.1": { - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", - "dependencies": { - "acorn": "acorn@8.12.1", - "acorn-jsx": "acorn-jsx@5.3.2_acorn@8.12.1", - "eslint-visitor-keys": "eslint-visitor-keys@4.0.0" - } - }, - "espree@9.6.1_acorn@8.12.1": { - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dependencies": { - "acorn": "acorn@8.12.1", - "acorn-jsx": "acorn-jsx@5.3.2_acorn@8.12.1", - "eslint-visitor-keys": "eslint-visitor-keys@3.4.3" - } - }, - "esquery@1.6.0": { - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dependencies": { - "estraverse": "estraverse@5.3.0" - } - }, - "esrecurse@4.3.0": { - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "estraverse@5.3.0" - } - }, - "estraverse@5.3.0": { - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dependencies": {} - }, - "esutils@2.0.3": { - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dependencies": {} - }, - "etag@1.8.1": { - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dependencies": {} - }, - "express@4.19.2": { - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "accepts@1.3.8", - "array-flatten": "array-flatten@1.1.1", - "body-parser": "body-parser@1.20.2", - "content-disposition": "content-disposition@0.5.4", - "content-type": "content-type@1.0.5", - "cookie": "cookie@0.6.0", - "cookie-signature": "cookie-signature@1.0.6", - "debug": "debug@2.6.9", - "depd": "depd@2.0.0", - "encodeurl": "encodeurl@1.0.2", - "escape-html": "escape-html@1.0.3", - "etag": "etag@1.8.1", - "finalhandler": "finalhandler@1.2.0", - "fresh": "fresh@0.5.2", - "http-errors": "http-errors@2.0.0", - "merge-descriptors": "merge-descriptors@1.0.1", - "methods": "methods@1.1.2", - "on-finished": "on-finished@2.4.1", - "parseurl": "parseurl@1.3.3", - "path-to-regexp": "path-to-regexp@0.1.7", - "proxy-addr": "proxy-addr@2.0.7", - "qs": "qs@6.11.0", - "range-parser": "range-parser@1.2.1", - "safe-buffer": "safe-buffer@5.2.1", - "send": "send@0.18.0", - "serve-static": "serve-static@1.15.0", - "setprototypeof": "setprototypeof@1.2.0", - "statuses": "statuses@2.0.1", - "type-is": "type-is@1.6.18", - "utils-merge": "utils-merge@1.0.1", - "vary": "vary@1.1.2" - } - }, - "fast-deep-equal@3.1.3": { - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dependencies": {} - }, - "fast-json-stable-stringify@2.1.0": { - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dependencies": {} - }, - "fast-levenshtein@2.0.6": { - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dependencies": {} - }, - "fastq@1.17.1": { - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "reusify@1.0.4" - } - }, - "file-entry-cache@6.0.1": { - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dependencies": { - "flat-cache": "flat-cache@3.2.0" - } - }, - "file-entry-cache@8.0.0": { - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dependencies": { - "flat-cache": "flat-cache@4.0.1" - } - }, - "finalhandler@1.2.0": { - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "debug@2.6.9", - "encodeurl": "encodeurl@1.0.2", - "escape-html": "escape-html@1.0.3", - "on-finished": "on-finished@2.4.1", - "parseurl": "parseurl@1.3.3", - "statuses": "statuses@2.0.1", - "unpipe": "unpipe@1.0.0" - } - }, - "find-up@5.0.0": { - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "locate-path@6.0.0", - "path-exists": "path-exists@4.0.0" - } - }, - "flat-cache@3.2.0": { - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dependencies": { - "flatted": "flatted@3.3.1", - "keyv": "keyv@4.5.4", - "rimraf": "rimraf@3.0.2" - } - }, - "flat-cache@4.0.1": { - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dependencies": { - "flatted": "flatted@3.3.1", - "keyv": "keyv@4.5.4" - } - }, - "flatted@3.3.1": { - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dependencies": {} - }, - "for-each@0.3.3": { - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "is-callable@1.2.7" - } - }, - "forwarded@0.2.0": { - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dependencies": {} - }, - "fresh@0.5.2": { - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dependencies": {} - }, - "fs.realpath@1.0.0": { - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dependencies": {} - }, - "function-bind@1.1.2": { - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dependencies": {} - }, - "function.prototype.name@1.1.6": { - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "functions-have-names": "functions-have-names@1.2.3" - } - }, - "functions-have-names@1.2.3": { - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dependencies": {} - }, - "get-intrinsic@1.2.4": { - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "has-proto": "has-proto@1.0.3", - "has-symbols": "has-symbols@1.0.3", - "hasown": "hasown@2.0.2" - } - }, - "get-port@7.1.0": { - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", - "dependencies": {} - }, - "get-symbol-description@1.0.2": { - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "gitignore-parser@0.0.2": { - "integrity": "sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg==", - "dependencies": {} - }, - "glob-parent@6.0.2": { - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "is-glob@4.0.3" - } - }, - "glob@7.2.3": { - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "fs.realpath@1.0.0", - "inflight": "inflight@1.0.6", - "inherits": "inherits@2.0.4", - "minimatch": "minimatch@3.1.2", - "once": "once@1.4.0", - "path-is-absolute": "path-is-absolute@1.0.1" - } - }, - "globals@13.24.0": { - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dependencies": { - "type-fest": "type-fest@0.20.2" - } - }, - "globals@14.0.0": { - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dependencies": {} - }, - "globalthis@1.0.4": { - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dependencies": { - "define-properties": "define-properties@1.2.1", - "gopd": "gopd@1.0.1" - } - }, - "gopd@1.0.1": { - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "graphemer@1.4.0": { - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dependencies": {} - }, - "has-bigints@1.0.2": { - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dependencies": {} - }, - "has-flag@4.0.0": { - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dependencies": {} - }, - "has-property-descriptors@1.0.2": { - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0" - } - }, - "has-proto@1.0.3": { - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dependencies": {} - }, - "has-symbols@1.0.3": { - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dependencies": {} - }, - "has-tostringtag@1.0.2": { - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "has-symbols@1.0.3" - } - }, - "hasown@2.0.2": { - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "function-bind@1.1.2" - } - }, - "http-errors@2.0.0": { - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "depd@2.0.0", - "inherits": "inherits@2.0.4", - "setprototypeof": "setprototypeof@1.2.0", - "statuses": "statuses@2.0.1", - "toidentifier": "toidentifier@1.0.1" - } - }, - "iconv-lite@0.4.24": { - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": "safer-buffer@2.1.2" - } - }, - "iconv-lite@0.6.3": { - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": "safer-buffer@2.1.2" - } - }, - "ignore@5.3.2": { - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dependencies": {} - }, - "immediate@3.0.6": { - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dependencies": {} - }, - "import-fresh@3.3.0": { - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "parent-module@1.0.1", - "resolve-from": "resolve-from@4.0.0" - } - }, - "imurmurhash@0.1.4": { - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dependencies": {} - }, - "inflight@1.0.6": { - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "once@1.4.0", - "wrappy": "wrappy@1.0.2" - } - }, - "inherits@2.0.4": { - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dependencies": {} - }, - "internal-slot@1.0.7": { - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dependencies": { - "es-errors": "es-errors@1.3.0", - "hasown": "hasown@2.0.2", - "side-channel": "side-channel@1.0.6" - } - }, - "ipaddr.js@1.9.1": { - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dependencies": {} - }, - "is-array-buffer@3.0.4": { - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "is-bigint@1.0.4": { - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "has-bigints@1.0.2" - } - }, - "is-boolean-object@1.1.2": { - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-callable@1.2.7": { - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dependencies": {} - }, - "is-core-module@2.15.1": { - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dependencies": { - "hasown": "hasown@2.0.2" - } - }, - "is-data-view@1.0.1": { - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dependencies": { - "is-typed-array": "is-typed-array@1.1.13" - } - }, - "is-date-object@1.0.5": { - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-docker@3.0.0": { - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dependencies": {} - }, - "is-extglob@2.1.1": { - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dependencies": {} - }, - "is-glob@4.0.3": { - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "is-extglob@2.1.1" - } - }, - "is-inside-container@1.0.0": { - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dependencies": { - "is-docker": "is-docker@3.0.0" - } - }, - "is-negative-zero@2.0.3": { - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dependencies": {} - }, - "is-number-object@1.0.7": { - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dependencies": { - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-path-inside@3.0.3": { - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dependencies": {} - }, - "is-regex@1.1.4": { - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-shared-array-buffer@1.0.3": { - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dependencies": { - "call-bind": "call-bind@1.0.7" - } - }, - "is-string@1.0.7": { - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-symbol@1.0.4": { - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "has-symbols@1.0.3" - } - }, - "is-typed-array@1.1.13": { - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dependencies": { - "which-typed-array": "which-typed-array@1.1.15" - } - }, - "is-weakref@1.0.2": { - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7" - } - }, - "is-wsl@3.1.0": { - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dependencies": { - "is-inside-container": "is-inside-container@1.0.0" - } - }, - "isarray@1.0.0": { - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dependencies": {} - }, - "isarray@2.0.5": { - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dependencies": {} - }, - "isexe@2.0.0": { - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dependencies": {} - }, - "isexe@3.1.1": { - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dependencies": {} - }, - "js-yaml@4.1.0": { - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "argparse@2.0.1" - } - }, - "json-buffer@3.0.1": { - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dependencies": {} - }, - "json-schema-traverse@0.4.1": { - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dependencies": {} - }, - "json-stable-stringify-without-jsonify@1.0.1": { - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dependencies": {} - }, - "json5@1.0.2": { - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "minimist@1.2.8" - } - }, - "jszip@3.7.1": { - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "dependencies": { - "lie": "lie@3.3.0", - "pako": "pako@1.0.11", - "readable-stream": "readable-stream@2.3.8", - "set-immediate-shim": "set-immediate-shim@1.0.1" - } - }, - "keyv@4.5.4": { - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": { - "json-buffer": "json-buffer@3.0.1" - } - }, - "levn@0.4.1": { - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": { - "prelude-ls": "prelude-ls@1.2.1", - "type-check": "type-check@0.4.0" - } - }, - "lie@3.3.0": { - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "immediate@3.0.6" - } - }, - "locate-path@6.0.0": { - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "p-locate@5.0.0" - } - }, - "lodash.merge@4.6.2": { - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dependencies": {} - }, - "media-typer@0.3.0": { - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dependencies": {} - }, - "merge-descriptors@1.0.1": { - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dependencies": {} - }, - "methods@1.1.2": { - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dependencies": {} - }, - "mime-db@1.52.0": { - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dependencies": {} - }, - "mime-types@2.1.35": { - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "mime-db@1.52.0" - } - }, - "mime@1.6.0": { - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dependencies": {} - }, - "minimatch@10.0.1": { - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dependencies": { - "brace-expansion": "brace-expansion@2.0.1" - } - }, - "minimatch@3.1.2": { - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "brace-expansion@1.1.11" - } - }, - "minimist@1.2.8": { - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dependencies": {} - }, - "ms@2.0.0": { - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dependencies": {} - }, - "ms@2.1.3": { - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dependencies": {} - }, - "natural-compare@1.4.0": { - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dependencies": {} - }, - "negotiator@0.6.3": { - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dependencies": {} - }, - "object-inspect@1.13.2": { - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dependencies": {} - }, - "object-keys@1.1.1": { - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dependencies": {} - }, - "object.assign@4.1.5": { - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "has-symbols": "has-symbols@1.0.3", - "object-keys": "object-keys@1.1.1" - } - }, - "object.fromentries@2.0.8": { - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-object-atoms": "es-object-atoms@1.0.0" - } - }, - "object.groupby@1.0.3": { - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3" - } - }, - "object.values@1.2.0": { - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-object-atoms": "es-object-atoms@1.0.0" - } - }, - "on-finished@2.4.1": { - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "ee-first@1.1.1" - } - }, - "once@1.4.0": { - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "wrappy@1.0.2" - } - }, - "open@10.1.0": { - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "dependencies": { - "default-browser": "default-browser@5.2.1", - "define-lazy-prop": "define-lazy-prop@3.0.0", - "is-inside-container": "is-inside-container@1.0.0", - "is-wsl": "is-wsl@3.1.0" - } - }, - "optionator@0.9.4": { - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dependencies": { - "deep-is": "deep-is@0.1.4", - "fast-levenshtein": "fast-levenshtein@2.0.6", - "levn": "levn@0.4.1", - "prelude-ls": "prelude-ls@1.2.1", - "type-check": "type-check@0.4.0", - "word-wrap": "word-wrap@1.2.5" - } - }, - "p-limit@3.1.0": { - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "yocto-queue@0.1.0" - } - }, - "p-locate@5.0.0": { - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "p-limit@3.1.0" - } - }, - "pako@1.0.11": { - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dependencies": {} - }, - "parent-module@1.0.1": { - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "callsites@3.1.0" - } - }, - "parseurl@1.3.3": { - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dependencies": {} - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dependencies": {} - }, - "path-is-absolute@1.0.1": { - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dependencies": {} - }, - "path-key@3.1.1": { - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dependencies": {} - }, - "path-parse@1.0.7": { - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dependencies": {} - }, - "path-to-regexp@0.1.7": { - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dependencies": {} - }, - "possible-typed-array-names@1.0.0": { - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dependencies": {} - }, - "prelude-ls@1.2.1": { - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dependencies": {} - }, - "process-nextick-args@2.0.1": { - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dependencies": {} - }, - "proxy-addr@2.0.7": { - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "forwarded@0.2.0", - "ipaddr.js": "ipaddr.js@1.9.1" - } - }, - "punycode@2.3.1": { - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dependencies": {} - }, - "qs@6.11.0": { - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "side-channel@1.0.6" - } - }, - "queue-microtask@1.2.3": { - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dependencies": {} - }, - "range-parser@1.2.1": { - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dependencies": {} - }, - "raw-body@2.5.2": { - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "bytes@3.1.2", - "http-errors": "http-errors@2.0.0", - "iconv-lite": "iconv-lite@0.4.24", - "unpipe": "unpipe@1.0.0" - } - }, - "readable-stream@2.3.8": { - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "core-util-is@1.0.3", - "inherits": "inherits@2.0.4", - "isarray": "isarray@1.0.0", - "process-nextick-args": "process-nextick-args@2.0.1", - "safe-buffer": "safe-buffer@5.1.2", - "string_decoder": "string_decoder@1.1.1", - "util-deprecate": "util-deprecate@1.0.2" - } - }, - "regexp.prototype.flags@1.5.2": { - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-errors": "es-errors@1.3.0", - "set-function-name": "set-function-name@2.0.2" - } - }, - "resolve-from@4.0.0": { - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dependencies": {} - }, - "resolve@1.22.8": { - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "is-core-module@2.15.1", - "path-parse": "path-parse@1.0.7", - "supports-preserve-symlinks-flag": "supports-preserve-symlinks-flag@1.0.0" - } - }, - "reusify@1.0.4": { - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dependencies": {} - }, - "rimraf@3.0.2": { - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "glob@7.2.3" - } - }, - "run-applescript@7.0.0": { - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "dependencies": {} - }, - "run-parallel@1.2.0": { - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dependencies": { - "queue-microtask": "queue-microtask@1.2.3" - } - }, - "safe-array-concat@1.1.2": { - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "get-intrinsic": "get-intrinsic@1.2.4", - "has-symbols": "has-symbols@1.0.3", - "isarray": "isarray@2.0.5" - } - }, - "safe-buffer@5.1.2": { - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dependencies": {} - }, - "safe-buffer@5.2.1": { - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dependencies": {} - }, - "safe-regex-test@1.0.3": { - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "is-regex": "is-regex@1.1.4" - } - }, - "safer-buffer@2.1.2": { - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dependencies": {} - }, - "semver@6.3.1": { - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dependencies": {} - }, - "send@0.18.0": { - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "debug@2.6.9", - "depd": "depd@2.0.0", - "destroy": "destroy@1.2.0", - "encodeurl": "encodeurl@1.0.2", - "escape-html": "escape-html@1.0.3", - "etag": "etag@1.8.1", - "fresh": "fresh@0.5.2", - "http-errors": "http-errors@2.0.0", - "mime": "mime@1.6.0", - "ms": "ms@2.1.3", - "on-finished": "on-finished@2.4.1", - "range-parser": "range-parser@1.2.1", - "statuses": "statuses@2.0.1" - } - }, - "serve-static@1.15.0": { - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "encodeurl@1.0.2", - "escape-html": "escape-html@1.0.3", - "parseurl": "parseurl@1.3.3", - "send": "send@0.18.0" - } - }, - "set-function-length@1.2.2": { - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "define-data-property@1.1.4", - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "get-intrinsic": "get-intrinsic@1.2.4", - "gopd": "gopd@1.0.1", - "has-property-descriptors": "has-property-descriptors@1.0.2" - } - }, - "set-function-name@2.0.2": { - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dependencies": { - "define-data-property": "define-data-property@1.1.4", - "es-errors": "es-errors@1.3.0", - "functions-have-names": "functions-have-names@1.2.3", - "has-property-descriptors": "has-property-descriptors@1.0.2" - } - }, - "set-immediate-shim@1.0.1": { - "integrity": "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==", - "dependencies": {} - }, - "setprototypeof@1.2.0": { - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dependencies": {} - }, - "shebang-command@2.0.0": { - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "shebang-regex@3.0.0" - } - }, - "shebang-regex@3.0.0": { - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dependencies": {} - }, - "side-channel@1.0.6": { - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "get-intrinsic": "get-intrinsic@1.2.4", - "object-inspect": "object-inspect@1.13.2" - } - }, - "statuses@2.0.1": { - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dependencies": {} - }, - "string.prototype.trim@1.2.9": { - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-abstract": "es-abstract@1.23.3", - "es-object-atoms": "es-object-atoms@1.0.0" - } - }, - "string.prototype.trimend@1.0.8": { - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-object-atoms": "es-object-atoms@1.0.0" - } - }, - "string.prototype.trimstart@1.0.8": { - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "es-object-atoms": "es-object-atoms@1.0.0" - } - }, - "string_decoder@1.1.1": { - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "safe-buffer@5.1.2" - } - }, - "strip-ansi@6.0.1": { - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "ansi-regex@5.0.1" - } - }, - "strip-bom@3.0.0": { - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dependencies": {} - }, - "strip-json-comments@3.1.1": { - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dependencies": {} - }, - "sudo-prompt@9.2.1": { - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "dependencies": {} - }, - "supports-color@7.2.0": { - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "has-flag@4.0.0" - } - }, - "supports-preserve-symlinks-flag@1.0.0": { - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dependencies": {} - }, - "text-table@0.2.0": { - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dependencies": {} - }, - "toidentifier@1.0.1": { - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dependencies": {} - }, - "tsconfig-paths@3.15.0": { - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dependencies": { - "@types/json5": "@types/json5@0.0.29", - "json5": "json5@1.0.2", - "minimist": "minimist@1.2.8", - "strip-bom": "strip-bom@3.0.0" - } - }, - "tslib@2.3.1": { - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dependencies": {} - }, - "type-check@0.4.0": { - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": { - "prelude-ls": "prelude-ls@1.2.1" - } - }, - "type-fest@0.20.2": { - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dependencies": {} - }, - "type-is@1.6.18": { - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "media-typer@0.3.0", - "mime-types": "mime-types@2.1.35" - } - }, - "typed-array-buffer@1.0.2": { - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "is-typed-array": "is-typed-array@1.1.13" - } - }, - "typed-array-byte-length@1.0.1": { - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "for-each": "for-each@0.3.3", - "gopd": "gopd@1.0.1", - "has-proto": "has-proto@1.0.3", - "is-typed-array": "is-typed-array@1.1.13" - } - }, - "typed-array-byte-offset@1.0.2": { - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dependencies": { - "available-typed-arrays": "available-typed-arrays@1.0.7", - "call-bind": "call-bind@1.0.7", - "for-each": "for-each@0.3.3", - "gopd": "gopd@1.0.1", - "has-proto": "has-proto@1.0.3", - "is-typed-array": "is-typed-array@1.1.13" - } - }, - "typed-array-length@1.0.6": { - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "for-each": "for-each@0.3.3", - "gopd": "gopd@1.0.1", - "has-proto": "has-proto@1.0.3", - "is-typed-array": "is-typed-array@1.1.13", - "possible-typed-array-names": "possible-typed-array-names@1.0.0" - } - }, - "unbox-primitive@1.0.2": { - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "has-bigints": "has-bigints@1.0.2", - "has-symbols": "has-symbols@1.0.3", - "which-boxed-primitive": "which-boxed-primitive@1.0.2" - } - }, - "undici@5.28.4": { - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dependencies": { - "@fastify/busboy": "@fastify/busboy@2.1.1" - } - }, - "unpipe@1.0.0": { - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dependencies": {} - }, - "uri-js@4.4.1": { - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "punycode@2.3.1" - } - }, - "util-deprecate@1.0.2": { - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dependencies": {} - }, - "utils-merge@1.0.1": { - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dependencies": {} - }, - "vary@1.1.2": { - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dependencies": {} - }, - "which-boxed-primitive@1.0.2": { - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "is-bigint@1.0.4", - "is-boolean-object": "is-boolean-object@1.1.2", - "is-number-object": "is-number-object@1.0.7", - "is-string": "is-string@1.0.7", - "is-symbol": "is-symbol@1.0.4" - } - }, - "which-typed-array@1.1.15": { - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dependencies": { - "available-typed-arrays": "available-typed-arrays@1.0.7", - "call-bind": "call-bind@1.0.7", - "for-each": "for-each@0.3.3", - "gopd": "gopd@1.0.1", - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "which@2.0.2": { - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "isexe@2.0.0" - } - }, - "which@4.0.0": { - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dependencies": { - "isexe": "isexe@3.1.1" - } - }, - "windmill-client@1.364.0": { - "integrity": "sha512-UjCbBB2IeyMVoDyO16boHEsTus2npVtXyER2pfP0zm7rmp6ko65HA3OXvMzhMVq3hW9RQ5esc3mrkTnb19PIaw==", - "dependencies": {} - }, - "word-wrap@1.2.5": { - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dependencies": {} - }, - "wrappy@1.0.2": { - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dependencies": {} - }, - "ws@8.18.0": { - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dependencies": {} - }, - "yocto-queue@0.1.0": { - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dependencies": {} - } + "@deno/dnt@0.41.3": { + "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", + "dependencies": [ + "jsr:@david/code-block-writer", + "jsr:@deno/cache-dir", + "jsr:@std/fmt@1", + "jsr:@std/fs@1", + "jsr:@std/path@1", + "jsr:@ts-morph/bootstrap" + ] + }, + "@deno/graph@0.73.1": { + "integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41" + }, + "@std/assert@0.223.0": { + "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" + }, + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" + }, + "@std/assert@1.0.0-rc.2": { + "integrity": "0484eab1d76b55fca1c3beaff485a274e67dd3b9f065edcbe70030dfc0b964d3" + }, + "@std/bytes@0.223.0": { + "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" + }, + "@std/bytes@1.0.2": { + "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" + }, + "@std/cli@1.0.0-rc.2": { + "integrity": "97dfae82b9f0e189768ebfa7a5da53375955b94bad0a1804f8e3b73563b03787" + }, + "@std/encoding@1.0.0-rc.2": { + "integrity": "160d7674a20ebfbccdf610b3801fee91cf6e42d1c106dd46bbaf46e395cd35ef" + }, + "@std/encoding@1.0.4": { + "integrity": "2266cd516b32369e3dc5695717c96bf88343a1f761d6e6187a02a2bbe2af86ae" + }, + "@std/fmt@0.223.0": { + "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" + }, + "@std/fmt@0.225.6": { + "integrity": "aba6aea27f66813cecfd9484e074a9e9845782ab0685c030e453a8a70b37afc8" + }, + "@std/fmt@1.0.2": { + "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" + }, + "@std/fs@0.223.0": { + "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" + }, + "@std/fs@0.229.3": { + "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", + "dependencies": [ + "jsr:@std/path@1.0.0-rc.1" + ] + }, + "@std/fs@1.0.3": { + "integrity": "3cb839b1360b0a42d8b367c3093bfe4071798e6694fa44cf1963e04a8edba4fe", + "dependencies": [ + "jsr:@std/path@^1.0.4" + ] + }, + "@std/io@0.223.0": { + "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", + "dependencies": [ + "jsr:@std/assert@0.223", + "jsr:@std/bytes@0.223" + ] + }, + "@std/io@0.224.7": { + "integrity": "a70848793c44a7c100926571a8c9be68ba85487bfcd4d0540d86deabe1123dc9", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] + }, + "@std/log@0.224.7": { + "integrity": "021941e5cd16de60cb11599c9b36f892aea95987fe66c753922808da27909e18", + "dependencies": [ + "jsr:@std/fmt@^1.0.2", + "jsr:@std/fs@^1.0.3", + "jsr:@std/io@~0.224.7" + ] + }, + "@std/net@1.0.2": { + "integrity": "520c18ddb7f67d3830a1adfef03a155d496fe9683a9cb63bb823b5afb86484dc" + }, + "@std/path@0.223.0": { + "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", + "dependencies": [ + "jsr:@std/assert@0.223" + ] + }, + "@std/path@0.225.2": { + "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", + "dependencies": [ + "jsr:@std/assert@0.226" + ] + }, + "@std/path@1.0.0-rc.1": { + "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" + }, + "@std/path@1.0.0-rc.2": { + "integrity": "39f20d37a44d1867abac8d91c169359ea6e942237a45a99ee1e091b32b921c7d" + }, + "@std/path@1.0.4": { + "integrity": "48dd5d8389bcfcd619338a01bdf862cb7799933390146a54ae59356a0acc7105" + }, + "@std/streams@1.0.4": { + "integrity": "a1a5b01c74ca1d2dcaacfe1d4bbb91392e765946d82a3471bd95539adc6da83a", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] + }, + "@std/text@1.0.0-rc.1": { + "integrity": "34c722203e87ee12792c8d4a0cd2ee0e001341cbce75b860fc21be19d62232b0" + }, + "@std/yaml@1.0.5": { + "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" + }, + "@ts-morph/bootstrap@0.24.0": { + "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", + "dependencies": [ + "jsr:@ts-morph/common" + ] + }, + "@ts-morph/common@0.24.0": { + "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", + "dependencies": [ + "jsr:@std/fs@~0.229.3", + "jsr:@std/path@~0.225.2" + ] + }, + "@windmill-labs/cliffy-ansi@1.0.0-rc.5": { + "integrity": "1109cbcb0c415b57779352f708f5969b8c645f56bc555cbafc6ea5e0c6a360a4", + "dependencies": [ + "jsr:@std/encoding@1.0.0-rc.2", + "jsr:@std/fmt@~0.225.4", + "jsr:@std/io@~0.224.2", + "jsr:@windmill-labs/cliffy-internal" + ] + }, + "@windmill-labs/cliffy-command@1.0.0-rc.5": { + "integrity": "3eaa9def5f5afa1028f4a60ee4d9065ccc5f194d032824365c6ebcb9f46db66e", + "dependencies": [ + "jsr:@std/fmt@~0.225.4", + "jsr:@std/text", + "jsr:@windmill-labs/cliffy-flags", + "jsr:@windmill-labs/cliffy-internal", + "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5" + ] + }, + "@windmill-labs/cliffy-flags@1.0.0-rc.5": { + "integrity": "0e4b5b53a02295f8bf27d93b3bcca5d5d001a0286aea17404e6ef6347f69363c", + "dependencies": [ + "jsr:@std/text" + ] + }, + "@windmill-labs/cliffy-internal@1.0.0-rc.5": { + "integrity": "876b989ad2d1b739cc4a4f1386dbb80f819d2851ad583c1f486fc6ebe9beadf6" + }, + "@windmill-labs/cliffy-keycode@1.0.0-rc.5": { + "integrity": "2bc1b1af363528e38ed47bce525f417cab278b829a423353ffd589622f5a746e" + }, + "@windmill-labs/cliffy-prompt@1.0.0-rc.5": { + "integrity": "329a097911f219b15ea643ae83b6b360a11df7fc4cafdac1bf6869259475033a", + "dependencies": [ + "jsr:@std/assert@1.0.0-rc.2", + "jsr:@std/fmt@~0.225.4", + "jsr:@std/io@~0.224.2", + "jsr:@std/path@1.0.0-rc.2", + "jsr:@std/text", + "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-internal", + "jsr:@windmill-labs/cliffy-keycode" + ] + }, + "@windmill-labs/cliffy-prompt@1.0.0-rc.6": { + "integrity": "ffe09bee0e1e07bc12b2147be509d10b47eb6a36cbfea80fc206b4ae86693205", + "dependencies": [ + "jsr:@std/assert@1.0.0-rc.2", + "jsr:@std/fmt@~0.225.4", + "jsr:@std/io@~0.224.2", + "jsr:@std/path@1.0.0-rc.2", + "jsr:@std/text", + "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5", + "jsr:@windmill-labs/cliffy-internal", + "jsr:@windmill-labs/cliffy-keycode" + ] + }, + "@windmill-labs/cliffy-table@1.0.0-rc.5": { + "integrity": "5f26cb6ccbc2fbf1b1f79f9062d166e32e11d34398c0deac7e6f9d8378970546", + "dependencies": [ + "jsr:@std/cli", + "jsr:@std/fmt@~0.225.4" + ] + } + }, + "npm": { + "@ayonli/jsext@0.9.58": { + "integrity": "sha512-AwGf64K6VqGyYLFA6rgyuU6jBbwUNJctENpW1bZayvXcIprhdfs7cn7S+EXi0pnNLupT9ptODYkKneB3/YuWww==", + "dependencies": [ + "iconv-lite@0.6.3", + "sudo-prompt", + "ws" + ] + }, + "@deno/shim-crypto@0.3.1": { + "integrity": "sha512-ed4pNnfur6UbASEgF34gVxR9p7Mc3qF+Ygbmjiil8ws5IhNFhPDFy5vE5hQAUA9JmVsSxXPcVLM5Rf8LOZqQ5Q==" + }, + "@deno/shim-deno-test@0.5.0": { + "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==" + }, + "@deno/shim-deno@0.17.0": { + "integrity": "sha512-+FzsP65eehAgTQdzt1izLEV17ePCZqHxDQqRDbpRc1yJVYtDI2MvbRq5DvOj90uRt6zKn9qtWpEueDqG1QORhQ==", + "dependencies": [ + "@deno/shim-deno-test", + "which@4.0.0" + ] + }, + "@esbuild/aix-ppc64@0.23.0": { + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==" + }, + "@esbuild/android-arm64@0.23.0": { + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==" + }, + "@esbuild/android-arm@0.23.0": { + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==" + }, + "@esbuild/android-x64@0.23.0": { + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==" + }, + "@esbuild/darwin-arm64@0.23.0": { + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==" + }, + "@esbuild/darwin-x64@0.23.0": { + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==" + }, + "@esbuild/freebsd-arm64@0.23.0": { + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==" + }, + "@esbuild/freebsd-x64@0.23.0": { + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==" + }, + "@esbuild/linux-arm64@0.23.0": { + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==" + }, + "@esbuild/linux-arm@0.23.0": { + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==" + }, + "@esbuild/linux-ia32@0.23.0": { + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==" + }, + "@esbuild/linux-loong64@0.23.0": { + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==" + }, + "@esbuild/linux-mips64el@0.23.0": { + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==" + }, + "@esbuild/linux-ppc64@0.23.0": { + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==" + }, + "@esbuild/linux-riscv64@0.23.0": { + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==" + }, + "@esbuild/linux-s390x@0.23.0": { + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==" + }, + "@esbuild/linux-x64@0.23.0": { + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==" + }, + "@esbuild/netbsd-x64@0.23.0": { + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==" + }, + "@esbuild/openbsd-arm64@0.23.0": { + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==" + }, + "@esbuild/openbsd-x64@0.23.0": { + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==" + }, + "@esbuild/sunos-x64@0.23.0": { + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==" + }, + "@esbuild/win32-arm64@0.23.0": { + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==" + }, + "@esbuild/win32-ia32@0.23.0": { + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==" + }, + "@esbuild/win32-x64@0.23.0": { + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==" + }, + "@eslint-community/eslint-utils@4.4.0_eslint@8.57.1": { + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": [ + "eslint@8.57.1", + "eslint-visitor-keys@3.4.3" + ] + }, + "@eslint-community/eslint-utils@4.4.0_eslint@9.10.0": { + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": [ + "eslint@9.10.0", + "eslint-visitor-keys@3.4.3" + ] + }, + "@eslint-community/regexpp@4.11.1": { + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==" + }, + "@eslint/config-array@0.18.0": { + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dependencies": [ + "@eslint/object-schema", + "debug@4.3.7", + "minimatch@3.1.2" + ] + }, + "@eslint/eslintrc@2.1.4": { + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": [ + "ajv", + "debug@4.3.7", + "espree@9.6.1_acorn@8.12.1", + "globals@13.24.0", + "ignore", + "import-fresh", + "js-yaml", + "minimatch@3.1.2", + "strip-json-comments" + ] + }, + "@eslint/eslintrc@3.1.0": { + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dependencies": [ + "ajv", + "debug@4.3.7", + "espree@10.1.0_acorn@8.12.1", + "globals@14.0.0", + "ignore", + "import-fresh", + "js-yaml", + "minimatch@3.1.2", + "strip-json-comments" + ] + }, + "@eslint/js@8.57.1": { + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==" + }, + "@eslint/js@9.10.0": { + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==" + }, + "@eslint/object-schema@2.1.4": { + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==" + }, + "@eslint/plugin-kit@0.1.0": { + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dependencies": [ + "levn" + ] + }, + "@fastify/busboy@2.1.1": { + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, + "@humanwhocodes/config-array@0.13.0": { + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dependencies": [ + "@humanwhocodes/object-schema", + "debug@4.3.7", + "minimatch@3.1.2" + ] + }, + "@humanwhocodes/module-importer@1.0.1": { + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/object-schema@2.0.3": { + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + }, + "@humanwhocodes/retry@0.3.0": { + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==" + }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, + "@oakserver/oak@12.6.2": { + "integrity": "sha512-q9LfyC9tWV68me0GEUuA66qbwH8ep0bBdq9V02fePlPmPVUBCAzQQkomyaI/L4Uur+YALVXgLoTaC2rviZ7I4w==", + "dependencies": [ + "@deno/shim-crypto", + "@deno/shim-deno", + "tslib", + "undici" + ] + }, + "@rtsao/scc@1.1.0": { + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==" + }, + "@types/diff@5.2.2": { + "integrity": "sha512-qVqLpd49rmJA2nZzLVsmfS/aiiBpfVE95dHhPVwG0NmSBAt+riPxnj53wq2oBq5m4Q2RF1IWFEUpnZTgrQZfEQ==" + }, + "@types/json5@0.0.29": { + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" + }, + "@ungap/structured-clone@1.2.0": { + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types", + "negotiator" + ] + }, + "acorn-jsx@5.3.2_acorn@8.12.1": { + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dependencies": [ + "acorn" + ] + }, + "acorn@8.12.1": { + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==" + }, + "ajv@6.12.6": { + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": [ + "fast-deep-equal", + "fast-json-stable-stringify", + "json-schema-traverse", + "uri-js" + ] + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-buffer-byte-length@1.0.1": { + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": [ + "call-bind", + "is-array-buffer" + ] + }, + "array-flatten@1.1.1": { + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-includes@3.1.8": { + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-object-atoms", + "get-intrinsic", + "is-string" + ] + }, + "array.prototype.findlastindex@1.2.5": { + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "es-object-atoms", + "es-shim-unscopables" + ] + }, + "array.prototype.flat@1.3.2": { + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-shim-unscopables" + ] + }, + "array.prototype.flatmap@1.3.2": { + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-shim-unscopables" + ] + }, + "arraybuffer.prototype.slice@1.0.3": { + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": [ + "array-buffer-byte-length", + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "get-intrinsic", + "is-array-buffer", + "is-shared-array-buffer" + ] + }, + "available-typed-arrays@1.0.7": { + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": [ + "possible-typed-array-names" + ] + }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "body-parser@1.20.2": { + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": [ + "bytes", + "content-type", + "debug@2.6.9", + "depd", + "destroy", + "http-errors", + "iconv-lite@0.4.24", + "on-finished", + "qs", + "raw-body", + "type-is", + "unpipe" + ] + }, + "brace-expansion@1.1.11": { + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, + "brace-expansion@2.0.1": { + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": [ + "balanced-match" + ] + }, + "bundle-name@4.1.0": { + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dependencies": [ + "run-applescript" + ] + }, + "bytes@3.1.2": { + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind@1.0.7": { + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": [ + "es-define-property", + "es-errors", + "function-bind", + "get-intrinsic", + "set-function-length" + ] + }, + "callsites@3.1.0": { + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles", + "supports-color" + ] + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "content-disposition@0.5.4": { + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": [ + "safe-buffer@5.2.1" + ] + }, + "content-type@1.0.5": { + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie-signature@1.0.6": { + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cookie@0.6.0": { + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "core-util-is@1.0.3": { + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cross-spawn@7.0.3": { + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": [ + "path-key", + "shebang-command", + "which@2.0.2" + ] + }, + "data-view-buffer@1.0.1": { + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": [ + "call-bind", + "es-errors", + "is-data-view" + ] + }, + "data-view-byte-length@1.0.1": { + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": [ + "call-bind", + "es-errors", + "is-data-view" + ] + }, + "data-view-byte-offset@1.0.0": { + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": [ + "call-bind", + "es-errors", + "is-data-view" + ] + }, + "debug@2.6.9": { + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": [ + "ms@2.0.0" + ] + }, + "debug@3.2.7": { + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": [ + "ms@2.1.3" + ] + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms@2.1.3" + ] + }, + "deep-is@0.1.4": { + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "default-browser-id@5.0.0": { + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==" + }, + "default-browser@5.2.1": { + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dependencies": [ + "bundle-name", + "default-browser-id" + ] + }, + "define-data-property@1.1.4": { + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": [ + "es-define-property", + "es-errors", + "gopd" + ] + }, + "define-lazy-prop@3.0.0": { + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==" + }, + "define-properties@1.2.1": { + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": [ + "define-data-property", + "has-property-descriptors", + "object-keys" + ] + }, + "depd@2.0.0": { + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy@1.2.0": { + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "diff@5.2.0": { + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==" + }, + "doctrine@2.1.0": { + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": [ + "esutils" + ] + }, + "doctrine@3.0.0": { + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": [ + "esutils" + ] + }, + "ee-first@1.1.1": { + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl@1.0.2": { + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "es-abstract@1.23.3": { + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": [ + "array-buffer-byte-length", + "arraybuffer.prototype.slice", + "available-typed-arrays", + "call-bind", + "data-view-buffer", + "data-view-byte-length", + "data-view-byte-offset", + "es-define-property", + "es-errors", + "es-object-atoms", + "es-set-tostringtag", + "es-to-primitive", + "function.prototype.name", + "get-intrinsic", + "get-symbol-description", + "globalthis", + "gopd", + "has-property-descriptors", + "has-proto", + "has-symbols", + "hasown", + "internal-slot", + "is-array-buffer", + "is-callable", + "is-data-view", + "is-negative-zero", + "is-regex", + "is-shared-array-buffer", + "is-string", + "is-typed-array", + "is-weakref", + "object-inspect", + "object-keys", + "object.assign", + "regexp.prototype.flags", + "safe-array-concat", + "safe-regex-test", + "string.prototype.trim", + "string.prototype.trimend", + "string.prototype.trimstart", + "typed-array-buffer", + "typed-array-byte-length", + "typed-array-byte-offset", + "typed-array-length", + "unbox-primitive", + "which-typed-array" + ] + }, + "es-define-property@1.0.0": { + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": [ + "get-intrinsic" + ] + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-main@1.3.0": { + "integrity": "sha512-AzORKdz1Zt97TzbYQnIrI3ZiibWpRXUfpo/w0xOJ20GpNYd2bd3MU9m31zS/aJ1TJl6JfLTok83Y8HjNunYT0A==" + }, + "es-object-atoms@1.0.0": { + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": [ + "es-errors" + ] + }, + "es-set-tostringtag@2.0.3": { + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": [ + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "es-shim-unscopables@1.0.2": { + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dependencies": [ + "hasown" + ] + }, + "es-to-primitive@1.2.1": { + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": [ + "is-callable", + "is-date-object", + "is-symbol" + ] + }, + "esbuild@0.23.0": { + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ] + }, + "escape-html@1.0.3": { + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint-import-resolver-node@0.3.9": { + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dependencies": [ + "debug@3.2.7", + "is-core-module", + "resolve" + ] + }, + "eslint-module-utils@2.11.0": { + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", + "dependencies": [ + "debug@3.2.7" + ] + }, + "eslint-plugin-import@2.30.0_eslint@8.57.1": { + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "dependencies": [ + "@rtsao/scc", + "array-includes", + "array.prototype.findlastindex", + "array.prototype.flat", + "array.prototype.flatmap", + "debug@3.2.7", + "doctrine@2.1.0", + "eslint@8.57.1", + "eslint-import-resolver-node", + "eslint-module-utils", + "hasown", + "is-core-module", + "is-glob", + "minimatch@3.1.2", + "object.fromentries", + "object.groupby", + "object.values", + "semver", + "tsconfig-paths" + ] + }, + "eslint-scope@7.2.2": { + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": [ + "esrecurse", + "estraverse" + ] + }, + "eslint-scope@8.0.2": { + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dependencies": [ + "esrecurse", + "estraverse" + ] + }, + "eslint-visitor-keys@3.4.3": { + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + }, + "eslint-visitor-keys@4.0.0": { + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==" + }, + "eslint@8.57.1": { + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dependencies": [ + "@eslint-community/eslint-utils@4.4.0_eslint@8.57.1", + "@eslint-community/regexpp", + "@eslint/eslintrc@2.1.4", + "@eslint/js@8.57.1", + "@humanwhocodes/config-array", + "@humanwhocodes/module-importer", + "@nodelib/fs.walk", + "@ungap/structured-clone", + "ajv", + "chalk", + "cross-spawn", + "debug@4.3.7", + "doctrine@3.0.0", + "escape-string-regexp", + "eslint-scope@7.2.2", + "eslint-visitor-keys@3.4.3", + "espree@9.6.1_acorn@8.12.1", + "esquery", + "esutils", + "fast-deep-equal", + "file-entry-cache@6.0.1", + "find-up", + "glob-parent", + "globals@13.24.0", + "graphemer", + "ignore", + "imurmurhash", + "is-glob", + "is-path-inside", + "js-yaml", + "json-stable-stringify-without-jsonify", + "levn", + "lodash.merge", + "minimatch@3.1.2", + "natural-compare", + "optionator", + "strip-ansi", + "text-table" + ] + }, + "eslint@9.10.0": { + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "dependencies": [ + "@eslint-community/eslint-utils@4.4.0_eslint@9.10.0", + "@eslint-community/regexpp", + "@eslint/config-array", + "@eslint/eslintrc@3.1.0", + "@eslint/js@9.10.0", + "@eslint/plugin-kit", + "@humanwhocodes/module-importer", + "@humanwhocodes/retry", + "@nodelib/fs.walk", + "ajv", + "chalk", + "cross-spawn", + "debug@4.3.7", + "escape-string-regexp", + "eslint-scope@8.0.2", + "eslint-visitor-keys@4.0.0", + "espree@10.1.0_acorn@8.12.1", + "esquery", + "esutils", + "fast-deep-equal", + "file-entry-cache@8.0.0", + "find-up", + "glob-parent", + "ignore", + "imurmurhash", + "is-glob", + "is-path-inside", + "json-stable-stringify-without-jsonify", + "lodash.merge", + "minimatch@3.1.2", + "natural-compare", + "optionator", + "strip-ansi", + "text-table" + ] + }, + "espree@10.1.0_acorn@8.12.1": { + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dependencies": [ + "acorn", + "acorn-jsx", + "eslint-visitor-keys@4.0.0" + ] + }, + "espree@9.6.1_acorn@8.12.1": { + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": [ + "acorn", + "acorn-jsx", + "eslint-visitor-keys@3.4.3" + ] + }, + "esquery@1.6.0": { + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": [ + "estraverse" + ] + }, + "esrecurse@4.3.0": { + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": [ + "estraverse" + ] + }, + "estraverse@5.3.0": { + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils@2.0.3": { + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag@1.8.1": { + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express@4.19.2": { + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": [ + "accepts", + "array-flatten", + "body-parser", + "content-disposition", + "content-type", + "cookie", + "cookie-signature", + "debug@2.6.9", + "depd", + "encodeurl", + "escape-html", + "etag", + "finalhandler", + "fresh", + "http-errors", + "merge-descriptors", + "methods", + "on-finished", + "parseurl", + "path-to-regexp", + "proxy-addr", + "qs", + "range-parser", + "safe-buffer@5.2.1", + "send", + "serve-static", + "setprototypeof", + "statuses", + "type-is", + "utils-merge", + "vary" + ] + }, + "fast-deep-equal@3.1.3": { + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify@2.1.0": { + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein@2.0.6": { + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastq@1.17.1": { + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": [ + "reusify" + ] + }, + "file-entry-cache@6.0.1": { + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": [ + "flat-cache@3.2.0" + ] + }, + "file-entry-cache@8.0.0": { + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": [ + "flat-cache@4.0.1" + ] + }, + "finalhandler@1.2.0": { + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": [ + "debug@2.6.9", + "encodeurl", + "escape-html", + "on-finished", + "parseurl", + "statuses", + "unpipe" + ] + }, + "find-up@5.0.0": { + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "flat-cache@3.2.0": { + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dependencies": [ + "flatted", + "keyv", + "rimraf" + ] + }, + "flat-cache@4.0.1": { + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": [ + "flatted", + "keyv" + ] + }, + "flatted@3.3.1": { + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "for-each@0.3.3": { + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": [ + "is-callable" + ] + }, + "forwarded@0.2.0": { + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh@0.5.2": { + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs.realpath@1.0.0": { + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name@1.1.6": { + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "functions-have-names" + ] + }, + "functions-have-names@1.2.3": { + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "get-intrinsic@1.2.4": { + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": [ + "es-errors", + "function-bind", + "has-proto", + "has-symbols", + "hasown" + ] + }, + "get-port@7.1.0": { + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==" + }, + "get-symbol-description@1.0.2": { + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": [ + "call-bind", + "es-errors", + "get-intrinsic" + ] + }, + "gitignore-parser@0.0.2": { + "integrity": "sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg==" + }, + "glob-parent@6.0.2": { + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": [ + "is-glob" + ] + }, + "glob@7.2.3": { + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": [ + "fs.realpath", + "inflight", + "inherits", + "minimatch@3.1.2", + "once", + "path-is-absolute" + ] + }, + "globals@13.24.0": { + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": [ + "type-fest" + ] + }, + "globals@14.0.0": { + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" + }, + "globalthis@1.0.4": { + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": [ + "define-properties", + "gopd" + ] + }, + "gopd@1.0.1": { + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": [ + "get-intrinsic" + ] + }, + "graphemer@1.4.0": { + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "has-bigints@1.0.2": { + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors@1.0.2": { + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": [ + "es-define-property" + ] + }, + "has-proto@1.0.3": { + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols@1.0.3": { + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, + "http-errors@2.0.0": { + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": [ + "depd", + "inherits", + "setprototypeof", + "statuses", + "toidentifier" + ] + }, + "iconv-lite@0.4.24": { + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": [ + "safer-buffer" + ] + }, + "iconv-lite@0.6.3": { + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": [ + "safer-buffer" + ] + }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "immediate@3.0.6": { + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "import-fresh@3.3.0": { + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": [ + "parent-module", + "resolve-from" + ] + }, + "imurmurhash@0.1.4": { + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "inflight@1.0.6": { + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": [ + "once", + "wrappy" + ] + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "internal-slot@1.0.7": { + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": [ + "es-errors", + "hasown", + "side-channel" + ] + }, + "ipaddr.js@1.9.1": { + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-array-buffer@3.0.4": { + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": [ + "call-bind", + "get-intrinsic" + ] + }, + "is-bigint@1.0.4": { + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": [ + "has-bigints" + ] + }, + "is-boolean-object@1.1.2": { + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": [ + "call-bind", + "has-tostringtag" + ] + }, + "is-callable@1.2.7": { + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module@2.15.1": { + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": [ + "hasown" + ] + }, + "is-data-view@1.0.1": { + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": [ + "is-typed-array" + ] + }, + "is-date-object@1.0.5": { + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": [ + "has-tostringtag" + ] + }, + "is-docker@3.0.0": { + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==" + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-inside-container@1.0.0": { + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": [ + "is-docker" + ] + }, + "is-negative-zero@2.0.3": { + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==" + }, + "is-number-object@1.0.7": { + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": [ + "has-tostringtag" + ] + }, + "is-path-inside@3.0.3": { + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-regex@1.1.4": { + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": [ + "call-bind", + "has-tostringtag" + ] + }, + "is-shared-array-buffer@1.0.3": { + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": [ + "call-bind" + ] + }, + "is-string@1.0.7": { + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": [ + "has-tostringtag" + ] + }, + "is-symbol@1.0.4": { + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": [ + "has-symbols" + ] + }, + "is-typed-array@1.1.13": { + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": [ + "which-typed-array" + ] + }, + "is-weakref@1.0.2": { + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": [ + "call-bind" + ] + }, + "is-wsl@3.1.0": { + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dependencies": [ + "is-inside-container" + ] + }, + "isarray@1.0.0": { + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "isarray@2.0.5": { + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "isexe@3.1.1": { + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" + }, + "js-yaml@4.1.0": { + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": [ + "argparse" + ] + }, + "json-buffer@3.0.1": { + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema-traverse@0.4.1": { + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify@1.0.1": { + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json5@1.0.2": { + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": [ + "minimist" + ] + }, + "jszip@3.7.1": { + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dependencies": [ + "lie", + "pako", + "readable-stream", + "set-immediate-shim" + ] + }, + "keyv@4.5.4": { + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": [ + "json-buffer" + ] + }, + "levn@0.4.1": { + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": [ + "prelude-ls", + "type-check" + ] + }, + "lie@3.3.0": { + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": [ + "immediate" + ] + }, + "locate-path@6.0.0": { + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": [ + "p-locate" + ] + }, + "lodash.merge@4.6.2": { + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "media-typer@0.3.0": { + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors@1.0.1": { + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods@1.1.2": { + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, + "mime@1.6.0": { + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "minimatch@10.0.1": { + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": [ + "brace-expansion@2.0.1" + ] + }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion@1.1.11" + ] + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "ms@2.0.0": { + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "natural-compare@1.4.0": { + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-inspect@1.13.2": { + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + }, + "object-keys@1.1.1": { + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign@4.1.5": { + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": [ + "call-bind", + "define-properties", + "has-symbols", + "object-keys" + ] + }, + "object.fromentries@2.0.8": { + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-object-atoms" + ] + }, + "object.groupby@1.0.3": { + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract" + ] + }, + "object.values@1.2.0": { + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-object-atoms" + ] + }, + "on-finished@2.4.1": { + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": [ + "ee-first" + ] + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, + "open@10.1.0": { + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dependencies": [ + "default-browser", + "define-lazy-prop", + "is-inside-container", + "is-wsl" + ] + }, + "optionator@0.9.4": { + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": [ + "deep-is", + "fast-levenshtein", + "levn", + "prelude-ls", + "type-check", + "word-wrap" + ] + }, + "p-limit@3.1.0": { + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": [ + "yocto-queue" + ] + }, + "p-locate@5.0.0": { + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": [ + "p-limit" + ] + }, + "pako@1.0.11": { + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parent-module@1.0.1": { + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": [ + "callsites" + ] + }, + "parseurl@1.3.3": { + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute@1.0.1": { + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse@1.0.7": { + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-to-regexp@0.1.7": { + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "possible-typed-array-names@1.0.0": { + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" + }, + "prelude-ls@1.2.1": { + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "process-nextick-args@2.0.1": { + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr@2.0.7": { + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": [ + "forwarded", + "ipaddr.js" + ] + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "qs@6.11.0": { + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": [ + "side-channel" + ] + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "range-parser@1.2.1": { + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body@2.5.2": { + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": [ + "bytes", + "http-errors", + "iconv-lite@0.4.24", + "unpipe" + ] + }, + "readable-stream@2.3.8": { + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": [ + "core-util-is", + "inherits", + "isarray@1.0.0", + "process-nextick-args", + "safe-buffer@5.1.2", + "string_decoder", + "util-deprecate" + ] + }, + "regexp.prototype.flags@1.5.2": { + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dependencies": [ + "call-bind", + "define-properties", + "es-errors", + "set-function-name" + ] + }, + "resolve-from@4.0.0": { + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve@1.22.8": { + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": [ + "is-core-module", + "path-parse", + "supports-preserve-symlinks-flag" + ] + }, + "reusify@1.0.4": { + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf@3.0.2": { + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": [ + "glob" + ] + }, + "run-applescript@7.0.0": { + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==" + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" + ] + }, + "safe-array-concat@1.1.2": { + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": [ + "call-bind", + "get-intrinsic", + "has-symbols", + "isarray@2.0.5" + ] + }, + "safe-buffer@5.1.2": { + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test@1.0.3": { + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": [ + "call-bind", + "es-errors", + "is-regex" + ] + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver@6.3.1": { + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "send@0.18.0": { + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": [ + "debug@2.6.9", + "depd", + "destroy", + "encodeurl", + "escape-html", + "etag", + "fresh", + "http-errors", + "mime", + "ms@2.1.3", + "on-finished", + "range-parser", + "statuses" + ] + }, + "serve-static@1.15.0": { + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": [ + "encodeurl", + "escape-html", + "parseurl", + "send" + ] + }, + "set-function-length@1.2.2": { + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": [ + "define-data-property", + "es-errors", + "function-bind", + "get-intrinsic", + "gopd", + "has-property-descriptors" + ] + }, + "set-function-name@2.0.2": { + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": [ + "define-data-property", + "es-errors", + "functions-have-names", + "has-property-descriptors" + ] + }, + "set-immediate-shim@1.0.1": { + "integrity": "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==" + }, + "setprototypeof@1.2.0": { + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "side-channel@1.0.6": { + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": [ + "call-bind", + "es-errors", + "get-intrinsic", + "object-inspect" + ] + }, + "statuses@2.0.1": { + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string.prototype.trim@1.2.9": { + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-object-atoms" + ] + }, + "string.prototype.trimend@1.0.8": { + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-object-atoms" + ] + }, + "string.prototype.trimstart@1.0.8": { + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-object-atoms" + ] + }, + "string_decoder@1.1.1": { + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": [ + "safe-buffer@5.1.2" + ] + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex" + ] + }, + "strip-bom@3.0.0": { + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + }, + "strip-json-comments@3.1.1": { + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "sudo-prompt@9.2.1": { + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, + "supports-preserve-symlinks-flag@1.0.0": { + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "text-table@0.2.0": { + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "toidentifier@1.0.1": { + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tsconfig-paths@3.15.0": { + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dependencies": [ + "@types/json5", + "json5", + "minimist", + "strip-bom" + ] + }, + "tslib@2.3.1": { + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "type-check@0.4.0": { + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": [ + "prelude-ls" + ] + }, + "type-fest@0.20.2": { + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "type-is@1.6.18": { + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": [ + "media-typer", + "mime-types" + ] + }, + "typed-array-buffer@1.0.2": { + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": [ + "call-bind", + "es-errors", + "is-typed-array" + ] + }, + "typed-array-byte-length@1.0.1": { + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": [ + "call-bind", + "for-each", + "gopd", + "has-proto", + "is-typed-array" + ] + }, + "typed-array-byte-offset@1.0.2": { + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": [ + "available-typed-arrays", + "call-bind", + "for-each", + "gopd", + "has-proto", + "is-typed-array" + ] + }, + "typed-array-length@1.0.6": { + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": [ + "call-bind", + "for-each", + "gopd", + "has-proto", + "is-typed-array", + "possible-typed-array-names" + ] + }, + "unbox-primitive@1.0.2": { + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": [ + "call-bind", + "has-bigints", + "has-symbols", + "which-boxed-primitive" + ] + }, + "undici@5.28.4": { + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dependencies": [ + "@fastify/busboy" + ] + }, + "unpipe@1.0.0": { + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "uri-js@4.4.1": { + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": [ + "punycode" + ] + }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge@1.0.1": { + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary@1.1.2": { + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "which-boxed-primitive@1.0.2": { + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": [ + "is-bigint", + "is-boolean-object", + "is-number-object", + "is-string", + "is-symbol" + ] + }, + "which-typed-array@1.1.15": { + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": [ + "available-typed-arrays", + "call-bind", + "for-each", + "gopd", + "has-tostringtag" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe@2.0.0" + ] + }, + "which@4.0.0": { + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": [ + "isexe@3.1.1" + ] + }, + "windmill-client@1.364.0": { + "integrity": "sha512-UjCbBB2IeyMVoDyO16boHEsTus2npVtXyER2pfP0zm7rmp6ko65HA3OXvMzhMVq3hW9RQ5esc3mrkTnb19PIaw==" + }, + "windmill-parser-wasm-csharp@1.437.1": { + "integrity": "sha512-qzB/kUE9JCf1CYFDz+50AI+SUVaZYn3lSgqmJ11Iuibl41AC4EIvfH4zrsOU53lcTOb9b4ZH3D6z9FjCCfqWsw==" + }, + "windmill-parser-wasm-go@1.429.0": { + "integrity": "sha512-M3jeGDqeTyPj9HyyX3msdzMrqIIzlfMfxTMsXS8m7MJp4Cm60qifMxD29Ipxb2B4WdzyGwCSlaBjLsXu0b3c5g==" + }, + "windmill-parser-wasm-php@1.429.0": { + "integrity": "sha512-SGJAtNpfdRZftkGboxWsm/yQDnJBJodwPQUbX2cWk/aoNook6ULesZwsYtBC9WN1VH6TIskLiVPohMmu6jtXmw==" + }, + "windmill-parser-wasm-py@1.477.1": { + "integrity": "sha512-EY3mSMWpqFPzd7fsLg2/hSfQFU8HpW9nplFwm4JHHCDbcTpBzlvzjPJoHAAGO5kMzowAxjqi5ai/mXjeUWuiSg==" + }, + "windmill-parser-wasm-regex@1.439.0": { + "integrity": "sha512-v7vcEOWurGbqvoTdtQ8wauyUYeuQExRCmr7phPtwUwD+1cNbqDrS11kM8p2Na3DXjL9RqGAGjd6uRjNSccZjjQ==" + }, + "windmill-parser-wasm-rust@1.429.0": { + "integrity": "sha512-c8mjpiw8RxoaBDtecb+sKeWM/IOjNr4Y06nHudGu8sMM48MNO1LhgcISLv8wl6Z9zWd7OzQrECJ6RLorpii5Uw==" + }, + "windmill-parser-wasm-ts@1.438.2": { + "integrity": "sha512-PC1KzhJ47Y3fa4XV3uHtUQN52N2WZCA6OxVopwZIZtwaiRS+MY4zhPFUKqe3rc7uoSkzk3qhkIJoggygBBXLCw==" + }, + "windmill-parser-wasm-yaml@1.429.0": { + "integrity": "sha512-elQYkaWOvzB8LiwVV9NbNqrupSmdtRY3mMEl+qmKTJhGLvYrVOxA8zyBtwGK0MGiFhTOO3ZO96LWSlDfBnpN9g==" + }, + "word-wrap@1.2.5": { + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws@8.18.0": { + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" + }, + "yocto-queue@0.1.0": { + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } }, - "remote": {}, "workspace": { "dependencies": [ - "jsr:@deno/dnt@^0.41.3", + "jsr:@deno/dnt@~0.41.3", "jsr:@std/encoding@^1.0.4", "jsr:@std/fs@^1.0.3", - "jsr:@std/io@^0.224.7", - "jsr:@std/log@^0.224.7", + "jsr:@std/io@~0.224.7", + "jsr:@std/log@~0.224.7", "jsr:@std/net@^1.0.2", "jsr:@std/path@^1.0.4", "jsr:@std/streams@^1.0.4", diff --git a/cli/deps.ts b/cli/deps.ts index 53e96a4f9c..23fd292365 100644 --- a/cli/deps.ts +++ b/cli/deps.ts @@ -15,7 +15,7 @@ export { CompletionsCommand } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5 export { ensureDir } from "jsr:@std/fs"; export { SEPARATOR as SEP } from "jsr:@std/path"; export * as path from "jsr:@std/path"; -export { encodeHex } from "jsr:@std/encoding"; +export { encodeHex } from "jsr:@std/encoding@1.0.4"; export { writeAllSync } from "jsr:@std/io/write-all"; export { copy } from "jsr:@std/io/copy"; export { readAll } from "jsr:@std/io/read-all"; diff --git a/cli/dnt.ts b/cli/dnt.ts index d711434b88..3820439a7e 100644 --- a/cli/dnt.ts +++ b/cli/dnt.ts @@ -13,9 +13,18 @@ await build({ }, ], outDir: "./npm", - shims: { + shims: { // see JS docs for overview and more options deno: true, + // shims to only use in the tests + customDev: [{ + // this is what `timers: "dev"` does internally + package: { + name: "@deno/shim-timers", + version: "~0.1.0", + }, + globalNames: ["setTimeout", "setInterval"], + }], }, scriptModule: false, filterDiagnostic(diagnostic) { @@ -50,12 +59,26 @@ await build({ postBuild() { // steps to run after building and before running the tests // add shebang to npm/esm/main.js + const dirs = [ + "nu", + "ts", + "regex", + "python", + "go", + "php", + "rust", + "yaml", + "csharp", + "java", + ]; + for (const l of dirs) { + Deno.copyFileSync( + "wasm/" + l + "/windmill_parser_wasm_bg.wasm", + "npm/esm/wasm/" + l + "/windmill_parser_wasm_bg.wasm" + ); + } Deno.copyFileSync("../LICENSE", "npm/LICENSE"); Deno.copyFileSync("README.md", "npm/README.md"); - Deno.copyFileSync( - "wasm/windmill_parser_wasm_bg.wasm", - "npm/esm/wasm/windmill_parser_wasm_bg.wasm" - ); }, }); diff --git a/cli/flow.ts b/cli/flow.ts index edca024f66..121e81f72a 100644 --- a/cli/flow.ts +++ b/cli/flow.ts @@ -245,7 +245,7 @@ async function generateLocks( const ignore = await ignoreF(opts); const elems = Object.keys( await elementsToMap( - await FSFSElement(Deno.cwd(), []), + await FSFSElement(Deno.cwd(), [], true), (p, isD) => { return ( ignore(p, isD) || diff --git a/cli/gen/core/ApiError.ts b/cli/gen/core/ApiError.ts deleted file mode 100644 index 81aa78a668..0000000000 --- a/cli/gen/core/ApiError.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ApiRequestOptions } from './ApiRequestOptions.ts'; -import type { ApiResult } from './ApiResult.ts'; - -export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: unknown; - public readonly request: ApiRequestOptions; - - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); - - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } -} \ No newline at end of file diff --git a/cli/gen/core/ApiRequestOptions.ts b/cli/gen/core/ApiRequestOptions.ts deleted file mode 100644 index 939a0aa4c8..0000000000 --- a/cli/gen/core/ApiRequestOptions.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type ApiRequestOptions = { - readonly body?: any; - readonly cookies?: Record; - readonly errors?: Record; - readonly formData?: Record | any[] | Blob | File; - readonly headers?: Record; - readonly mediaType?: string; - readonly method: - | 'DELETE' - | 'GET' - | 'HEAD' - | 'OPTIONS' - | 'PATCH' - | 'POST' - | 'PUT'; - readonly path?: Record; - readonly query?: Record; - readonly responseHeader?: string; - readonly responseTransformer?: (data: unknown) => Promise; - readonly url: string; -}; \ No newline at end of file diff --git a/cli/gen/core/ApiResult.ts b/cli/gen/core/ApiResult.ts deleted file mode 100644 index 4c58e39138..0000000000 --- a/cli/gen/core/ApiResult.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type ApiResult = { - readonly body: TData; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly url: string; -}; \ No newline at end of file diff --git a/cli/gen/core/CancelablePromise.ts b/cli/gen/core/CancelablePromise.ts deleted file mode 100644 index ccc082e8f2..0000000000 --- a/cli/gen/core/CancelablePromise.ts +++ /dev/null @@ -1,126 +0,0 @@ -export class CancelError extends Error { - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } -} - -export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; - - (cancelHandler: () => void): void; -} - -export class CancelablePromise implements Promise { - private _isResolved: boolean; - private _isRejected: boolean; - private _isCancelled: boolean; - readonly cancelHandlers: (() => void)[]; - readonly promise: Promise; - private _resolve?: (value: T | PromiseLike) => void; - private _reject?: (reason?: unknown) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: unknown) => void, - onCancel: OnCancel - ) => void - ) { - this._isResolved = false; - this._isRejected = false; - this._isCancelled = false; - this.cancelHandlers = []; - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isResolved = true; - if (this._resolve) this._resolve(value); - }; - - const onReject = (reason?: unknown): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isRejected = true; - if (this._reject) this._reject(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this.cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this._isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this._isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this._isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null - ): Promise { - return this.promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: unknown) => TResult | PromiseLike) | null - ): Promise { - return this.promise.catch(onRejected); - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.promise.finally(onFinally); - } - - public cancel(): void { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isCancelled = true; - if (this.cancelHandlers.length) { - try { - for (const cancelHandler of this.cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } - } - this.cancelHandlers.length = 0; - if (this._reject) this._reject(new CancelError('Request aborted')); - } - - public get isCancelled(): boolean { - return this._isCancelled; - } -} \ No newline at end of file diff --git a/cli/gen/core/OpenAPI.ts b/cli/gen/core/OpenAPI.ts deleted file mode 100644 index fda891b01d..0000000000 --- a/cli/gen/core/OpenAPI.ts +++ /dev/null @@ -1,63 +0,0 @@ -const getEnv = (key: string) => { - return Deno.env.get(key) -}; - -const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://localhost:8000"; -const baseUrlApi = (baseUrl ?? '') + "/api"; - -import type { ApiRequestOptions } from './ApiRequestOptions.ts'; - -type Headers = Record; -type Middleware = (value: T) => T | Promise; -type Resolver = (options: ApiRequestOptions) => Promise; - -export class Interceptors { - _fns: Middleware[]; - - constructor() { - this._fns = []; - } - - eject(fn: Middleware): void { - const index = this._fns.indexOf(fn); - if (index !== -1) { - this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; - } - } - - use(fn: Middleware): void { - this._fns = [...this._fns, fn]; - } -} - -export type OpenAPIConfig = { - BASE: string; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - ENCODE_PATH?: ((path: string) => string) | undefined; - HEADERS?: Headers | Resolver | undefined; - PASSWORD?: string | Resolver | undefined; - TOKEN?: string | Resolver | undefined; - USERNAME?: string | Resolver | undefined; - VERSION: string; - WITH_CREDENTIALS: boolean; - interceptors: { - request: Interceptors; - response: Interceptors; - }; -}; - -export const OpenAPI: OpenAPIConfig = { - BASE: baseUrlApi, - CREDENTIALS: 'include', - ENCODE_PATH: undefined, - HEADERS: undefined, - PASSWORD: undefined, - TOKEN: getEnv("WM_TOKEN"), - USERNAME: undefined, - VERSION: '1.465.0', - WITH_CREDENTIALS: true, - interceptors: { - request: new Interceptors(), - response: new Interceptors(), - }, -}; \ No newline at end of file diff --git a/cli/gen/core/request.ts b/cli/gen/core/request.ts deleted file mode 100644 index ed11eb4482..0000000000 --- a/cli/gen/core/request.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { ApiError } from './ApiError.ts'; -import type { ApiRequestOptions } from './ApiRequestOptions.ts'; -import type { ApiResult } from './ApiResult.ts'; -import { CancelablePromise } from './CancelablePromise.ts'; -import type { OnCancel } from './CancelablePromise.ts'; -import type { OpenAPIConfig } from './OpenAPI.ts'; - -export const isString = (value: unknown): value is string => { - return typeof value === 'string'; -}; - -export const isStringWithValue = (value: unknown): value is string => { - return isString(value) && value !== ''; -}; - -export const isBlob = (value: any): value is Blob => { - return value instanceof Blob; -}; - -export const isFormData = (value: unknown): value is FormData => { - return value instanceof FormData; -}; - -export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; - -export const getQueryString = (params: Record): string => { - const qs: string[] = []; - - const append = (key: string, value: unknown) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const encodePair = (key: string, value: unknown) => { - if (value === undefined || value === null) { - return; - } - - if (value instanceof Date) { - append(key, value.toISOString()); - } else if (Array.isArray(value)) { - value.forEach(v => encodePair(key, v)); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); - } else { - append(key, value); - } - }; - - Object.entries(params).forEach(([key, value]) => encodePair(key, value)); - - return qs.length ? `?${qs.join('&')}` : ''; -}; - -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = config.BASE + path; - return options.query ? url + getQueryString(options.query) : url; -}; - -export const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: unknown) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([, value]) => value !== undefined && value !== null) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(v => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; -}; - -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { - const [token, username, password, additionalHeaders] = await Promise.all([ - // @ts-ignore - resolve(options, config.TOKEN), - // @ts-ignore - resolve(options, config.USERNAME), - // @ts-ignore - resolve(options, config.PASSWORD), - // @ts-ignore - resolve(options, config.HEADERS), - ]); - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - }) - .filter(([, value]) => value !== undefined && value !== null) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; - } - - if (options.body !== undefined) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } - - return new Headers(headers); -}; - -export const getRequestBody = (options: ApiRequestOptions): unknown => { - if (options.body !== undefined) { - if (options.mediaType?.includes('application/json') || options.mediaType?.includes('+json')) { - return JSON.stringify(options.body); - } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { - return options.body; - } else { - return JSON.stringify(options.body); - } - } - return undefined; -}; - -export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Headers, - onCancel: OnCancel -): Promise => { - const controller = new AbortController(); - - let request: RequestInit = { - headers, - body: body ?? formData, - method: options.method, - signal: controller.signal, - }; - - if (config.WITH_CREDENTIALS) { - request.credentials = config.CREDENTIALS; - } - - for (const fn of config.interceptors.request._fns) { - request = await fn(request); - } - - onCancel(() => controller.abort()); - - return await fetch(url, request); -}; - -export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { - if (responseHeader) { - const content = response.headers.get(responseHeader); - if (isString(content)) { - return content; - } - } - return undefined; -}; - -export const getResponseBody = async (response: Response): Promise => { - if (response.status !== 204) { - try { - const contentType = response.headers.get('Content-Type'); - if (contentType) { - const binaryTypes = ['application/octet-stream', 'application/pdf', 'application/zip', 'audio/', 'image/', 'video/']; - if (contentType.includes('application/json') || contentType.includes('+json')) { - return await response.json(); - } else if (binaryTypes.some(type => contentType.includes(type))) { - return await response.blob(); - } else if (contentType.includes('multipart/form-data')) { - return await response.formData(); - } else if (contentType.includes('text/')) { - return await response.text(); - } - } - } catch (error) { - console.error(error); - } - } - return undefined; -}; - -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Payload Too Large', - 414: 'URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Range Not Satisfiable', - 417: 'Expectation Failed', - 418: 'Im a teapot', - 421: 'Misdirected Request', - 422: 'Unprocessable Content', - 423: 'Locked', - 424: 'Failed Dependency', - 425: 'Too Early', - 426: 'Upgrade Required', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - 451: 'Unavailable For Legal Reasons', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 506: 'Variant Also Negotiates', - 507: 'Insufficient Storage', - 508: 'Loop Detected', - 510: 'Not Extended', - 511: 'Network Authentication Required', - ...options.errors, - } - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? 'unknown'; - const errorStatusText = result.statusText ?? 'unknown'; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError(options, result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` - ); - } -}; - -/** - * Request method - * @param config The OpenAPI configuration object - * @param options The request options from the service - * @returns CancelablePromise - * @throws ApiError - */ -export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options); - - if (!onCancel.isCancelled) { - let response = await sendRequest(config, options, url, body, formData, headers, onCancel); - - for (const fn of config.interceptors.response._fns) { - response = await fn(response); - } - - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); - - let transformedBody = responseBody; - if (options.responseTransformer && response.ok) { - transformedBody = await options.responseTransformer(responseBody) - } - - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader ?? transformedBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); - } - }); -}; \ No newline at end of file diff --git a/cli/gen/index.ts b/cli/gen/index.ts deleted file mode 100644 index 77b08aeebb..0000000000 --- a/cli/gen/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export { ApiError } from './core/ApiError.ts'; -export { CancelablePromise, CancelError } from './core/CancelablePromise.ts'; -export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI.ts'; -export * from './services.gen.ts'; -export * from './types.gen.ts'; \ No newline at end of file diff --git a/cli/gen/services.gen.ts b/cli/gen/services.gen.ts deleted file mode 100644 index 0eefeb39a4..0000000000 --- a/cli/gen/services.gen.ts +++ /dev/null @@ -1,8507 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { CancelablePromise } from './core/CancelablePromise.ts'; -import { OpenAPI } from './core/OpenAPI.ts'; -import { request as __request } from './core/request.ts'; -import type { BackendVersionResponse, BackendUptodateResponse, GetLicenseIdResponse, GetOpenApiYamlResponse, GetAuditLogData, GetAuditLogResponse, ListAuditLogsData, ListAuditLogsResponse, LoginData, LoginResponse, LogoutResponse, GetUserData, GetUserResponse, UpdateUserData, UpdateUserResponse, IsOwnerOfPathData, IsOwnerOfPathResponse, SetPasswordData, SetPasswordResponse, SetPasswordForUserData, SetPasswordForUserResponse, SetLoginTypeForUserData, SetLoginTypeForUserResponse, CreateUserGloballyData, CreateUserGloballyResponse, GlobalUserUpdateData, GlobalUserUpdateResponse, GlobalUsernameInfoData, GlobalUsernameInfoResponse, GlobalUserRenameData, GlobalUserRenameResponse, GlobalUserDeleteData, GlobalUserDeleteResponse, GlobalUsersOverwriteData, GlobalUsersOverwriteResponse, GlobalUsersExportResponse, DeleteUserData, DeleteUserResponse, ListWorkspacesResponse, IsDomainAllowedResponse, ListUserWorkspacesResponse, ListWorkspacesAsSuperAdminData, ListWorkspacesAsSuperAdminResponse, CreateWorkspaceData, CreateWorkspaceResponse, ExistsWorkspaceData, ExistsWorkspaceResponse, ExistsUsernameData, ExistsUsernameResponse, GetGlobalData, GetGlobalResponse, SetGlobalData, SetGlobalResponse, GetLocalResponse, TestSmtpData, TestSmtpResponse, TestCriticalChannelsData, TestCriticalChannelsResponse, GetCriticalAlertsData, GetCriticalAlertsResponse, AcknowledgeCriticalAlertData, AcknowledgeCriticalAlertResponse, AcknowledgeAllCriticalAlertsResponse, TestLicenseKeyData, TestLicenseKeyResponse, TestObjectStorageConfigData, TestObjectStorageConfigResponse, SendStatsResponse, GetLatestKeyRenewalAttemptResponse, RenewLicenseKeyData, RenewLicenseKeyResponse, CreateCustomerPortalSessionData, CreateCustomerPortalSessionResponse, TestMetadataData, TestMetadataResponse, ListGlobalSettingsResponse, GetCurrentEmailResponse, RefreshUserTokenData, RefreshUserTokenResponse, GetTutorialProgressResponse, UpdateTutorialProgressData, UpdateTutorialProgressResponse, LeaveInstanceResponse, GetUsageResponse, GetRunnableResponse, GlobalWhoamiResponse, ListWorkspaceInvitesResponse, WhoamiData, WhoamiResponse, AcceptInviteData, AcceptInviteResponse, DeclineInviteData, DeclineInviteResponse, InviteUserData, InviteUserResponse, AddUserData, AddUserResponse, DeleteInviteData, DeleteInviteResponse, ArchiveWorkspaceData, ArchiveWorkspaceResponse, UnarchiveWorkspaceData, UnarchiveWorkspaceResponse, DeleteWorkspaceData, DeleteWorkspaceResponse, LeaveWorkspaceData, LeaveWorkspaceResponse, GetWorkspaceNameData, GetWorkspaceNameResponse, ChangeWorkspaceNameData, ChangeWorkspaceNameResponse, ChangeWorkspaceIdData, ChangeWorkspaceIdResponse, ChangeWorkspaceColorData, ChangeWorkspaceColorResponse, WhoisData, WhoisResponse, UpdateOperatorSettingsData, UpdateOperatorSettingsResponse, ExistsEmailData, ExistsEmailResponse, ListUsersAsSuperAdminData, ListUsersAsSuperAdminResponse, ListPendingInvitesData, ListPendingInvitesResponse, GetSettingsData, GetSettingsResponse, GetDeployToData, GetDeployToResponse, GetIsPremiumData, GetIsPremiumResponse, GetPremiumInfoData, GetPremiumInfoResponse, SetAutomaticBillingData, SetAutomaticBillingResponse, GetThresholdAlertData, GetThresholdAlertResponse, SetThresholdAlertData, SetThresholdAlertResponse, EditSlackCommandData, EditSlackCommandResponse, EditTeamsCommandData, EditTeamsCommandResponse, ListAvailableTeamsIdsData, ListAvailableTeamsIdsResponse, ListAvailableTeamsChannelsData, ListAvailableTeamsChannelsResponse, ConnectTeamsData, ConnectTeamsResponse, RunSlackMessageTestJobData, RunSlackMessageTestJobResponse, RunTeamsMessageTestJobData, RunTeamsMessageTestJobResponse, EditDeployToData, EditDeployToResponse, EditAutoInviteData, EditAutoInviteResponse, EditWebhookData, EditWebhookResponse, EditCopilotConfigData, EditCopilotConfigResponse, GetCopilotInfoData, GetCopilotInfoResponse, EditErrorHandlerData, EditErrorHandlerResponse, EditLargeFileStorageConfigData, EditLargeFileStorageConfigResponse, EditWorkspaceGitSyncConfigData, EditWorkspaceGitSyncConfigResponse, EditWorkspaceDeployUiSettingsData, EditWorkspaceDeployUiSettingsResponse, EditWorkspaceDefaultAppData, EditWorkspaceDefaultAppResponse, EditDefaultScriptsData, EditDefaultScriptsResponse, GetDefaultScriptsData, GetDefaultScriptsResponse, SetEnvironmentVariableData, SetEnvironmentVariableResponse, GetWorkspaceEncryptionKeyData, GetWorkspaceEncryptionKeyResponse, SetWorkspaceEncryptionKeyData, SetWorkspaceEncryptionKeyResponse, GetWorkspaceDefaultAppData, GetWorkspaceDefaultAppResponse, GetLargeFileStorageConfigData, GetLargeFileStorageConfigResponse, GetWorkspaceUsageData, GetWorkspaceUsageResponse, GetUsedTriggersData, GetUsedTriggersResponse, ListUsersData, ListUsersResponse, ListUsersUsageData, ListUsersUsageResponse, ListUsernamesData, ListUsernamesResponse, UsernameToEmailData, UsernameToEmailResponse, CreateTokenData, CreateTokenResponse, CreateTokenImpersonateData, CreateTokenImpersonateResponse, DeleteTokenData, DeleteTokenResponse, ListTokensData, ListTokensResponse, GetOidcTokenData, GetOidcTokenResponse, CreateVariableData, CreateVariableResponse, EncryptValueData, EncryptValueResponse, DeleteVariableData, DeleteVariableResponse, UpdateVariableData, UpdateVariableResponse, GetVariableData, GetVariableResponse, GetVariableValueData, GetVariableValueResponse, ExistsVariableData, ExistsVariableResponse, ListVariableData, ListVariableResponse, ListContextualVariablesData, ListContextualVariablesResponse, WorkspaceGetCriticalAlertsData, WorkspaceGetCriticalAlertsResponse, WorkspaceAcknowledgeCriticalAlertData, WorkspaceAcknowledgeCriticalAlertResponse, WorkspaceAcknowledgeAllCriticalAlertsData, WorkspaceAcknowledgeAllCriticalAlertsResponse, WorkspaceMuteCriticalAlertsUiData, WorkspaceMuteCriticalAlertsUiResponse, LoginWithOauthData, LoginWithOauthResponse, ConnectSlackCallbackData, ConnectSlackCallbackResponse, ConnectSlackCallbackInstanceData, ConnectSlackCallbackInstanceResponse, ConnectCallbackData, ConnectCallbackResponse, CreateAccountData, CreateAccountResponse, RefreshTokenData, RefreshTokenResponse, DisconnectAccountData, DisconnectAccountResponse, DisconnectSlackData, DisconnectSlackResponse, DisconnectTeamsData, DisconnectTeamsResponse, ListOauthLoginsResponse, ListOauthConnectsResponse, GetOauthConnectData, GetOauthConnectResponse, SyncTeamsResponse, SendMessageToConversationData, SendMessageToConversationResponse, CreateResourceData, CreateResourceResponse, DeleteResourceData, DeleteResourceResponse, UpdateResourceData, UpdateResourceResponse, UpdateResourceValueData, UpdateResourceValueResponse, GetResourceData, GetResourceResponse, GetResourceValueInterpolatedData, GetResourceValueInterpolatedResponse, GetResourceValueData, GetResourceValueResponse, ExistsResourceData, ExistsResourceResponse, ListResourceData, ListResourceResponse, ListSearchResourceData, ListSearchResourceResponse, ListResourceNamesData, ListResourceNamesResponse, CreateResourceTypeData, CreateResourceTypeResponse, FileResourceTypeToFileExtMapData, FileResourceTypeToFileExtMapResponse, DeleteResourceTypeData, DeleteResourceTypeResponse, UpdateResourceTypeData, UpdateResourceTypeResponse, GetResourceTypeData, GetResourceTypeResponse, ExistsResourceTypeData, ExistsResourceTypeResponse, ListResourceTypeData, ListResourceTypeResponse, ListResourceTypeNamesData, ListResourceTypeNamesResponse, QueryResourceTypesData, QueryResourceTypesResponse, ListHubIntegrationsData, ListHubIntegrationsResponse, ListHubFlowsResponse, GetHubFlowByIdData, GetHubFlowByIdResponse, ListHubAppsResponse, GetHubAppByIdData, GetHubAppByIdResponse, GetPublicAppByCustomPathData, GetPublicAppByCustomPathResponse, GetHubScriptContentByPathData, GetHubScriptContentByPathResponse, GetHubScriptByPathData, GetHubScriptByPathResponse, GetTopHubScriptsData, GetTopHubScriptsResponse, QueryHubScriptsData, QueryHubScriptsResponse, ListSearchScriptData, ListSearchScriptResponse, ListScriptsData, ListScriptsResponse, ListScriptPathsData, ListScriptPathsResponse, CreateDraftData, CreateDraftResponse, DeleteDraftData, DeleteDraftResponse, CreateScriptData, CreateScriptResponse, ToggleWorkspaceErrorHandlerForScriptData, ToggleWorkspaceErrorHandlerForScriptResponse, GetCustomTagsData, GetCustomTagsResponse, GeDefaultTagsResponse, IsDefaultTagsPerWorkspaceResponse, ArchiveScriptByPathData, ArchiveScriptByPathResponse, ArchiveScriptByHashData, ArchiveScriptByHashResponse, DeleteScriptByHashData, DeleteScriptByHashResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, GetScriptByPathData, GetScriptByPathResponse, GetTriggersCountOfScriptData, GetTriggersCountOfScriptResponse, ListTokensOfScriptData, ListTokensOfScriptResponse, GetScriptByPathWithDraftData, GetScriptByPathWithDraftResponse, GetScriptHistoryByPathData, GetScriptHistoryByPathResponse, GetScriptLatestVersionData, GetScriptLatestVersionResponse, UpdateScriptHistoryData, UpdateScriptHistoryResponse, RawScriptByPathData, RawScriptByPathResponse, RawScriptByPathTokenedData, RawScriptByPathTokenedResponse, ExistsScriptByPathData, ExistsScriptByPathResponse, GetScriptByHashData, GetScriptByHashResponse, RawScriptByHashData, RawScriptByHashResponse, GetScriptDeploymentStatusData, GetScriptDeploymentStatusResponse, RunScriptByPathData, RunScriptByPathResponse, OpenaiSyncScriptByPathData, OpenaiSyncScriptByPathResponse, RunWaitResultScriptByPathData, RunWaitResultScriptByPathResponse, RunWaitResultScriptByPathGetData, RunWaitResultScriptByPathGetResponse, OpenaiSyncFlowByPathData, OpenaiSyncFlowByPathResponse, RunWaitResultFlowByPathData, RunWaitResultFlowByPathResponse, ResultByIdData, ResultByIdResponse, ListFlowPathsData, ListFlowPathsResponse, ListSearchFlowData, ListSearchFlowResponse, ListFlowsData, ListFlowsResponse, GetFlowHistoryData, GetFlowHistoryResponse, GetFlowLatestVersionData, GetFlowLatestVersionResponse, GetFlowVersionData, GetFlowVersionResponse, UpdateFlowHistoryData, UpdateFlowHistoryResponse, GetFlowByPathData, GetFlowByPathResponse, GetTriggersCountOfFlowData, GetTriggersCountOfFlowResponse, ListTokensOfFlowData, ListTokensOfFlowResponse, ToggleWorkspaceErrorHandlerForFlowData, ToggleWorkspaceErrorHandlerForFlowResponse, GetFlowByPathWithDraftData, GetFlowByPathWithDraftResponse, ExistsFlowByPathData, ExistsFlowByPathResponse, CreateFlowData, CreateFlowResponse, UpdateFlowData, UpdateFlowResponse, ArchiveFlowByPathData, ArchiveFlowByPathResponse, DeleteFlowByPathData, DeleteFlowByPathResponse, ListRawAppsData, ListRawAppsResponse, ExistsRawAppData, ExistsRawAppResponse, GetRawAppDataData, GetRawAppDataResponse, ListSearchAppData, ListSearchAppResponse, ListAppsData, ListAppsResponse, CreateAppData, CreateAppResponse, ExistsAppData, ExistsAppResponse, GetAppByPathData, GetAppByPathResponse, GetAppLiteByPathData, GetAppLiteByPathResponse, GetAppByPathWithDraftData, GetAppByPathWithDraftResponse, GetAppHistoryByPathData, GetAppHistoryByPathResponse, GetAppLatestVersionData, GetAppLatestVersionResponse, UpdateAppHistoryData, UpdateAppHistoryResponse, GetPublicAppBySecretData, GetPublicAppBySecretResponse, GetPublicResourceData, GetPublicResourceResponse, GetPublicSecretOfAppData, GetPublicSecretOfAppResponse, GetAppByVersionData, GetAppByVersionResponse, CreateRawAppData, CreateRawAppResponse, UpdateRawAppData, UpdateRawAppResponse, DeleteRawAppData, DeleteRawAppResponse, DeleteAppData, DeleteAppResponse, UpdateAppData, UpdateAppResponse, CustomPathExistsData, CustomPathExistsResponse, ExecuteComponentData, ExecuteComponentResponse, RunFlowByPathData, RunFlowByPathResponse, RestartFlowAtStepData, RestartFlowAtStepResponse, RunScriptByHashData, RunScriptByHashResponse, RunScriptPreviewData, RunScriptPreviewResponse, RunCodeWorkflowTaskData, RunCodeWorkflowTaskResponse, RunRawScriptDependenciesData, RunRawScriptDependenciesResponse, RunFlowPreviewData, RunFlowPreviewResponse, ListQueueData, ListQueueResponse, GetQueueCountData, GetQueueCountResponse, GetCompletedCountData, GetCompletedCountResponse, CountCompletedJobsData, CountCompletedJobsResponse, ListFilteredUuidsData, ListFilteredUuidsResponse, CancelSelectionData, CancelSelectionResponse, ListCompletedJobsData, ListCompletedJobsResponse, ListJobsData, ListJobsResponse, GetDbClockResponse, CountJobsByTagData, CountJobsByTagResponse, GetJobData, GetJobResponse, GetRootJobIdData, GetRootJobIdResponse, GetJobLogsData, GetJobLogsResponse, GetJobArgsData, GetJobArgsResponse, GetJobUpdatesData, GetJobUpdatesResponse, GetLogFileFromStoreData, GetLogFileFromStoreResponse, GetFlowDebugInfoData, GetFlowDebugInfoResponse, GetCompletedJobData, GetCompletedJobResponse, GetCompletedJobResultData, GetCompletedJobResultResponse, GetCompletedJobResultMaybeData, GetCompletedJobResultMaybeResponse, DeleteCompletedJobData, DeleteCompletedJobResponse, CancelQueuedJobData, CancelQueuedJobResponse, CancelPersistentQueuedJobsData, CancelPersistentQueuedJobsResponse, ForceCancelQueuedJobData, ForceCancelQueuedJobResponse, CreateJobSignatureData, CreateJobSignatureResponse, GetResumeUrlsData, GetResumeUrlsResponse, GetSlackApprovalPayloadData, GetSlackApprovalPayloadResponse, ResumeSuspendedJobGetData, ResumeSuspendedJobGetResponse, ResumeSuspendedJobPostData, ResumeSuspendedJobPostResponse, SetFlowUserStateData, SetFlowUserStateResponse, GetFlowUserStateData, GetFlowUserStateResponse, ResumeSuspendedFlowAsOwnerData, ResumeSuspendedFlowAsOwnerResponse, CancelSuspendedJobGetData, CancelSuspendedJobGetResponse, CancelSuspendedJobPostData, CancelSuspendedJobPostResponse, GetSuspendedJobFlowData, GetSuspendedJobFlowResponse, PreviewScheduleData, PreviewScheduleResponse, CreateScheduleData, CreateScheduleResponse, UpdateScheduleData, UpdateScheduleResponse, SetScheduleEnabledData, SetScheduleEnabledResponse, DeleteScheduleData, DeleteScheduleResponse, GetScheduleData, GetScheduleResponse, ExistsScheduleData, ExistsScheduleResponse, ListSchedulesData, ListSchedulesResponse, ListSchedulesWithJobsData, ListSchedulesWithJobsResponse, SetDefaultErrorOrRecoveryHandlerData, SetDefaultErrorOrRecoveryHandlerResponse, CreateHttpTriggerData, CreateHttpTriggerResponse, UpdateHttpTriggerData, UpdateHttpTriggerResponse, DeleteHttpTriggerData, DeleteHttpTriggerResponse, GetHttpTriggerData, GetHttpTriggerResponse, ListHttpTriggersData, ListHttpTriggersResponse, ExistsHttpTriggerData, ExistsHttpTriggerResponse, ExistsRouteData, ExistsRouteResponse, CreateWebsocketTriggerData, CreateWebsocketTriggerResponse, UpdateWebsocketTriggerData, UpdateWebsocketTriggerResponse, DeleteWebsocketTriggerData, DeleteWebsocketTriggerResponse, GetWebsocketTriggerData, GetWebsocketTriggerResponse, ListWebsocketTriggersData, ListWebsocketTriggersResponse, ExistsWebsocketTriggerData, ExistsWebsocketTriggerResponse, SetWebsocketTriggerEnabledData, SetWebsocketTriggerEnabledResponse, TestWebsocketConnectionData, TestWebsocketConnectionResponse, CreateKafkaTriggerData, CreateKafkaTriggerResponse, UpdateKafkaTriggerData, UpdateKafkaTriggerResponse, DeleteKafkaTriggerData, DeleteKafkaTriggerResponse, GetKafkaTriggerData, GetKafkaTriggerResponse, ListKafkaTriggersData, ListKafkaTriggersResponse, ExistsKafkaTriggerData, ExistsKafkaTriggerResponse, SetKafkaTriggerEnabledData, SetKafkaTriggerEnabledResponse, TestKafkaConnectionData, TestKafkaConnectionResponse, CreateNatsTriggerData, CreateNatsTriggerResponse, UpdateNatsTriggerData, UpdateNatsTriggerResponse, DeleteNatsTriggerData, DeleteNatsTriggerResponse, GetNatsTriggerData, GetNatsTriggerResponse, ListNatsTriggersData, ListNatsTriggersResponse, ExistsNatsTriggerData, ExistsNatsTriggerResponse, SetNatsTriggerEnabledData, SetNatsTriggerEnabledResponse, TestNatsConnectionData, TestNatsConnectionResponse, CreateSqsTriggerData, CreateSqsTriggerResponse, UpdateSqsTriggerData, UpdateSqsTriggerResponse, DeleteSqsTriggerData, DeleteSqsTriggerResponse, GetSqsTriggerData, GetSqsTriggerResponse, ListSqsTriggersData, ListSqsTriggersResponse, ExistsSqsTriggerData, ExistsSqsTriggerResponse, SetSqsTriggerEnabledData, SetSqsTriggerEnabledResponse, TestSqsConnectionData, TestSqsConnectionResponse, CreateMqttTriggerData, CreateMqttTriggerResponse, UpdateMqttTriggerData, UpdateMqttTriggerResponse, DeleteMqttTriggerData, DeleteMqttTriggerResponse, GetMqttTriggerData, GetMqttTriggerResponse, ListMqttTriggersData, ListMqttTriggersResponse, ExistsMqttTriggerData, ExistsMqttTriggerResponse, SetMqttTriggerEnabledData, SetMqttTriggerEnabledResponse, TestMqttConnectionData, TestMqttConnectionResponse, IsValidPostgresConfigurationData, IsValidPostgresConfigurationResponse, CreateTemplateScriptData, CreateTemplateScriptResponse, GetTemplateScriptData, GetTemplateScriptResponse, ListPostgresReplicationSlotData, ListPostgresReplicationSlotResponse, CreatePostgresReplicationSlotData, CreatePostgresReplicationSlotResponse, DeletePostgresReplicationSlotData, DeletePostgresReplicationSlotResponse, ListPostgresPublicationData, ListPostgresPublicationResponse, GetPostgresPublicationData, GetPostgresPublicationResponse, CreatePostgresPublicationData, CreatePostgresPublicationResponse, UpdatePostgresPublicationData, UpdatePostgresPublicationResponse, DeletePostgresPublicationData, DeletePostgresPublicationResponse, CreatePostgresTriggerData, CreatePostgresTriggerResponse, UpdatePostgresTriggerData, UpdatePostgresTriggerResponse, DeletePostgresTriggerData, DeletePostgresTriggerResponse, GetPostgresTriggerData, GetPostgresTriggerResponse, ListPostgresTriggersData, ListPostgresTriggersResponse, ExistsPostgresTriggerData, ExistsPostgresTriggerResponse, SetPostgresTriggerEnabledData, SetPostgresTriggerEnabledResponse, TestPostgresConnectionData, TestPostgresConnectionResponse, ListInstanceGroupsResponse, GetInstanceGroupData, GetInstanceGroupResponse, CreateInstanceGroupData, CreateInstanceGroupResponse, UpdateInstanceGroupData, UpdateInstanceGroupResponse, DeleteInstanceGroupData, DeleteInstanceGroupResponse, AddUserToInstanceGroupData, AddUserToInstanceGroupResponse, RemoveUserFromInstanceGroupData, RemoveUserFromInstanceGroupResponse, ExportInstanceGroupsResponse, OverwriteInstanceGroupsData, OverwriteInstanceGroupsResponse, ListGroupsData, ListGroupsResponse, ListGroupNamesData, ListGroupNamesResponse, CreateGroupData, CreateGroupResponse, UpdateGroupData, UpdateGroupResponse, DeleteGroupData, DeleteGroupResponse, GetGroupData, GetGroupResponse, AddUserToGroupData, AddUserToGroupResponse, RemoveUserToGroupData, RemoveUserToGroupResponse, ListFoldersData, ListFoldersResponse, ListFolderNamesData, ListFolderNamesResponse, CreateFolderData, CreateFolderResponse, UpdateFolderData, UpdateFolderResponse, DeleteFolderData, DeleteFolderResponse, GetFolderData, GetFolderResponse, GetFolderUsageData, GetFolderUsageResponse, AddOwnerToFolderData, AddOwnerToFolderResponse, RemoveOwnerToFolderData, RemoveOwnerToFolderResponse, ListWorkersData, ListWorkersResponse, ExistsWorkerWithTagData, ExistsWorkerWithTagResponse, GetQueueMetricsResponse, GetCountsOfJobsWaitingPerTagResponse, ListWorkerGroupsResponse, GetConfigData, GetConfigResponse, UpdateConfigData, UpdateConfigResponse, DeleteConfigData, DeleteConfigResponse, ListConfigsResponse, ListAutoscalingEventsData, ListAutoscalingEventsResponse, GetGranularAclsData, GetGranularAclsResponse, AddGranularAclsData, AddGranularAclsResponse, RemoveGranularAclsData, RemoveGranularAclsResponse, SetCaptureConfigData, SetCaptureConfigResponse, PingCaptureConfigData, PingCaptureConfigResponse, GetCaptureConfigsData, GetCaptureConfigsResponse, ListCapturesData, ListCapturesResponse, GetCaptureData, GetCaptureResponse, DeleteCaptureData, DeleteCaptureResponse, StarData, StarResponse, UnstarData, UnstarResponse, GetInputHistoryData, GetInputHistoryResponse, GetArgsFromHistoryOrSavedInputData, GetArgsFromHistoryOrSavedInputResponse, ListInputsData, ListInputsResponse, CreateInputData, CreateInputResponse, UpdateInputData, UpdateInputResponse, DeleteInputData, DeleteInputResponse, DuckdbConnectionSettingsData, DuckdbConnectionSettingsResponse, DuckdbConnectionSettingsV2Data, DuckdbConnectionSettingsV2Response, PolarsConnectionSettingsData, PolarsConnectionSettingsResponse, PolarsConnectionSettingsV2Data, PolarsConnectionSettingsV2Response, S3ResourceInfoData, S3ResourceInfoResponse, DatasetStorageTestConnectionData, DatasetStorageTestConnectionResponse, ListStoredFilesData, ListStoredFilesResponse, LoadFileMetadataData, LoadFileMetadataResponse, LoadFilePreviewData, LoadFilePreviewResponse, LoadParquetPreviewData, LoadParquetPreviewResponse, LoadTableRowCountData, LoadTableRowCountResponse, LoadCsvPreviewData, LoadCsvPreviewResponse, DeleteS3FileData, DeleteS3FileResponse, MoveS3FileData, MoveS3FileResponse, FileUploadData, FileUploadResponse, FileDownloadData, FileDownloadResponse, FileDownloadParquetAsCsvData, FileDownloadParquetAsCsvResponse, GetJobMetricsData, GetJobMetricsResponse, SetJobProgressData, SetJobProgressResponse, GetJobProgressData, GetJobProgressResponse, ListLogFilesData, ListLogFilesResponse, GetLogFileData, GetLogFileResponse, ListConcurrencyGroupsResponse, DeleteConcurrencyGroupData, DeleteConcurrencyGroupResponse, GetConcurrencyKeyData, GetConcurrencyKeyResponse, ListExtendedJobsData, ListExtendedJobsResponse, SearchJobsIndexData, SearchJobsIndexResponse, SearchLogsIndexData, SearchLogsIndexResponse, CountSearchLogsIndexData, CountSearchLogsIndexResponse, ClearIndexData, ClearIndexResponse } from './types.gen.ts'; - -/** - * get backend version - * @returns string git version of backend - * @throws ApiError - */ -export const backendVersion = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/version' -}); }; - -/** - * is backend up to date - * @returns string is backend up to date - * @throws ApiError - */ -export const backendUptodate = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/uptodate' -}); }; - -/** - * get license id - * @returns string get license id (empty if not ee) - * @throws ApiError - */ -export const getLicenseId = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/ee_license' -}); }; - -/** - * get openapi yaml spec - * @returns string openapi yaml file content - * @throws ApiError - */ -export const getOpenApiYaml = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/openapi.yaml' -}); }; - -/** - * get audit log (requires admin privilege) - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns AuditLog an audit log - * @throws ApiError - */ -export const getAuditLog = (data: GetAuditLogData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/audit/get/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * list audit logs (requires admin privilege) - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.before filter on started before (inclusive) timestamp - * @param data.after filter on created after (exclusive) timestamp - * @param data.username filter on exact username of user - * @param data.operation filter on exact or prefix name of operation - * @param data.operations comma separated list of exact operations to include - * @param data.excludeOperations comma separated list of operations to exclude - * @param data.resource filter on exact or prefix name of resource - * @param data.actionKind filter on type of operation - * @returns AuditLog a list of audit logs - * @throws ApiError - */ -export const listAuditLogs = (data: ListAuditLogsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/audit/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - before: data.before, - after: data.after, - username: data.username, - operation: data.operation, - operations: data.operations, - exclude_operations: data.excludeOperations, - resource: data.resource, - action_kind: data.actionKind - } -}); }; - -/** - * login with password - * @param data The data for the request. - * @param data.requestBody credentials - * @returns string Successfully authenticated. The session ID is returned in a cookie named `token` and as plaintext response. Preferred method of authorization is through the bearer token. The cookie is only for browser convenience. - * - * @throws ApiError - */ -export const login = (data: LoginData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/auth/login', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * logout - * @returns string clear cookies and clear token (if applicable) - * @throws ApiError - */ -export const logout = (): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/auth/logout' -}); }; - -/** - * get user (require admin privilege) - * @param data The data for the request. - * @param data.workspace - * @param data.username - * @returns User user created - * @throws ApiError - */ -export const getUser = (data: GetUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/get/{username}', - path: { - workspace: data.workspace, - username: data.username - } -}); }; - -/** - * update user (require admin privilege) - * @param data The data for the request. - * @param data.workspace - * @param data.username - * @param data.requestBody new user - * @returns string edited user - * @throws ApiError - */ -export const updateUser = (data: UpdateUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/users/update/{username}', - path: { - workspace: data.workspace, - username: data.username - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * is owner of path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean is owner - * @throws ApiError - */ -export const isOwnerOfPath = (data: IsOwnerOfPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/is_owner/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set password - * @param data The data for the request. - * @param data.requestBody set password - * @returns string password set - * @throws ApiError - */ -export const setPassword = (data: SetPasswordData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/setpassword', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set password for a specific user (require super admin) - * @param data The data for the request. - * @param data.user - * @param data.requestBody set password - * @returns string password set - * @throws ApiError - */ -export const setPasswordForUser = (data: SetPasswordForUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/set_password_of/{user}', - path: { - user: data.user - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set login type for a specific user (require super admin) - * @param data The data for the request. - * @param data.user - * @param data.requestBody set login type - * @returns string login type set - * @throws ApiError - */ -export const setLoginTypeForUser = (data: SetLoginTypeForUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/set_login_type/{user}', - path: { - user: data.user - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create user - * @param data The data for the request. - * @param data.requestBody user info - * @returns string user created - * @throws ApiError - */ -export const createUserGlobally = (data: CreateUserGloballyData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/create', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * global update user (require super admin) - * @param data The data for the request. - * @param data.email - * @param data.requestBody new user info - * @returns string user updated - * @throws ApiError - */ -export const globalUserUpdate = (data: GlobalUserUpdateData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/update/{email}', - path: { - email: data.email - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * global username info (require super admin) - * @param data The data for the request. - * @param data.email - * @returns unknown user renamed - * @throws ApiError - */ -export const globalUsernameInfo = (data: GlobalUsernameInfoData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/username_info/{email}', - path: { - email: data.email - } -}); }; - -/** - * global rename user (require super admin) - * @param data The data for the request. - * @param data.email - * @param data.requestBody new username - * @returns string user renamed - * @throws ApiError - */ -export const globalUserRename = (data: GlobalUserRenameData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/rename/{email}', - path: { - email: data.email - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * global delete user (require super admin) - * @param data The data for the request. - * @param data.email - * @returns string user deleted - * @throws ApiError - */ -export const globalUserDelete = (data: GlobalUserDeleteData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/users/delete/{email}', - path: { - email: data.email - } -}); }; - -/** - * global overwrite users (require super admin and EE) - * @param data The data for the request. - * @param data.requestBody List of users - * @returns string Success message - * @throws ApiError - */ -export const globalUsersOverwrite = (data: GlobalUsersOverwriteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/overwrite', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * global export users (require super admin and EE) - * @returns ExportedUser exported users - * @throws ApiError - */ -export const globalUsersExport = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/export' -}); }; - -/** - * delete user (require admin privilege) - * @param data The data for the request. - * @param data.workspace - * @param data.username - * @returns string delete user - * @throws ApiError - */ -export const deleteUser = (data: DeleteUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/users/delete/{username}', - path: { - workspace: data.workspace, - username: data.username - } -}); }; - -/** - * list all workspaces visible to me - * @returns Workspace all workspaces - * @throws ApiError - */ -export const listWorkspaces = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workspaces/list' -}); }; - -/** - * is domain allowed for auto invi - * @returns boolean domain allowed or not - * @throws ApiError - */ -export const isDomainAllowed = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workspaces/allowed_domain_auto_invite' -}); }; - -/** - * list all workspaces visible to me with user info - * @returns UserWorkspaceList workspace with associated username - * @throws ApiError - */ -export const listUserWorkspaces = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workspaces/users' -}); }; - -/** - * list all workspaces as super admin (require to be super admin) - * @param data The data for the request. - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns Workspace workspaces - * @throws ApiError - */ -export const listWorkspacesAsSuperAdmin = (data: ListWorkspacesAsSuperAdminData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workspaces/list_as_superadmin', - query: { - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * create workspace - * @param data The data for the request. - * @param data.requestBody new token - * @returns string token created - * @throws ApiError - */ -export const createWorkspace = (data: CreateWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/workspaces/create', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * exists workspace - * @param data The data for the request. - * @param data.requestBody id of workspace - * @returns boolean status - * @throws ApiError - */ -export const existsWorkspace = (data: ExistsWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/workspaces/exists', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * exists username - * @param data The data for the request. - * @param data.requestBody - * @returns boolean status - * @throws ApiError - */ -export const existsUsername = (data: ExistsUsernameData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/workspaces/exists_username', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get global settings - * @param data The data for the request. - * @param data.key - * @returns unknown status - * @throws ApiError - */ -export const getGlobal = (data: GetGlobalData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/settings/global/{key}', - path: { - key: data.key - } -}); }; - -/** - * post global settings - * @param data The data for the request. - * @param data.key - * @param data.requestBody value set - * @returns string status - * @throws ApiError - */ -export const setGlobal = (data: SetGlobalData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/global/{key}', - path: { - key: data.key - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get local settings - * @returns unknown status - * @throws ApiError - */ -export const getLocal = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/settings/local' -}); }; - -/** - * test smtp - * @param data The data for the request. - * @param data.requestBody test smtp payload - * @returns string status - * @throws ApiError - */ -export const testSmtp = (data: TestSmtpData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/test_smtp', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test critical channels - * @param data The data for the request. - * @param data.requestBody test critical channel payload - * @returns string status - * @throws ApiError - */ -export const testCriticalChannels = (data: TestCriticalChannelsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/test_critical_channels', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Get all critical alerts - * @param data The data for the request. - * @param data.page - * @param data.pageSize - * @param data.acknowledged - * @returns unknown Successfully retrieved all critical alerts - * @throws ApiError - */ -export const getCriticalAlerts = (data: GetCriticalAlertsData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/settings/critical_alerts', - query: { - page: data.page, - page_size: data.pageSize, - acknowledged: data.acknowledged - } -}); }; - -/** - * Acknowledge a critical alert - * @param data The data for the request. - * @param data.id The ID of the critical alert to acknowledge - * @returns string Successfully acknowledged the critical alert - * @throws ApiError - */ -export const acknowledgeCriticalAlert = (data: AcknowledgeCriticalAlertData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/critical_alerts/{id}/acknowledge', - path: { - id: data.id - } -}); }; - -/** - * Acknowledge all unacknowledged critical alerts - * @returns string Successfully acknowledged all unacknowledged critical alerts. - * @throws ApiError - */ -export const acknowledgeAllCriticalAlerts = (): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/critical_alerts/acknowledge_all' -}); }; - -/** - * test license key - * @param data The data for the request. - * @param data.requestBody test license key - * @returns string status - * @throws ApiError - */ -export const testLicenseKey = (data: TestLicenseKeyData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/test_license_key', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test object storage config - * @param data The data for the request. - * @param data.requestBody test object storage config - * @returns string status - * @throws ApiError - */ -export const testObjectStorageConfig = (data: TestObjectStorageConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/test_object_storage_config', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * send stats - * @returns string status - * @throws ApiError - */ -export const sendStats = (): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/send_stats' -}); }; - -/** - * get latest key renewal attempt - * @returns unknown status - * @throws ApiError - */ -export const getLatestKeyRenewalAttempt = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/settings/latest_key_renewal_attempt' -}); }; - -/** - * renew license key - * @param data The data for the request. - * @param data.licenseKey - * @returns string status - * @throws ApiError - */ -export const renewLicenseKey = (data: RenewLicenseKeyData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/renew_license_key', - query: { - license_key: data.licenseKey - } -}); }; - -/** - * create customer portal session - * @param data The data for the request. - * @param data.licenseKey - * @returns string url to portal - * @throws ApiError - */ -export const createCustomerPortalSession = (data: CreateCustomerPortalSessionData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/settings/customer_portal', - query: { - license_key: data.licenseKey - } -}); }; - -/** - * test metadata - * @param data The data for the request. - * @param data.requestBody test metadata - * @returns string status - * @throws ApiError - */ -export const testMetadata = (data: TestMetadataData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/saml/test_metadata', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list global settings - * @returns GlobalSetting list of settings - * @throws ApiError - */ -export const listGlobalSettings = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/settings/list_global' -}); }; - -/** - * get current user email (if logged in) - * @returns string user email - * @throws ApiError - */ -export const getCurrentEmail = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/email' -}); }; - -/** - * refresh the current token - * @param data The data for the request. - * @param data.ifExpiringInLessThanS - * @returns string new token - * @throws ApiError - */ -export const refreshUserToken = (data: RefreshUserTokenData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/refresh_token', - query: { - if_expiring_in_less_than_s: data.ifExpiringInLessThanS - } -}); }; - -/** - * get tutorial progress - * @returns unknown tutorial progress - * @throws ApiError - */ -export const getTutorialProgress = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/tutorial_progress' -}); }; - -/** - * update tutorial progress - * @param data The data for the request. - * @param data.requestBody progress update - * @returns string tutorial progress - * @throws ApiError - */ -export const updateTutorialProgress = (data: UpdateTutorialProgressData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/tutorial_progress', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * leave instance - * @returns string status - * @throws ApiError - */ -export const leaveInstance = (): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/leave_instance' -}); }; - -/** - * get current usage outside of premium workspaces - * @returns number free usage - * @throws ApiError - */ -export const getUsage = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/usage' -}); }; - -/** - * get all runnables in every workspace - * @returns unknown free all runnables - * @throws ApiError - */ -export const getRunnable = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/all_runnables' -}); }; - -/** - * get current global whoami (if logged in) - * @returns GlobalUserInfo user email - * @throws ApiError - */ -export const globalWhoami = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/whoami' -}); }; - -/** - * list all workspace invites - * @returns WorkspaceInvite list all workspace invites - * @throws ApiError - */ -export const listWorkspaceInvites = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/list_invites' -}); }; - -/** - * whoami - * @param data The data for the request. - * @param data.workspace - * @returns User user - * @throws ApiError - */ -export const whoami = (data: WhoamiData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/whoami', - path: { - workspace: data.workspace - } -}); }; - -/** - * accept invite to workspace - * @param data The data for the request. - * @param data.requestBody accept invite - * @returns string status - * @throws ApiError - */ -export const acceptInvite = (data: AcceptInviteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/accept_invite', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * decline invite to workspace - * @param data The data for the request. - * @param data.requestBody decline invite - * @returns string status - * @throws ApiError - */ -export const declineInvite = (data: DeclineInviteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/decline_invite', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * invite user to workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const inviteUser = (data: InviteUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/invite_user', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * add user to workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const addUser = (data: AddUserData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/add_user', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete user invite - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const deleteInvite = (data: DeleteInviteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/delete_invite', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * archive workspace - * @param data The data for the request. - * @param data.workspace - * @returns string status - * @throws ApiError - */ -export const archiveWorkspace = (data: ArchiveWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/archive', - path: { - workspace: data.workspace - } -}); }; - -/** - * unarchive workspace - * @param data The data for the request. - * @param data.workspace - * @returns string status - * @throws ApiError - */ -export const unarchiveWorkspace = (data: UnarchiveWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/workspaces/unarchive/{workspace}', - path: { - workspace: data.workspace - } -}); }; - -/** - * delete workspace (require super admin) - * @param data The data for the request. - * @param data.workspace - * @returns string status - * @throws ApiError - */ -export const deleteWorkspace = (data: DeleteWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/workspaces/delete/{workspace}', - path: { - workspace: data.workspace - } -}); }; - -/** - * leave workspace - * @param data The data for the request. - * @param data.workspace - * @returns string status - * @throws ApiError - */ -export const leaveWorkspace = (data: LeaveWorkspaceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/leave', - path: { - workspace: data.workspace - } -}); }; - -/** - * get workspace name - * @param data The data for the request. - * @param data.workspace - * @returns string status - * @throws ApiError - */ -export const getWorkspaceName = (data: GetWorkspaceNameData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/get_workspace_name', - path: { - workspace: data.workspace - } -}); }; - -/** - * change workspace name - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string status - * @throws ApiError - */ -export const changeWorkspaceName = (data: ChangeWorkspaceNameData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/change_workspace_name', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * change workspace id - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string status - * @throws ApiError - */ -export const changeWorkspaceId = (data: ChangeWorkspaceIdData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/change_workspace_id', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * change workspace id - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string status - * @throws ApiError - */ -export const changeWorkspaceColor = (data: ChangeWorkspaceColorData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/change_workspace_color', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * whois - * @param data The data for the request. - * @param data.workspace - * @param data.username - * @returns User user - * @throws ApiError - */ -export const whois = (data: WhoisData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/whois/{username}', - path: { - workspace: data.workspace, - username: data.username - } -}); }; - -/** - * Update operator settings for a workspace - * Updates the operator settings for a specific workspace. Requires workspace admin privileges. - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string Operator settings updated successfully - * @throws ApiError - */ -export const updateOperatorSettings = (data: UpdateOperatorSettingsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/operator_settings', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * exists email - * @param data The data for the request. - * @param data.email - * @returns boolean user - * @throws ApiError - */ -export const existsEmail = (data: ExistsEmailData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/exists/{email}', - path: { - email: data.email - } -}); }; - -/** - * list all users as super admin (require to be super amdin) - * @param data The data for the request. - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.activeOnly filter only active users - * @returns GlobalUserInfo user - * @throws ApiError - */ -export const listUsersAsSuperAdmin = (data: ListUsersAsSuperAdminData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/list_as_super_admin', - query: { - page: data.page, - per_page: data.perPage, - active_only: data.activeOnly - } -}); }; - -/** - * list pending invites for a workspace - * @param data The data for the request. - * @param data.workspace - * @returns WorkspaceInvite user - * @throws ApiError - */ -export const listPendingInvites = (data: ListPendingInvitesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/list_pending_invites', - path: { - workspace: data.workspace - } -}); }; - -/** - * get settings - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getSettings = (data: GetSettingsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/get_settings', - path: { - workspace: data.workspace - } -}); }; - -/** - * get deploy to - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getDeployTo = (data: GetDeployToData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/get_deploy_to', - path: { - workspace: data.workspace - } -}); }; - -/** - * get if workspace is premium - * @param data The data for the request. - * @param data.workspace - * @returns boolean status - * @throws ApiError - */ -export const getIsPremium = (data: GetIsPremiumData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/is_premium', - path: { - workspace: data.workspace - } -}); }; - -/** - * get premium info - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getPremiumInfo = (data: GetPremiumInfoData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/premium_info', - path: { - workspace: data.workspace - } -}); }; - -/** - * set automatic billing - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody automatic billing - * @returns string status - * @throws ApiError - */ -export const setAutomaticBilling = (data: SetAutomaticBillingData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/set_automatic_billing', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get threshold alert info - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getThresholdAlert = (data: GetThresholdAlertData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/threshold_alert', - path: { - workspace: data.workspace - } -}); }; - -/** - * set threshold alert info - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody threshold alert info - * @returns string status - * @throws ApiError - */ -export const setThresholdAlert = (data: SetThresholdAlertData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/threshold_alert', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit slack command - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const editSlackCommand = (data: EditSlackCommandData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_slack_command', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit teams command - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const editTeamsCommand = (data: EditTeamsCommandData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_teams_command', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list available teams ids - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const listAvailableTeamsIds = (data: ListAvailableTeamsIdsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/available_teams_ids', - path: { - workspace: data.workspace - } -}); }; - -/** - * list available teams channels - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const listAvailableTeamsChannels = (data: ListAvailableTeamsChannelsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/available_teams_channels', - path: { - workspace: data.workspace - } -}); }; - -/** - * connect teams - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody connect teams - * @returns string status - * @throws ApiError - */ -export const connectTeams = (data: ConnectTeamsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/connect_teams', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run a job that sends a message to Slack - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody path to hub script to run and its corresponding args - * @returns unknown status - * @throws ApiError - */ -export const runSlackMessageTestJob = (data: RunSlackMessageTestJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/run_slack_message_test_job', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run a job that sends a message to Teams - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody path to hub script to run and its corresponding args - * @returns unknown status - * @throws ApiError - */ -export const runTeamsMessageTestJob = (data: RunTeamsMessageTestJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/run_teams_message_test_job', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit deploy to - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string status - * @throws ApiError - */ -export const editDeployTo = (data: EditDeployToData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_deploy_to', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit auto invite - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceInvite - * @returns string status - * @throws ApiError - */ -export const editAutoInvite = (data: EditAutoInviteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_auto_invite', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit webhook - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceWebhook - * @returns string status - * @throws ApiError - */ -export const editWebhook = (data: EditWebhookData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_webhook', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit copilot config - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceCopilotConfig - * @returns string status - * @throws ApiError - */ -export const editCopilotConfig = (data: EditCopilotConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_copilot_config', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get copilot info - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getCopilotInfo = (data: GetCopilotInfoData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/get_copilot_info', - path: { - workspace: data.workspace - } -}); }; - -/** - * edit error handler - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody WorkspaceErrorHandler - * @returns string status - * @throws ApiError - */ -export const editErrorHandler = (data: EditErrorHandlerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_error_handler', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit large file storage settings - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody LargeFileStorage info - * @returns unknown status - * @throws ApiError - */ -export const editLargeFileStorageConfig = (data: EditLargeFileStorageConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_large_file_storage_config', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit workspace git sync settings - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Workspace Git sync settings - * @returns unknown status - * @throws ApiError - */ -export const editWorkspaceGitSyncConfig = (data: EditWorkspaceGitSyncConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_git_sync_config', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit workspace deploy ui settings - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Workspace deploy UI settings - * @returns unknown status - * @throws ApiError - */ -export const editWorkspaceDeployUiSettings = (data: EditWorkspaceDeployUiSettingsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_deploy_ui_config', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit default app for workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Workspace default app - * @returns string status - * @throws ApiError - */ -export const editWorkspaceDefaultApp = (data: EditWorkspaceDefaultAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/edit_default_app', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * edit default scripts for workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Workspace default app - * @returns string status - * @throws ApiError - */ -export const editDefaultScripts = (data: EditDefaultScriptsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/default_scripts', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get default scripts for workspace - * @param data The data for the request. - * @param data.workspace - * @returns WorkspaceDefaultScripts status - * @throws ApiError - */ -export const getDefaultScripts = (data: GetDefaultScriptsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/default_scripts', - path: { - workspace: data.workspace - } -}); }; - -/** - * set environment variable - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Workspace default app - * @returns string status - * @throws ApiError - */ -export const setEnvironmentVariable = (data: SetEnvironmentVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/set_environment_variable', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * retrieves the encryption key for this workspace - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getWorkspaceEncryptionKey = (data: GetWorkspaceEncryptionKeyData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/encryption_key', - path: { - workspace: data.workspace - } -}); }; - -/** - * update the encryption key for this workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody New encryption key - * @returns string status - * @throws ApiError - */ -export const setWorkspaceEncryptionKey = (data: SetWorkspaceEncryptionKeyData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/encryption_key', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get default app for workspace - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getWorkspaceDefaultApp = (data: GetWorkspaceDefaultAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/default_app', - path: { - workspace: data.workspace - } -}); }; - -/** - * get large file storage config - * @param data The data for the request. - * @param data.workspace - * @returns LargeFileStorage status - * @throws ApiError - */ -export const getLargeFileStorageConfig = (data: GetLargeFileStorageConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/get_large_file_storage_config', - path: { - workspace: data.workspace - } -}); }; - -/** - * get usage - * @param data The data for the request. - * @param data.workspace - * @returns number usage - * @throws ApiError - */ -export const getWorkspaceUsage = (data: GetWorkspaceUsageData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/usage', - path: { - workspace: data.workspace - } -}); }; - -/** - * get used triggers - * @param data The data for the request. - * @param data.workspace - * @returns unknown status - * @throws ApiError - */ -export const getUsedTriggers = (data: GetUsedTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/used_triggers', - path: { - workspace: data.workspace - } -}); }; - -/** - * list users - * @param data The data for the request. - * @param data.workspace - * @returns User user - * @throws ApiError - */ -export const listUsers = (data: ListUsersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/list', - path: { - workspace: data.workspace - } -}); }; - -/** - * list users usage - * @param data The data for the request. - * @param data.workspace - * @returns UserUsage user - * @throws ApiError - */ -export const listUsersUsage = (data: ListUsersUsageData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/list_usage', - path: { - workspace: data.workspace - } -}); }; - -/** - * list usernames - * @param data The data for the request. - * @param data.workspace - * @returns string user - * @throws ApiError - */ -export const listUsernames = (data: ListUsernamesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/list_usernames', - path: { - workspace: data.workspace - } -}); }; - -/** - * get email from username - * @param data The data for the request. - * @param data.workspace - * @param data.username - * @returns string email - * @throws ApiError - */ -export const usernameToEmail = (data: UsernameToEmailData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/users/username_to_email/{username}', - path: { - workspace: data.workspace, - username: data.username - } -}); }; - -/** - * create token - * @param data The data for the request. - * @param data.requestBody new token - * @returns string token created - * @throws ApiError - */ -export const createToken = (data: CreateTokenData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/tokens/create', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create token to impersonate a user (require superadmin) - * @param data The data for the request. - * @param data.requestBody new token - * @returns string token created - * @throws ApiError - */ -export const createTokenImpersonate = (data: CreateTokenImpersonateData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/users/tokens/impersonate', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete token - * @param data The data for the request. - * @param data.tokenPrefix - * @returns string delete token - * @throws ApiError - */ -export const deleteToken = (data: DeleteTokenData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/users/tokens/delete/{token_prefix}', - path: { - token_prefix: data.tokenPrefix - } -}); }; - -/** - * list token - * @param data The data for the request. - * @param data.excludeEphemeral - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns TruncatedToken truncated token - * @throws ApiError - */ -export const listTokens = (data: ListTokensData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/users/tokens/list', - query: { - exclude_ephemeral: data.excludeEphemeral, - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * get OIDC token (ee only) - * @param data The data for the request. - * @param data.workspace - * @param data.audience - * @returns string new oidc token - * @throws ApiError - */ -export const getOidcToken = (data: GetOidcTokenData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oidc/token/{audience}', - path: { - workspace: data.workspace, - audience: data.audience - } -}); }; - -/** - * create variable - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new variable - * @param data.alreadyEncrypted - * @returns string variable created - * @throws ApiError - */ -export const createVariable = (data: CreateVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/variables/create', - path: { - workspace: data.workspace - }, - query: { - already_encrypted: data.alreadyEncrypted - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * encrypt value - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new variable - * @returns string encrypted value - * @throws ApiError - */ -export const encryptValue = (data: EncryptValueData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/variables/encrypt', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete variable - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string variable deleted - * @throws ApiError - */ -export const deleteVariable = (data: DeleteVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/variables/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update variable - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated variable - * @param data.alreadyEncrypted - * @returns string variable updated - * @throws ApiError - */ -export const updateVariable = (data: UpdateVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/variables/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - already_encrypted: data.alreadyEncrypted - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get variable - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.decryptSecret ask to decrypt secret if this variable is secret - * (if not secret no effect, default: true) - * - * @param data.includeEncrypted ask to include the encrypted value if secret and decrypt secret is not true (default: false) - * - * @returns ListableVariable variable - * @throws ApiError - */ -export const getVariable = (data: GetVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/variables/get/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - decrypt_secret: data.decryptSecret, - include_encrypted: data.includeEncrypted - } -}); }; - -/** - * get variable value - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string variable - * @throws ApiError - */ -export const getVariableValue = (data: GetVariableValueData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/variables/get_value/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * does variable exists at path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean variable - * @throws ApiError - */ -export const existsVariable = (data: ExistsVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/variables/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list variables - * @param data The data for the request. - * @param data.workspace - * @param data.pathStart - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns ListableVariable variable list - * @throws ApiError - */ -export const listVariable = (data: ListVariableData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/variables/list', - path: { - workspace: data.workspace - }, - query: { - path_start: data.pathStart, - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * list contextual variables - * @param data The data for the request. - * @param data.workspace - * @returns ContextualVariable contextual variable list - * @throws ApiError - */ -export const listContextualVariables = (data: ListContextualVariablesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/variables/list_contextual', - path: { - workspace: data.workspace - } -}); }; - -/** - * Get all critical alerts for this workspace - * @param data The data for the request. - * @param data.workspace - * @param data.page - * @param data.pageSize - * @param data.acknowledged - * @returns unknown Successfully retrieved all critical alerts - * @throws ApiError - */ -export const workspaceGetCriticalAlerts = (data: WorkspaceGetCriticalAlertsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/workspaces/critical_alerts', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - page_size: data.pageSize, - acknowledged: data.acknowledged - } -}); }; - -/** - * Acknowledge a critical alert for this workspace - * @param data The data for the request. - * @param data.workspace - * @param data.id The ID of the critical alert to acknowledge - * @returns string Successfully acknowledged the critical alert - * @throws ApiError - */ -export const workspaceAcknowledgeCriticalAlert = (data: WorkspaceAcknowledgeCriticalAlertData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/critical_alerts/{id}/acknowledge', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * Acknowledge all unacknowledged critical alerts for this workspace - * @param data The data for the request. - * @param data.workspace - * @returns string Successfully acknowledged all unacknowledged critical alerts. - * @throws ApiError - */ -export const workspaceAcknowledgeAllCriticalAlerts = (data: WorkspaceAcknowledgeAllCriticalAlertsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/critical_alerts/acknowledge_all', - path: { - workspace: data.workspace - } -}); }; - -/** - * Mute critical alert UI for this workspace - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Boolean flag to mute critical alerts. - * @returns string Successfully updated mute critical alert settings. - * @throws ApiError - */ -export const workspaceMuteCriticalAlertsUi = (data: WorkspaceMuteCriticalAlertsUiData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/workspaces/critical_alerts/mute', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * login with oauth authorization flow - * @param data The data for the request. - * @param data.clientName - * @param data.requestBody Partially filled script - * @returns string Successfully authenticated. The session ID is returned in a cookie named `token` and as plaintext response. Preferred method of authorization is through the bearer token. The cookie is only for browser convenience. - * - * @throws ApiError - */ -export const loginWithOauth = (data: LoginWithOauthData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/oauth/login_callback/{client_name}', - path: { - client_name: data.clientName - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * connect slack callback - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody code endpoint - * @returns string slack token - * @throws ApiError - */ -export const connectSlackCallback = (data: ConnectSlackCallbackData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/connect_slack_callback', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * connect slack callback instance - * @param data The data for the request. - * @param data.requestBody code endpoint - * @returns string success message - * @throws ApiError - */ -export const connectSlackCallbackInstance = (data: ConnectSlackCallbackInstanceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/oauth/connect_slack_callback', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * connect callback - * @param data The data for the request. - * @param data.clientName - * @param data.requestBody code endpoint - * @returns TokenResponse oauth token - * @throws ApiError - */ -export const connectCallback = (data: ConnectCallbackData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/oauth/connect_callback/{client_name}', - path: { - client_name: data.clientName - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create OAuth account - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody code endpoint - * @returns string account set - * @throws ApiError - */ -export const createAccount = (data: CreateAccountData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/create_account', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * refresh token - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody variable path - * @returns string token refreshed - * @throws ApiError - */ -export const refreshToken = (data: RefreshTokenData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/refresh_token/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * disconnect account - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns string disconnected client - * @throws ApiError - */ -export const disconnectAccount = (data: DisconnectAccountData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/disconnect/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * disconnect slack - * @param data The data for the request. - * @param data.workspace - * @returns string disconnected slack - * @throws ApiError - */ -export const disconnectSlack = (data: DisconnectSlackData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/disconnect_slack', - path: { - workspace: data.workspace - } -}); }; - -/** - * disconnect teams - * @param data The data for the request. - * @param data.workspace - * @returns string disconnected teams - * @throws ApiError - */ -export const disconnectTeams = (data: DisconnectTeamsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/oauth/disconnect_teams', - path: { - workspace: data.workspace - } -}); }; - -/** - * list oauth logins - * @returns unknown list of oauth and saml login clients - * @throws ApiError - */ -export const listOauthLogins = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/oauth/list_logins' -}); }; - -/** - * list oauth connects - * @returns string list of oauth connects clients - * @throws ApiError - */ -export const listOauthConnects = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/oauth/list_connects' -}); }; - -/** - * get oauth connect - * @param data The data for the request. - * @param data.client client name - * @returns unknown get - * @throws ApiError - */ -export const getOauthConnect = (data: GetOauthConnectData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/oauth/get_connect/{client}', - path: { - client: data.client - } -}); }; - -/** - * synchronize Microsoft Teams information (teams/channels) - * @returns TeamInfo Teams information successfully synchronized - * @throws ApiError - */ -export const syncTeams = (): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/teams/sync' -}); }; - -/** - * send update to Microsoft Teams activity - * Respond to a Microsoft Teams activity after a workspace command is run - * @param data The data for the request. - * @param data.requestBody - * @returns unknown Activity processed successfully - * @throws ApiError - */ -export const sendMessageToConversation = (data: SendMessageToConversationData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/teams/activities', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create resource - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new resource - * @param data.updateIfExists - * @returns string resource created - * @throws ApiError - */ -export const createResource = (data: CreateResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/resources/create', - path: { - workspace: data.workspace - }, - query: { - update_if_exists: data.updateIfExists - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete resource - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string resource deleted - * @throws ApiError - */ -export const deleteResource = (data: DeleteResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/resources/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update resource - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated resource - * @returns string resource updated - * @throws ApiError - */ -export const updateResource = (data: UpdateResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/resources/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update resource value - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated resource - * @returns string resource value updated - * @throws ApiError - */ -export const updateResourceValue = (data: UpdateResourceValueData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/resources/update_value/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get resource - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns Resource resource - * @throws ApiError - */ -export const getResource = (data: GetResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get resource interpolated (variables and resources are fully unrolled) - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.jobId job id - * @returns unknown resource value - * @throws ApiError - */ -export const getResourceValueInterpolated = (data: GetResourceValueInterpolatedData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/get_value_interpolated/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - job_id: data.jobId - } -}); }; - -/** - * get resource value - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns unknown resource value - * @throws ApiError - */ -export const getResourceValue = (data: GetResourceValueData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/get_value/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * does resource exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean does resource exists - * @throws ApiError - */ -export const existsResource = (data: ExistsResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list resources - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.resourceType resource_types to list from, separated by ',', - * @param data.resourceTypeExclude resource_types to not list from, separated by ',', - * @param data.pathStart - * @returns ListableResource resource list - * @throws ApiError - */ -export const listResource = (data: ListResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - resource_type: data.resourceType, - resource_type_exclude: data.resourceTypeExclude, - path_start: data.pathStart - } -}); }; - -/** - * list resources for search - * @param data The data for the request. - * @param data.workspace - * @returns unknown resource list - * @throws ApiError - */ -export const listSearchResource = (data: ListSearchResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/list_search', - path: { - workspace: data.workspace - } -}); }; - -/** - * list resource names - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns unknown resource list names - * @throws ApiError - */ -export const listResourceNames = (data: ListResourceNamesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/list_names/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * create resource_type - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new resource_type - * @returns string resource_type created - * @throws ApiError - */ -export const createResourceType = (data: CreateResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/resources/type/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get map from resource type to format extension - * @param data The data for the request. - * @param data.workspace - * @returns unknown map from resource type to file ext - * @throws ApiError - */ -export const fileResourceTypeToFileExtMap = (data: FileResourceTypeToFileExtMapData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/file_resource_type_to_file_ext_map', - path: { - workspace: data.workspace - } -}); }; - -/** - * delete resource_type - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string resource_type deleted - * @throws ApiError - */ -export const deleteResourceType = (data: DeleteResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/resources/type/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update resource_type - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated resource_type - * @returns string resource_type updated - * @throws ApiError - */ -export const updateResourceType = (data: UpdateResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/resources/type/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get resource_type - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns ResourceType resource_type deleted - * @throws ApiError - */ -export const getResourceType = (data: GetResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/type/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * does resource_type exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean does resource_type exist - * @throws ApiError - */ -export const existsResourceType = (data: ExistsResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/type/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list resource_types - * @param data The data for the request. - * @param data.workspace - * @returns ResourceType resource_type list - * @throws ApiError - */ -export const listResourceType = (data: ListResourceTypeData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/type/list', - path: { - workspace: data.workspace - } -}); }; - -/** - * list resource_types names - * @param data The data for the request. - * @param data.workspace - * @returns string resource_type list - * @throws ApiError - */ -export const listResourceTypeNames = (data: ListResourceTypeNamesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/resources/type/listnames', - path: { - workspace: data.workspace - } -}); }; - -/** - * query resource types by similarity - * @param data The data for the request. - * @param data.workspace - * @param data.text query text - * @param data.limit query limit - * @returns unknown resource type details - * @throws ApiError - */ -export const queryResourceTypes = (data: QueryResourceTypesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/embeddings/query_resource_types', - path: { - workspace: data.workspace - }, - query: { - text: data.text, - limit: data.limit - } -}); }; - -/** - * list hub integrations - * @param data The data for the request. - * @param data.kind query integrations kind - * @returns unknown integrations details - * @throws ApiError - */ -export const listHubIntegrations = (data: ListHubIntegrationsData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/integrations/hub/list', - query: { - kind: data.kind - } -}); }; - -/** - * list all hub flows - * @returns unknown hub flows list - * @throws ApiError - */ -export const listHubFlows = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/flows/hub/list' -}); }; - -/** - * get hub flow by id - * @param data The data for the request. - * @param data.id - * @returns unknown flow - * @throws ApiError - */ -export const getHubFlowById = (data: GetHubFlowByIdData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/flows/hub/get/{id}', - path: { - id: data.id - } -}); }; - -/** - * list all hub apps - * @returns unknown hub apps list - * @throws ApiError - */ -export const listHubApps = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/apps/hub/list' -}); }; - -/** - * get hub app by id - * @param data The data for the request. - * @param data.id - * @returns unknown app - * @throws ApiError - */ -export const getHubAppById = (data: GetHubAppByIdData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/apps/hub/get/{id}', - path: { - id: data.id - } -}); }; - -/** - * get public app by custom path - * @param data The data for the request. - * @param data.customPath - * @returns unknown app details - * @throws ApiError - */ -export const getPublicAppByCustomPath = (data: GetPublicAppByCustomPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/apps_u/public_app_by_custom_path/{custom_path}', - path: { - custom_path: data.customPath - } -}); }; - -/** - * get hub script content by path - * @param data The data for the request. - * @param data.path - * @returns string script details - * @throws ApiError - */ -export const getHubScriptContentByPath = (data: GetHubScriptContentByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/scripts/hub/get/{path}', - path: { - path: data.path - } -}); }; - -/** - * get full hub script by path - * @param data The data for the request. - * @param data.path - * @returns unknown script details - * @throws ApiError - */ -export const getHubScriptByPath = (data: GetHubScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/scripts/hub/get_full/{path}', - path: { - path: data.path - } -}); }; - -/** - * get top hub scripts - * @param data The data for the request. - * @param data.limit query limit - * @param data.app query scripts app - * @param data.kind query scripts kind - * @returns unknown hub scripts list - * @throws ApiError - */ -export const getTopHubScripts = (data: GetTopHubScriptsData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/scripts/hub/top', - query: { - limit: data.limit, - app: data.app, - kind: data.kind - } -}); }; - -/** - * query hub scripts by similarity - * @param data The data for the request. - * @param data.text query text - * @param data.kind query scripts kind - * @param data.limit query limit - * @param data.app query scripts app - * @returns unknown script details - * @throws ApiError - */ -export const queryHubScripts = (data: QueryHubScriptsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/embeddings/query_hub_scripts', - query: { - text: data.text, - kind: data.kind, - limit: data.limit, - app: data.app - } -}); }; - -/** - * list scripts for search - * @param data The data for the request. - * @param data.workspace - * @returns unknown script list - * @throws ApiError - */ -export const listSearchScript = (data: ListSearchScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/list_search', - path: { - workspace: data.workspace - } -}); }; - -/** - * list all scripts - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.pathStart mask to filter matching starting path - * @param data.pathExact mask to filter exact matching path - * @param data.firstParentHash mask to filter scripts whom first direct parent has exact hash - * @param data.lastParentHash mask to filter scripts whom last parent in the chain has exact hash. - * Beware that each script stores only a limited number of parents. Hence - * the last parent hash for a script is not necessarily its top-most parent. - * To find the top-most parent you will have to jump from last to last hash - * until finding the parent - * - * @param data.parentHash is the hash present in the array of stored parent hashes for this script. - * The same warning applies than for last_parent_hash. A script only store a - * limited number of direct parent - * - * @param data.showArchived (default false) - * show only the archived files. - * when multiple archived hash share the same path, only the ones with the latest create_at - * are - * ed. - * - * @param data.includeWithoutMain (default false) - * include scripts without an exported main function - * - * @param data.includeDraftOnly (default false) - * include scripts that have no deployed version - * - * @param data.isTemplate (default regardless) - * if true show only the templates - * if false show only the non templates - * if not defined, show all regardless of if the script is a template - * - * @param data.kinds (default regardless) - * script kinds to filter, split by comma - * - * @param data.starredOnly (default false) - * show only the starred items - * - * @param data.withDeploymentMsg (default false) - * include deployment message - * - * @returns Script All scripts - * @throws ApiError - */ -export const listScripts = (data: ListScriptsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - order_desc: data.orderDesc, - created_by: data.createdBy, - path_start: data.pathStart, - path_exact: data.pathExact, - first_parent_hash: data.firstParentHash, - last_parent_hash: data.lastParentHash, - parent_hash: data.parentHash, - show_archived: data.showArchived, - include_without_main: data.includeWithoutMain, - include_draft_only: data.includeDraftOnly, - is_template: data.isTemplate, - kinds: data.kinds, - starred_only: data.starredOnly, - with_deployment_msg: data.withDeploymentMsg - } -}); }; - -/** - * list all scripts paths - * @param data The data for the request. - * @param data.workspace - * @returns string list of script paths - * @throws ApiError - */ -export const listScriptPaths = (data: ListScriptPathsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/list_paths', - path: { - workspace: data.workspace - } -}); }; - -/** - * create draft - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns string draft created - * @throws ApiError - */ -export const createDraft = (data: CreateDraftData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/drafts/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete draft - * @param data The data for the request. - * @param data.workspace - * @param data.kind - * @param data.path - * @returns string draft deleted - * @throws ApiError - */ -export const deleteDraft = (data: DeleteDraftData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/drafts/delete/{kind}/{path}', - path: { - workspace: data.workspace, - kind: data.kind, - path: data.path - } -}); }; - -/** - * create script - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Partially filled script - * @returns string script created - * @throws ApiError - */ -export const createScript = (data: CreateScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Toggle ON and OFF the workspace error handler for a given script - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody Workspace error handler enabled - * @returns string error handler toggled - * @throws ApiError - */ -export const toggleWorkspaceErrorHandlerForScript = (data: ToggleWorkspaceErrorHandlerForScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/toggle_workspace_error_handler/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get all instance custom tags (tags are used to dispatch jobs to different worker groups) - * @param data The data for the request. - * @param data.workspace - * @param data.showWorkspaceRestriction - * @returns string list of custom tags - * @throws ApiError - */ -export const getCustomTags = (data: GetCustomTagsData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/custom_tags', - query: { - workspace: data.workspace, - show_workspace_restriction: data.showWorkspaceRestriction - } -}); }; - -/** - * get all instance default tags - * @returns string list of default tags - * @throws ApiError - */ -export const geDefaultTags = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/get_default_tags' -}); }; - -/** - * is default tags per workspace - * @returns boolean is the default tags per workspace - * @throws ApiError - */ -export const isDefaultTagsPerWorkspace = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/is_default_tags_per_workspace' -}); }; - -/** - * archive script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string script archived - * @throws ApiError - */ -export const archiveScriptByPath = (data: ArchiveScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/archive/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * archive script by hash - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @returns Script script details - * @throws ApiError - */ -export const archiveScriptByHash = (data: ArchiveScriptByHashData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/archive/h/{hash}', - path: { - workspace: data.workspace, - hash: data.hash - } -}); }; - -/** - * delete script by hash (erase content but keep hash, require admin) - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @returns Script script details - * @throws ApiError - */ -export const deleteScriptByHash = (data: DeleteScriptByHashData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/delete/h/{hash}', - path: { - workspace: data.workspace, - hash: data.hash - } -}); }; - -/** - * delete script at a given path (require admin) - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string script path - * @throws ApiError - */ -export const deleteScriptByPath = (data: DeleteScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/delete/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.withStarredInfo - * @returns Script script details - * @throws ApiError - */ -export const getScriptByPath = (data: GetScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/get/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - with_starred_info: data.withStarredInfo - } -}); }; - -/** - * get triggers count of script - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns TriggersCount triggers count - * @throws ApiError - */ -export const getTriggersCountOfScript = (data: GetTriggersCountOfScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/get_triggers_count/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get tokens with script scope - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns TruncatedToken tokens list - * @throws ApiError - */ -export const listTokensOfScript = (data: ListTokensOfScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/list_tokens/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get script by path with draft - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns NewScriptWithDraft script details - * @throws ApiError - */ -export const getScriptByPathWithDraft = (data: GetScriptByPathWithDraftData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/get/draft/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get history of a script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns ScriptHistory script history - * @throws ApiError - */ -export const getScriptHistoryByPath = (data: GetScriptHistoryByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/history/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get scripts's latest version (hash) - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns ScriptHistory Script version/hash - * @throws ApiError - */ -export const getScriptLatestVersion = (data: GetScriptLatestVersionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/get_latest_version/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update history of a script - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @param data.path - * @param data.requestBody Script deployment message - * @returns string success - * @throws ApiError - */ -export const updateScriptHistory = (data: UpdateScriptHistoryData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/scripts/history_update/h/{hash}/p/{path}', - path: { - workspace: data.workspace, - hash: data.hash, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * raw script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string script content - * @throws ApiError - */ -export const rawScriptByPath = (data: RawScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/raw/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * raw script by path with a token (mostly used by lsp to be used with import maps to resolve scripts) - * @param data The data for the request. - * @param data.workspace - * @param data.token - * @param data.path - * @returns string script content - * @throws ApiError - */ -export const rawScriptByPathTokened = (data: RawScriptByPathTokenedData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/scripts_u/tokened_raw/{workspace}/{token}/{path}', - path: { - workspace: data.workspace, - token: data.token, - path: data.path - } -}); }; - -/** - * exists script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean does it exists - * @throws ApiError - */ -export const existsScriptByPath = (data: ExistsScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/exists/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get script by hash - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @param data.withStarredInfo - * @returns Script script details - * @throws ApiError - */ -export const getScriptByHash = (data: GetScriptByHashData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/get/h/{hash}', - path: { - workspace: data.workspace, - hash: data.hash - }, - query: { - with_starred_info: data.withStarredInfo - } -}); }; - -/** - * raw script by hash - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string script content - * @throws ApiError - */ -export const rawScriptByHash = (data: RawScriptByHashData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/raw/h/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get script deployment status - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @returns unknown script details - * @throws ApiError - */ -export const getScriptDeploymentStatus = (data: GetScriptDeploymentStatusData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/scripts/deployment_status/h/{hash}', - path: { - workspace: data.workspace, - hash: data.hash - } -}); }; - -/** - * run script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody script args - * @param data.scheduledFor when to schedule this job (leave empty for immediate run) - * @param data.scheduledInSecs schedule the script to execute in the number of seconds starting now - * @param data.skipPreprocessor skip the preprocessor - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.cacheTtl Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.invisibleToOwner make the run invisible to the the script owner (default false) - * @returns string job created - * @throws ApiError - */ -export const runScriptByPath = (data: RunScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - scheduled_for: data.scheduledFor, - scheduled_in_secs: data.scheduledInSecs, - skip_preprocessor: data.skipPreprocessor, - parent_job: data.parentJob, - tag: data.tag, - cache_ttl: data.cacheTtl, - job_id: data.jobId, - invisible_to_owner: data.invisibleToOwner - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run script by path in openai format - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody script args - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.queueLimit The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - * @returns unknown job result - * @throws ApiError - */ -export const openaiSyncScriptByPath = (data: OpenaiSyncScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/openai_sync/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - parent_job: data.parentJob, - job_id: data.jobId, - include_header: data.includeHeader, - queue_limit: data.queueLimit - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run script by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody script args - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.cacheTtl Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.queueLimit The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - * @returns unknown job result - * @throws ApiError - */ -export const runWaitResultScriptByPath = (data: RunWaitResultScriptByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run_wait_result/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - parent_job: data.parentJob, - tag: data.tag, - cache_ttl: data.cacheTtl, - job_id: data.jobId, - include_header: data.includeHeader, - queue_limit: data.queueLimit - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run script by path with get - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.cacheTtl Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.queueLimit The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - * @param data.payload The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent - * `encodeURIComponent(btoa(JSON.stringify({a: 2})))` - * - * @returns unknown job result - * @throws ApiError - */ -export const runWaitResultScriptByPathGet = (data: RunWaitResultScriptByPathGetData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/run_wait_result/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - parent_job: data.parentJob, - tag: data.tag, - cache_ttl: data.cacheTtl, - job_id: data.jobId, - include_header: data.includeHeader, - queue_limit: data.queueLimit, - payload: data.payload - } -}); }; - -/** - * run flow by path and wait until completion in openai format - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody script args - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.queueLimit The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @returns unknown job result - * @throws ApiError - */ -export const openaiSyncFlowByPath = (data: OpenaiSyncFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/openai_sync/f/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - include_header: data.includeHeader, - queue_limit: data.queueLimit, - job_id: data.jobId - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run flow by path and wait until completion - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody script args - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.queueLimit The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @returns unknown job result - * @throws ApiError - */ -export const runWaitResultFlowByPath = (data: RunWaitResultFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run_wait_result/f/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - include_header: data.includeHeader, - queue_limit: data.queueLimit, - job_id: data.jobId - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get job result by id - * @param data The data for the request. - * @param data.workspace - * @param data.flowJobId - * @param data.nodeId - * @returns unknown job result - * @throws ApiError - */ -export const resultById = (data: ResultByIdData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/result_by_id/{flow_job_id}/{node_id}', - path: { - workspace: data.workspace, - flow_job_id: data.flowJobId, - node_id: data.nodeId - } -}); }; - -/** - * list all flow paths - * @param data The data for the request. - * @param data.workspace - * @returns string list of flow paths - * @throws ApiError - */ -export const listFlowPaths = (data: ListFlowPathsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/list_paths', - path: { - workspace: data.workspace - } -}); }; - -/** - * list flows for search - * @param data The data for the request. - * @param data.workspace - * @returns unknown flow list - * @throws ApiError - */ -export const listSearchFlow = (data: ListSearchFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/list_search', - path: { - workspace: data.workspace - } -}); }; - -/** - * list all flows - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.pathStart mask to filter matching starting path - * @param data.pathExact mask to filter exact matching path - * @param data.showArchived (default false) - * show only the archived files. - * when multiple archived hash share the same path, only the ones with the latest create_at - * are displayed. - * - * @param data.starredOnly (default false) - * show only the starred items - * - * @param data.includeDraftOnly (default false) - * include items that have no deployed version - * - * @param data.withDeploymentMsg (default false) - * include deployment message - * - * @returns unknown All flow - * @throws ApiError - */ -export const listFlows = (data: ListFlowsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - order_desc: data.orderDesc, - created_by: data.createdBy, - path_start: data.pathStart, - path_exact: data.pathExact, - show_archived: data.showArchived, - starred_only: data.starredOnly, - include_draft_only: data.includeDraftOnly, - with_deployment_msg: data.withDeploymentMsg - } -}); }; - -/** - * get flow history by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns FlowVersion Flow history - * @throws ApiError - */ -export const getFlowHistory = (data: GetFlowHistoryData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/history/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get flow's latest version - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns FlowVersion Flow version - * @throws ApiError - */ -export const getFlowLatestVersion = (data: GetFlowLatestVersionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/get_latest_version/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get flow version - * @param data The data for the request. - * @param data.workspace - * @param data.version - * @param data.path - * @returns Flow flow details - * @throws ApiError - */ -export const getFlowVersion = (data: GetFlowVersionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/get/v/{version}/p/{path}', - path: { - workspace: data.workspace, - version: data.version, - path: data.path - } -}); }; - -/** - * update flow history - * @param data The data for the request. - * @param data.workspace - * @param data.version - * @param data.path - * @param data.requestBody Flow deployment message - * @returns string success - * @throws ApiError - */ -export const updateFlowHistory = (data: UpdateFlowHistoryData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/flows/history_update/v/{version}/p/{path}', - path: { - workspace: data.workspace, - version: data.version, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get flow by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.withStarredInfo - * @returns Flow flow details - * @throws ApiError - */ -export const getFlowByPath = (data: GetFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/get/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - with_starred_info: data.withStarredInfo - } -}); }; - -/** - * get triggers count of flow - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns TriggersCount triggers count - * @throws ApiError - */ -export const getTriggersCountOfFlow = (data: GetTriggersCountOfFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/get_triggers_count/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get tokens with flow scope - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns TruncatedToken tokens list - * @throws ApiError - */ -export const listTokensOfFlow = (data: ListTokensOfFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/list_tokens/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * Toggle ON and OFF the workspace error handler for a given flow - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody Workspace error handler enabled - * @returns string error handler toggled - * @throws ApiError - */ -export const toggleWorkspaceErrorHandlerForFlow = (data: ToggleWorkspaceErrorHandlerForFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/flows/toggle_workspace_error_handler/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get flow by path with draft - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns unknown flow details with draft - * @throws ApiError - */ -export const getFlowByPathWithDraft = (data: GetFlowByPathWithDraftData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/get/draft/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * exists flow by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean flow details - * @throws ApiError - */ -export const existsFlowByPath = (data: ExistsFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/flows/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * create flow - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Partially filled flow - * @returns string flow created - * @throws ApiError - */ -export const createFlow = (data: CreateFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/flows/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update flow - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody Partially filled flow - * @returns string flow updated - * @throws ApiError - */ -export const updateFlow = (data: UpdateFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/flows/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * archive flow by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody archiveFlow - * @returns string flow archived - * @throws ApiError - */ -export const archiveFlowByPath = (data: ArchiveFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/flows/archive/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete flow by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string flow delete - * @throws ApiError - */ -export const deleteFlowByPath = (data: DeleteFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/flows/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list all raw apps - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.pathStart mask to filter matching starting path - * @param data.pathExact mask to filter exact matching path - * @param data.starredOnly (default false) - * show only the starred items - * - * @returns ListableRawApp All raw apps - * @throws ApiError - */ -export const listRawApps = (data: ListRawAppsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/raw_apps/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - order_desc: data.orderDesc, - created_by: data.createdBy, - path_start: data.pathStart, - path_exact: data.pathExact, - starred_only: data.starredOnly - } -}); }; - -/** - * does an app exisst at path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean app exists - * @throws ApiError - */ -export const existsRawApp = (data: ExistsRawAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/raw_apps/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get app by path - * @param data The data for the request. - * @param data.workspace - * @param data.version - * @param data.path - * @returns string app details - * @throws ApiError - */ -export const getRawAppData = (data: GetRawAppDataData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get_data/{version}/{path}', - path: { - workspace: data.workspace, - version: data.version, - path: data.path - } -}); }; - -/** - * list apps for search - * @param data The data for the request. - * @param data.workspace - * @returns unknown app list - * @throws ApiError - */ -export const listSearchApp = (data: ListSearchAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/list_search', - path: { - workspace: data.workspace - } -}); }; - -/** - * list all apps - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.pathStart mask to filter matching starting path - * @param data.pathExact mask to filter exact matching path - * @param data.starredOnly (default false) - * show only the starred items - * - * @param data.includeDraftOnly (default false) - * include items that have no deployed version - * - * @param data.withDeploymentMsg (default false) - * include deployment message - * - * @returns ListableApp All apps - * @throws ApiError - */ -export const listApps = (data: ListAppsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - order_desc: data.orderDesc, - created_by: data.createdBy, - path_start: data.pathStart, - path_exact: data.pathExact, - starred_only: data.starredOnly, - include_draft_only: data.includeDraftOnly, - with_deployment_msg: data.withDeploymentMsg - } -}); }; - -/** - * create app - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new app - * @returns string app created - * @throws ApiError - */ -export const createApp = (data: CreateAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/apps/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * does an app exisst at path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean app exists - * @throws ApiError - */ -export const existsApp = (data: ExistsAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get app by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.withStarredInfo - * @returns AppWithLastVersion app details - * @throws ApiError - */ -export const getAppByPath = (data: GetAppByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get/p/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - with_starred_info: data.withStarredInfo - } -}); }; - -/** - * get app lite by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns AppWithLastVersion app lite details - * @throws ApiError - */ -export const getAppLiteByPath = (data: GetAppLiteByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get/lite/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get app by path with draft - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns AppWithLastVersionWDraft app details with draft - * @throws ApiError - */ -export const getAppByPathWithDraft = (data: GetAppByPathWithDraftData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get/draft/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get app history by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns AppHistory app history - * @throws ApiError - */ -export const getAppHistoryByPath = (data: GetAppHistoryByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/history/p/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get apps's latest version - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns AppHistory App version - * @throws ApiError - */ -export const getAppLatestVersion = (data: GetAppLatestVersionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get_latest_version/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update app history - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.version - * @param data.requestBody App deployment message - * @returns string success - * @throws ApiError - */ -export const updateAppHistory = (data: UpdateAppHistoryData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/apps/history_update/a/{id}/v/{version}', - path: { - workspace: data.workspace, - id: data.id, - version: data.version - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get public app by secret - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns AppWithLastVersion app details - * @throws ApiError - */ -export const getPublicAppBySecret = (data: GetPublicAppBySecretData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps_u/public_app/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get public resource - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns unknown resource value - * @throws ApiError - */ -export const getPublicResource = (data: GetPublicResourceData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps_u/public_resource/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get public secret of app - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string app secret - * @throws ApiError - */ -export const getPublicSecretOfApp = (data: GetPublicSecretOfAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/secret_of/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get app by version - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns AppWithLastVersion app details - * @throws ApiError - */ -export const getAppByVersion = (data: GetAppByVersionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/get/v/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * create raw app - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new raw app - * @returns string raw app created - * @throws ApiError - */ -export const createRawApp = (data: CreateRawAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/raw_apps/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update app - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updateraw app - * @returns string app updated - * @throws ApiError - */ -export const updateRawApp = (data: UpdateRawAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/raw_apps/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete raw app - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string app deleted - * @throws ApiError - */ -export const deleteRawApp = (data: DeleteRawAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/raw_apps/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * delete app - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string app deleted - * @throws ApiError - */ -export const deleteApp = (data: DeleteAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/apps/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * update app - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody update app - * @returns string app updated - * @throws ApiError - */ -export const updateApp = (data: UpdateAppData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/apps/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * check if custom path exists - * @param data The data for the request. - * @param data.workspace - * @param data.customPath - * @returns boolean custom path exists - * @throws ApiError - */ -export const customPathExists = (data: CustomPathExistsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/apps/custom_path_exists/{custom_path}', - path: { - workspace: data.workspace, - custom_path: data.customPath - } -}); }; - -/** - * executeComponent - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody update app - * @returns string job uuid - * @throws ApiError - */ -export const executeComponent = (data: ExecuteComponentData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/apps_u/execute_component/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run flow by path - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody flow args - * @param data.scheduledFor when to schedule this job (leave empty for immediate run) - * @param data.scheduledInSecs schedule the script to execute in the number of seconds starting now - * @param data.skipPreprocessor skip the preprocessor - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.invisibleToOwner make the run invisible to the the flow owner (default false) - * @returns string job created - * @throws ApiError - */ -export const runFlowByPath = (data: RunFlowByPathData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/f/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - scheduled_for: data.scheduledFor, - scheduled_in_secs: data.scheduledInSecs, - skip_preprocessor: data.skipPreprocessor, - parent_job: data.parentJob, - tag: data.tag, - job_id: data.jobId, - include_header: data.includeHeader, - invisible_to_owner: data.invisibleToOwner - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * restart a completed flow at a given step - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.stepId step id to restart the flow from - * @param data.branchOrIterationN for branchall or loop, the iteration at which the flow should restart - * @param data.requestBody flow args - * @param data.scheduledFor when to schedule this job (leave empty for immediate run) - * @param data.scheduledInSecs schedule the script to execute in the number of seconds starting now - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.invisibleToOwner make the run invisible to the the flow owner (default false) - * @returns string job created - * @throws ApiError - */ -export const restartFlowAtStep = (data: RestartFlowAtStepData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/restart/f/{id}/from/{step_id}/{branch_or_iteration_n}', - path: { - workspace: data.workspace, - id: data.id, - step_id: data.stepId, - branch_or_iteration_n: data.branchOrIterationN - }, - query: { - scheduled_for: data.scheduledFor, - scheduled_in_secs: data.scheduledInSecs, - parent_job: data.parentJob, - tag: data.tag, - job_id: data.jobId, - include_header: data.includeHeader, - invisible_to_owner: data.invisibleToOwner - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run script by hash - * @param data The data for the request. - * @param data.workspace - * @param data.hash - * @param data.requestBody Partially filled args - * @param data.scheduledFor when to schedule this job (leave empty for immediate run) - * @param data.scheduledInSecs schedule the script to execute in the number of seconds starting now - * @param data.skipPreprocessor skip the preprocessor - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.tag Override the tag to use - * @param data.cacheTtl Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.invisibleToOwner make the run invisible to the the script owner (default false) - * @returns string job created - * @throws ApiError - */ -export const runScriptByHash = (data: RunScriptByHashData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/h/{hash}', - path: { - workspace: data.workspace, - hash: data.hash - }, - query: { - scheduled_for: data.scheduledFor, - scheduled_in_secs: data.scheduledInSecs, - skip_preprocessor: data.skipPreprocessor, - parent_job: data.parentJob, - tag: data.tag, - cache_ttl: data.cacheTtl, - job_id: data.jobId, - include_header: data.includeHeader, - invisible_to_owner: data.invisibleToOwner - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run script preview - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody preview - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.invisibleToOwner make the run invisible to the the script owner (default false) - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @returns string job created - * @throws ApiError - */ -export const runScriptPreview = (data: RunScriptPreviewData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/preview', - path: { - workspace: data.workspace - }, - query: { - include_header: data.includeHeader, - invisible_to_owner: data.invisibleToOwner, - job_id: data.jobId - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run code-workflow task - * @param data The data for the request. - * @param data.workspace - * @param data.jobId - * @param data.entrypoint - * @param data.requestBody preview - * @returns string job created - * @throws ApiError - */ -export const runCodeWorkflowTask = (data: RunCodeWorkflowTaskData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/workflow_as_code/{job_id}/{entrypoint}', - path: { - workspace: data.workspace, - job_id: data.jobId, - entrypoint: data.entrypoint - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run a one-off dependencies job - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody raw script content - * @returns unknown dependency job result - * @throws ApiError - */ -export const runRawScriptDependencies = (data: RunRawScriptDependenciesData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/dependencies', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * run flow preview - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody preview - * @param data.includeHeader List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - * @param data.invisibleToOwner make the run invisible to the the script owner (default false) - * @param data.jobId The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - * @returns string job created - * @throws ApiError - */ -export const runFlowPreview = (data: RunFlowPreviewData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/run/preview_flow', - path: { - workspace: data.workspace - }, - query: { - include_header: data.includeHeader, - invisible_to_owner: data.invisibleToOwner, - job_id: data.jobId - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list all queued jobs - * @param data The data for the request. - * @param data.workspace - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.scriptPathExact mask to filter exact matching path - * @param data.scriptPathStart mask to filter matching starting path - * @param data.schedulePath mask to filter by schedule path - * @param data.scriptHash mask to filter exact matching path - * @param data.startedBefore filter on started before (inclusive) timestamp - * @param data.startedAfter filter on started after (exclusive) timestamp - * @param data.success filter on successful jobs - * @param data.scheduledForBeforeNow filter on jobs scheduled_for before now (hence waitinf for a worker) - * @param data.jobKinds filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - * @param data.suspended filter on suspended jobs - * @param data.running filter on running jobs - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.result filter on jobs containing those result as a json subset (@> in postgres) - * @param data.tag filter on jobs with a given tag/worker group - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.allWorkspaces get jobs from all workspaces (only valid if request come from the `admins` workspace) - * @param data.isNotSchedule is not a scheduled job - * @returns QueuedJob All queued jobs - * @throws ApiError - */ -export const listQueue = (data: ListQueueData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/queue/list', - path: { - workspace: data.workspace - }, - query: { - order_desc: data.orderDesc, - created_by: data.createdBy, - parent_job: data.parentJob, - script_path_exact: data.scriptPathExact, - script_path_start: data.scriptPathStart, - schedule_path: data.schedulePath, - script_hash: data.scriptHash, - started_before: data.startedBefore, - started_after: data.startedAfter, - success: data.success, - scheduled_for_before_now: data.scheduledForBeforeNow, - job_kinds: data.jobKinds, - suspended: data.suspended, - running: data.running, - args: data.args, - result: data.result, - tag: data.tag, - page: data.page, - per_page: data.perPage, - all_workspaces: data.allWorkspaces, - is_not_schedule: data.isNotSchedule - } -}); }; - -/** - * get queue count - * @param data The data for the request. - * @param data.workspace - * @param data.allWorkspaces get jobs from all workspaces (only valid if request come from the `admins` workspace) - * @returns unknown queue count - * @throws ApiError - */ -export const getQueueCount = (data: GetQueueCountData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/queue/count', - path: { - workspace: data.workspace - }, - query: { - all_workspaces: data.allWorkspaces - } -}); }; - -/** - * get completed count - * @param data The data for the request. - * @param data.workspace - * @returns unknown completed count - * @throws ApiError - */ -export const getCompletedCount = (data: GetCompletedCountData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/completed/count', - path: { - workspace: data.workspace - } -}); }; - -/** - * count number of completed jobs with filter - * @param data The data for the request. - * @param data.workspace - * @param data.completedAfterSAgo - * @param data.success - * @param data.tags - * @param data.allWorkspaces - * @returns number Count of completed jobs - * @throws ApiError - */ -export const countCompletedJobs = (data: CountCompletedJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/completed/count_jobs', - path: { - workspace: data.workspace - }, - query: { - completed_after_s_ago: data.completedAfterSAgo, - success: data.success, - tags: data.tags, - all_workspaces: data.allWorkspaces - } -}); }; - -/** - * get the ids of all jobs matching the given filters - * @param data The data for the request. - * @param data.workspace - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.scriptPathExact mask to filter exact matching path - * @param data.scriptPathStart mask to filter matching starting path - * @param data.schedulePath mask to filter by schedule path - * @param data.scriptHash mask to filter exact matching path - * @param data.startedBefore filter on started before (inclusive) timestamp - * @param data.startedAfter filter on started after (exclusive) timestamp - * @param data.success filter on successful jobs - * @param data.scheduledForBeforeNow filter on jobs scheduled_for before now (hence waitinf for a worker) - * @param data.jobKinds filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - * @param data.suspended filter on suspended jobs - * @param data.running filter on running jobs - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.result filter on jobs containing those result as a json subset (@> in postgres) - * @param data.tag filter on jobs with a given tag/worker group - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.concurrencyKey - * @param data.allWorkspaces get jobs from all workspaces (only valid if request come from the `admins` workspace) - * @param data.isNotSchedule is not a scheduled job - * @returns string uuids of jobs - * @throws ApiError - */ -export const listFilteredUuids = (data: ListFilteredUuidsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/queue/list_filtered_uuids', - path: { - workspace: data.workspace - }, - query: { - order_desc: data.orderDesc, - created_by: data.createdBy, - parent_job: data.parentJob, - script_path_exact: data.scriptPathExact, - script_path_start: data.scriptPathStart, - schedule_path: data.schedulePath, - script_hash: data.scriptHash, - started_before: data.startedBefore, - started_after: data.startedAfter, - success: data.success, - scheduled_for_before_now: data.scheduledForBeforeNow, - job_kinds: data.jobKinds, - suspended: data.suspended, - running: data.running, - args: data.args, - result: data.result, - tag: data.tag, - page: data.page, - per_page: data.perPage, - concurrency_key: data.concurrencyKey, - all_workspaces: data.allWorkspaces, - is_not_schedule: data.isNotSchedule - } -}); }; - -/** - * cancel jobs based on the given uuids - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody uuids of the jobs to cancel - * @returns string uuids of canceled jobs - * @throws ApiError - */ -export const cancelSelection = (data: CancelSelectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/queue/cancel_selection', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list all completed jobs - * @param data The data for the request. - * @param data.workspace - * @param data.orderDesc order by desc order (default true) - * @param data.createdBy mask to filter exact matching user creator - * @param data.label mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.scriptPathExact mask to filter exact matching path - * @param data.scriptPathStart mask to filter matching starting path - * @param data.schedulePath mask to filter by schedule path - * @param data.scriptHash mask to filter exact matching path - * @param data.startedBefore filter on started before (inclusive) timestamp - * @param data.startedAfter filter on started after (exclusive) timestamp - * @param data.success filter on successful jobs - * @param data.jobKinds filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.result filter on jobs containing those result as a json subset (@> in postgres) - * @param data.tag filter on jobs with a given tag/worker group - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.isSkipped is the job skipped - * @param data.isFlowStep is the job a flow step - * @param data.hasNullParent has null parent - * @param data.isNotSchedule is not a scheduled job - * @returns CompletedJob All completed jobs - * @throws ApiError - */ -export const listCompletedJobs = (data: ListCompletedJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/completed/list', - path: { - workspace: data.workspace - }, - query: { - order_desc: data.orderDesc, - created_by: data.createdBy, - label: data.label, - parent_job: data.parentJob, - script_path_exact: data.scriptPathExact, - script_path_start: data.scriptPathStart, - schedule_path: data.schedulePath, - script_hash: data.scriptHash, - started_before: data.startedBefore, - started_after: data.startedAfter, - success: data.success, - job_kinds: data.jobKinds, - args: data.args, - result: data.result, - tag: data.tag, - page: data.page, - per_page: data.perPage, - is_skipped: data.isSkipped, - is_flow_step: data.isFlowStep, - has_null_parent: data.hasNullParent, - is_not_schedule: data.isNotSchedule - } -}); }; - -/** - * list all jobs - * @param data The data for the request. - * @param data.workspace - * @param data.createdBy mask to filter exact matching user creator - * @param data.label mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.scriptPathExact mask to filter exact matching path - * @param data.scriptPathStart mask to filter matching starting path - * @param data.schedulePath mask to filter by schedule path - * @param data.scriptHash mask to filter exact matching path - * @param data.startedBefore filter on started before (inclusive) timestamp - * @param data.startedAfter filter on started after (exclusive) timestamp - * @param data.createdBefore filter on created before (inclusive) timestamp - * @param data.createdAfter filter on created after (exclusive) timestamp - * @param data.createdOrStartedBefore filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp - * @param data.running filter on running jobs - * @param data.scheduledForBeforeNow filter on jobs scheduled_for before now (hence waitinf for a worker) - * @param data.createdOrStartedAfter filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp - * @param data.createdOrStartedAfterCompletedJobs filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs - * @param data.jobKinds filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - * @param data.suspended filter on suspended jobs - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.tag filter on jobs with a given tag/worker group - * @param data.result filter on jobs containing those result as a json subset (@> in postgres) - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.isSkipped is the job skipped - * @param data.isFlowStep is the job a flow step - * @param data.hasNullParent has null parent - * @param data.success filter on successful jobs - * @param data.allWorkspaces get jobs from all workspaces (only valid if request come from the `admins` workspace) - * @param data.isNotSchedule is not a scheduled job - * @returns Job All jobs - * @throws ApiError - */ -export const listJobs = (data: ListJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/list', - path: { - workspace: data.workspace - }, - query: { - created_by: data.createdBy, - label: data.label, - parent_job: data.parentJob, - script_path_exact: data.scriptPathExact, - script_path_start: data.scriptPathStart, - schedule_path: data.schedulePath, - script_hash: data.scriptHash, - started_before: data.startedBefore, - started_after: data.startedAfter, - created_before: data.createdBefore, - created_after: data.createdAfter, - created_or_started_before: data.createdOrStartedBefore, - running: data.running, - scheduled_for_before_now: data.scheduledForBeforeNow, - created_or_started_after: data.createdOrStartedAfter, - created_or_started_after_completed_jobs: data.createdOrStartedAfterCompletedJobs, - job_kinds: data.jobKinds, - suspended: data.suspended, - args: data.args, - tag: data.tag, - result: data.result, - page: data.page, - per_page: data.perPage, - is_skipped: data.isSkipped, - is_flow_step: data.isFlowStep, - has_null_parent: data.hasNullParent, - success: data.success, - all_workspaces: data.allWorkspaces, - is_not_schedule: data.isNotSchedule - } -}); }; - -/** - * get db clock - * @returns number the timestamp of the db that can be used to compute the drift - * @throws ApiError - */ -export const getDbClock = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/jobs/db_clock' -}); }; - -/** - * Count jobs by tag - * @param data The data for the request. - * @param data.horizonSecs Past Time horizon in seconds (when to start the count = now - horizon) (default is 3600) - * @param data.workspaceId Specific workspace ID to filter results (optional) - * @returns unknown Job counts by tag - * @throws ApiError - */ -export const countJobsByTag = (data: CountJobsByTagData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/jobs/completed/count_by_tag', - query: { - horizon_secs: data.horizonSecs, - workspace_id: data.workspaceId - } -}); }; - -/** - * get job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.noLogs - * @returns Job job details - * @throws ApiError - */ -export const getJob = (data: GetJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - query: { - no_logs: data.noLogs - } -}); }; - -/** - * get root job id - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns string get root job id - * @throws ApiError - */ -export const getRootJobId = (data: GetRootJobIdData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_root_job_id/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * get job logs - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns string job details - * @throws ApiError - */ -export const getJobLogs = (data: GetJobLogsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_logs/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * get job args - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns unknown job args - * @throws ApiError - */ -export const getJobArgs = (data: GetJobArgsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_args/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * get job updates - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.running - * @param data.logOffset - * @param data.getProgress - * @returns unknown job details - * @throws ApiError - */ -export const getJobUpdates = (data: GetJobUpdatesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/getupdate/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - query: { - running: data.running, - log_offset: data.logOffset, - get_progress: data.getProgress - } -}); }; - -/** - * get log file from object store - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns unknown job log - * @throws ApiError - */ -export const getLogFileFromStore = (data: GetLogFileFromStoreData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_log_file/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get flow debug info - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns unknown flow debug info details - * @throws ApiError - */ -export const getFlowDebugInfo = (data: GetFlowDebugInfoData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_flow_debug_info/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * get completed job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns CompletedJob job details - * @throws ApiError - */ -export const getCompletedJob = (data: GetCompletedJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/completed/get/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * get completed job result - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.suspendedJob - * @param data.resumeId - * @param data.secret - * @param data.approver - * @returns unknown result - * @throws ApiError - */ -export const getCompletedJobResult = (data: GetCompletedJobResultData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/completed/get_result/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - query: { - suspended_job: data.suspendedJob, - resume_id: data.resumeId, - secret: data.secret, - approver: data.approver - } -}); }; - -/** - * get completed job result if job is completed - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.getStarted - * @returns unknown result - * @throws ApiError - */ -export const getCompletedJobResultMaybe = (data: GetCompletedJobResultMaybeData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/completed/get_result_maybe/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - query: { - get_started: data.getStarted - } -}); }; - -/** - * delete completed job (erase content but keep run id) - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns CompletedJob job details - * @throws ApiError - */ -export const deleteCompletedJob = (data: DeleteCompletedJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/completed/delete/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * cancel queued or running job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody reason - * @returns string job canceled - * @throws ApiError - */ -export const cancelQueuedJob = (data: CancelQueuedJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs_u/queue/cancel/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * cancel all queued jobs for persistent script - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody reason - * @returns string persistent job scaled down to zero - * @throws ApiError - */ -export const cancelPersistentQueuedJobs = (data: CancelPersistentQueuedJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs_u/queue/cancel_persistent/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * force cancel queued job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody reason - * @returns string job canceled - * @throws ApiError - */ -export const forceCancelQueuedJob = (data: ForceCancelQueuedJobData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs_u/queue/force_cancel/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create an HMac signature given a job id and a resume id - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.approver - * @returns string job signature - * @throws ApiError - */ -export const createJobSignature = (data: CreateJobSignatureData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/job_signature/{id}/{resume_id}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId - }, - query: { - approver: data.approver - } -}); }; - -/** - * get resume urls given a job_id, resume_id and a nonce to resume a flow - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.approver - * @returns unknown url endpoints - * @throws ApiError - */ -export const getResumeUrls = (data: GetResumeUrlsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/resume_urls/{id}/{resume_id}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId - }, - query: { - approver: data.approver - } -}); }; - -/** - * generate interactive slack approval for suspended job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.slackResourcePath - * @param data.channelId - * @param data.flowStepId - * @param data.approver - * @param data.message - * @param data.defaultArgsJson - * @param data.dynamicEnumsJson - * @returns unknown Interactive slack approval message sent successfully - * @throws ApiError - */ -export const getSlackApprovalPayload = (data: GetSlackApprovalPayloadData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/slack_approval/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - query: { - approver: data.approver, - message: data.message, - slack_resource_path: data.slackResourcePath, - channel_id: data.channelId, - flow_step_id: data.flowStepId, - default_args_json: data.defaultArgsJson, - dynamic_enums_json: data.dynamicEnumsJson - } -}); }; - -/** - * resume a job for a suspended flow - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.signature - * @param data.payload The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent - * `encodeURIComponent(btoa(JSON.stringify({a: 2})))` - * - * @param data.approver - * @returns string job resumed - * @throws ApiError - */ -export const resumeSuspendedJobGet = (data: ResumeSuspendedJobGetData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId, - signature: data.signature - }, - query: { - payload: data.payload, - approver: data.approver - } -}); }; - -/** - * resume a job for a suspended flow - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.signature - * @param data.requestBody - * @param data.approver - * @returns string job resumed - * @throws ApiError - */ -export const resumeSuspendedJobPost = (data: ResumeSuspendedJobPostData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId, - signature: data.signature - }, - query: { - approver: data.approver - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set flow user state at a given key - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.key - * @param data.requestBody new value - * @returns string flow user state updated - * @throws ApiError - */ -export const setFlowUserState = (data: SetFlowUserStateData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/flow/user_states/{id}/{key}', - path: { - workspace: data.workspace, - id: data.id, - key: data.key - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get flow user state at a given key - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.key - * @returns unknown flow user state updated - * @throws ApiError - */ -export const getFlowUserState = (data: GetFlowUserStateData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs/flow/user_states/{id}/{key}', - path: { - workspace: data.workspace, - id: data.id, - key: data.key - } -}); }; - -/** - * resume a job for a suspended flow as an owner - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody - * @returns string job resumed - * @throws ApiError - */ -export const resumeSuspendedFlowAsOwner = (data: ResumeSuspendedFlowAsOwnerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs/flow/resume/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * cancel a job for a suspended flow - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.signature - * @param data.approver - * @returns string job canceled - * @throws ApiError - */ -export const cancelSuspendedJobGet = (data: CancelSuspendedJobGetData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/cancel/{id}/{resume_id}/{signature}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId, - signature: data.signature - }, - query: { - approver: data.approver - } -}); }; - -/** - * cancel a job for a suspended flow - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.signature - * @param data.requestBody - * @param data.approver - * @returns string job canceled - * @throws ApiError - */ -export const cancelSuspendedJobPost = (data: CancelSuspendedJobPostData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/jobs_u/cancel/{id}/{resume_id}/{signature}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId, - signature: data.signature - }, - query: { - approver: data.approver - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get parent flow job of suspended job - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.resumeId - * @param data.signature - * @param data.approver - * @returns unknown parent flow details - * @throws ApiError - */ -export const getSuspendedJobFlow = (data: GetSuspendedJobFlowData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/jobs_u/get_flow/{id}/{resume_id}/{signature}', - path: { - workspace: data.workspace, - id: data.id, - resume_id: data.resumeId, - signature: data.signature - }, - query: { - approver: data.approver - } -}); }; - -/** - * preview schedule - * @param data The data for the request. - * @param data.requestBody schedule - * @returns string List of 5 estimated upcoming execution events (in UTC) - * @throws ApiError - */ -export const previewSchedule = (data: PreviewScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/schedules/preview', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create schedule - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new schedule - * @returns string schedule created - * @throws ApiError - */ -export const createSchedule = (data: CreateScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/schedules/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update schedule - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated schedule - * @returns string schedule updated - * @throws ApiError - */ -export const updateSchedule = (data: UpdateScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/schedules/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set enabled schedule - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated schedule enable - * @returns string schedule enabled set - * @throws ApiError - */ -export const setScheduleEnabled = (data: SetScheduleEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/schedules/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete schedule - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string schedule deleted - * @throws ApiError - */ -export const deleteSchedule = (data: DeleteScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/schedules/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get schedule - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns Schedule schedule deleted - * @throws ApiError - */ -export const getSchedule = (data: GetScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/schedules/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * does schedule exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean schedule exists - * @throws ApiError - */ -export const existsSchedule = (data: ExistsScheduleData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/schedules/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list schedules - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns Schedule schedule list - * @throws ApiError - */ -export const listSchedules = (data: ListSchedulesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/schedules/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - args: data.args, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * list schedules with last 20 jobs - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns ScheduleWJobs schedule list - * @throws ApiError - */ -export const listSchedulesWithJobs = (data: ListSchedulesWithJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/schedules/list_with_jobs', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * Set default error or recoevery handler - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Handler description - * @returns unknown default error handler set - * @throws ApiError - */ -export const setDefaultErrorOrRecoveryHandler = (data: SetDefaultErrorOrRecoveryHandlerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/schedules/setdefaulthandler', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create http trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new http trigger - * @returns string http trigger created - * @throws ApiError - */ -export const createHttpTrigger = (data: CreateHttpTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/http_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update http trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string http trigger updated - * @throws ApiError - */ -export const updateHttpTrigger = (data: UpdateHttpTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/http_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete http trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string http trigger deleted - * @throws ApiError - */ -export const deleteHttpTrigger = (data: DeleteHttpTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/http_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get http trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns HttpTrigger http trigger deleted - * @throws ApiError - */ -export const getHttpTrigger = (data: GetHttpTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/http_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list http triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns HttpTrigger http trigger list - * @throws ApiError - */ -export const listHttpTriggers = (data: ListHttpTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/http_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does http trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean http trigger exists - * @throws ApiError - */ -export const existsHttpTrigger = (data: ExistsHttpTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/http_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * does route exists - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody route exists request - * @returns boolean route exists - * @throws ApiError - */ -export const existsRoute = (data: ExistsRouteData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/http_triggers/route_exists', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create websocket trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new websocket trigger - * @returns string websocket trigger created - * @throws ApiError - */ -export const createWebsocketTrigger = (data: CreateWebsocketTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/websocket_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update websocket trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string websocket trigger updated - * @throws ApiError - */ -export const updateWebsocketTrigger = (data: UpdateWebsocketTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/websocket_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete websocket trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string websocket trigger deleted - * @throws ApiError - */ -export const deleteWebsocketTrigger = (data: DeleteWebsocketTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/websocket_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get websocket trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns WebsocketTrigger websocket trigger deleted - * @throws ApiError - */ -export const getWebsocketTrigger = (data: GetWebsocketTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/websocket_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list websocket triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns WebsocketTrigger websocket trigger list - * @throws ApiError - */ -export const listWebsocketTriggers = (data: ListWebsocketTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/websocket_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does websocket trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean websocket trigger exists - * @throws ApiError - */ -export const existsWebsocketTrigger = (data: ExistsWebsocketTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/websocket_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled websocket trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated websocket trigger enable - * @returns string websocket trigger enabled set - * @throws ApiError - */ -export const setWebsocketTriggerEnabled = (data: SetWebsocketTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/websocket_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test websocket connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test websocket connection - * @returns string successfuly connected to websocket - * @throws ApiError - */ -export const testWebsocketConnection = (data: TestWebsocketConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/websocket_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create kafka trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new kafka trigger - * @returns string kafka trigger created - * @throws ApiError - */ -export const createKafkaTrigger = (data: CreateKafkaTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/kafka_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update kafka trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string kafka trigger updated - * @throws ApiError - */ -export const updateKafkaTrigger = (data: UpdateKafkaTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/kafka_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete kafka trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string kafka trigger deleted - * @throws ApiError - */ -export const deleteKafkaTrigger = (data: DeleteKafkaTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/kafka_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get kafka trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns KafkaTrigger kafka trigger deleted - * @throws ApiError - */ -export const getKafkaTrigger = (data: GetKafkaTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/kafka_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list kafka triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns KafkaTrigger kafka trigger list - * @throws ApiError - */ -export const listKafkaTriggers = (data: ListKafkaTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/kafka_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does kafka trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean kafka trigger exists - * @throws ApiError - */ -export const existsKafkaTrigger = (data: ExistsKafkaTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/kafka_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled kafka trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated kafka trigger enable - * @returns string kafka trigger enabled set - * @throws ApiError - */ -export const setKafkaTriggerEnabled = (data: SetKafkaTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/kafka_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test kafka connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test kafka connection - * @returns string successfuly connected to kafka brokers - * @throws ApiError - */ -export const testKafkaConnection = (data: TestKafkaConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/kafka_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create nats trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new nats trigger - * @returns string nats trigger created - * @throws ApiError - */ -export const createNatsTrigger = (data: CreateNatsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/nats_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update nats trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string nats trigger updated - * @throws ApiError - */ -export const updateNatsTrigger = (data: UpdateNatsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/nats_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete nats trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string nats trigger deleted - * @throws ApiError - */ -export const deleteNatsTrigger = (data: DeleteNatsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/nats_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get nats trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns NatsTrigger nats trigger deleted - * @throws ApiError - */ -export const getNatsTrigger = (data: GetNatsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/nats_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list nats triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns NatsTrigger nats trigger list - * @throws ApiError - */ -export const listNatsTriggers = (data: ListNatsTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/nats_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does nats trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean nats trigger exists - * @throws ApiError - */ -export const existsNatsTrigger = (data: ExistsNatsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/nats_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled nats trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated nats trigger enable - * @returns string nats trigger enabled set - * @throws ApiError - */ -export const setNatsTriggerEnabled = (data: SetNatsTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/nats_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test NATS connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test nats connection - * @returns string successfuly connected to NATS servers - * @throws ApiError - */ -export const testNatsConnection = (data: TestNatsConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/nats_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create sqs trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new sqs trigger - * @returns string sqs trigger created - * @throws ApiError - */ -export const createSqsTrigger = (data: CreateSqsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/sqs_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update sqs trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string sqs trigger updated - * @throws ApiError - */ -export const updateSqsTrigger = (data: UpdateSqsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/sqs_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete sqs trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string sqs trigger deleted - * @throws ApiError - */ -export const deleteSqsTrigger = (data: DeleteSqsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/sqs_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get sqs trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns SqsTrigger sqs trigger deleted - * @throws ApiError - */ -export const getSqsTrigger = (data: GetSqsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/sqs_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list sqs triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns SqsTrigger sqs trigger list - * @throws ApiError - */ -export const listSqsTriggers = (data: ListSqsTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/sqs_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does sqs trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean sqs trigger exists - * @throws ApiError - */ -export const existsSqsTrigger = (data: ExistsSqsTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/sqs_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled sqs trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated sqs trigger enable - * @returns string sqs trigger enabled set - * @throws ApiError - */ -export const setSqsTriggerEnabled = (data: SetSqsTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/sqs_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test sqs connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test sqs connection - * @returns string successfuly connected to sqs - * @throws ApiError - */ -export const testSqsConnection = (data: TestSqsConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/sqs_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * create mqtt trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new mqtt trigger - * @returns string mqtt trigger created - * @throws ApiError - */ -export const createMqttTrigger = (data: CreateMqttTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/mqtt_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update mqtt trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string mqtt trigger updated - * @throws ApiError - */ -export const updateMqttTrigger = (data: UpdateMqttTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/mqtt_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete mqtt trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string mqtt trigger deleted - * @throws ApiError - */ -export const deleteMqttTrigger = (data: DeleteMqttTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/mqtt_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get mqtt trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns MqttTrigger mqtt trigger deleted - * @throws ApiError - */ -export const getMqttTrigger = (data: GetMqttTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/mqtt_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list mqtt triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns MqttTrigger mqtt trigger list - * @throws ApiError - */ -export const listMqttTriggers = (data: ListMqttTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/mqtt_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does mqtt trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean mqtt trigger exists - * @throws ApiError - */ -export const existsMqttTrigger = (data: ExistsMqttTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/mqtt_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled mqtt trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated mqtt trigger enable - * @returns string mqtt trigger enabled set - * @throws ApiError - */ -export const setMqttTriggerEnabled = (data: SetMqttTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/mqtt_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test mqtt connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test mqtt connection - * @returns string successfuly connected to mqtt - * @throws ApiError - */ -export const testMqttConnection = (data: TestMqttConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/mqtt_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * check if postgres configuration is set to logical - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean boolean that indicates if postgres is set to logical level or not - * @throws ApiError - */ -export const isValidPostgresConfiguration = (data: IsValidPostgresConfigurationData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * create template script - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody template script - * @returns string custom id to retrieve template script - * @throws ApiError - */ -export const createTemplateScript = (data: CreateTemplateScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/create_template_script', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get template script - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns string template script - * @throws ApiError - */ -export const getTemplateScript = (data: GetTemplateScriptData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/get_template_script/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * list postgres replication slot - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns SlotList list postgres slot - * @throws ApiError - */ -export const listPostgresReplicationSlot = (data: ListPostgresReplicationSlotData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/slot/list/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * create replication slot for postgres - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody new slot for postgres - * @returns string slot created - * @throws ApiError - */ -export const createPostgresReplicationSlot = (data: CreatePostgresReplicationSlotData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/slot/create/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete postgres replication slot - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody replication slot of postgres - * @returns string postgres replication slot deleted - * @throws ApiError - */ -export const deletePostgresReplicationSlot = (data: DeletePostgresReplicationSlotData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/postgres_triggers/slot/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list postgres publication - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string database publication list - * @throws ApiError - */ -export const listPostgresPublication = (data: ListPostgresPublicationData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/publication/list/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get postgres publication - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.publication - * @returns PublicationData postgres publication get - * @throws ApiError - */ -export const getPostgresPublication = (data: GetPostgresPublicationData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/publication/get/{publication}/{path}', - path: { - workspace: data.workspace, - path: data.path, - publication: data.publication - } -}); }; - -/** - * create publication for postgres - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.publication - * @param data.requestBody new publication for postgres - * @returns string publication created - * @throws ApiError - */ -export const createPostgresPublication = (data: CreatePostgresPublicationData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/publication/create/{publication}/{path}', - path: { - workspace: data.workspace, - path: data.path, - publication: data.publication - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update publication for postgres - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.publication - * @param data.requestBody update publication for postgres - * @returns string publication updated - * @throws ApiError - */ -export const updatePostgresPublication = (data: UpdatePostgresPublicationData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/publication/update/{publication}/{path}', - path: { - workspace: data.workspace, - path: data.path, - publication: data.publication - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete postgres publication - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.publication - * @returns string postgres publication deleted - * @throws ApiError - */ -export const deletePostgresPublication = (data: DeletePostgresPublicationData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/postgres_triggers/publication/delete/{publication}/{path}', - path: { - workspace: data.workspace, - path: data.path, - publication: data.publication - } -}); }; - -/** - * create postgres trigger - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody new postgres trigger - * @returns string postgres trigger created - * @throws ApiError - */ -export const createPostgresTrigger = (data: CreatePostgresTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update postgres trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated trigger - * @returns string postgres trigger updated - * @throws ApiError - */ -export const updatePostgresTrigger = (data: UpdatePostgresTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/update/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete postgres trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns string postgres trigger deleted - * @throws ApiError - */ -export const deletePostgresTrigger = (data: DeletePostgresTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/postgres_triggers/delete/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * get postgres trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns PostgresTrigger get postgres trigger - * @throws ApiError - */ -export const getPostgresTrigger = (data: GetPostgresTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/get/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * list postgres triggers - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.path filter by path - * @param data.isFlow - * @param data.pathStart - * @returns PostgresTrigger postgres trigger list - * @throws ApiError - */ -export const listPostgresTriggers = (data: ListPostgresTriggersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage, - path: data.path, - is_flow: data.isFlow, - path_start: data.pathStart - } -}); }; - -/** - * does postgres trigger exists - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @returns boolean postgres trigger exists - * @throws ApiError - */ -export const existsPostgresTrigger = (data: ExistsPostgresTriggerData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/postgres_triggers/exists/{path}', - path: { - workspace: data.workspace, - path: data.path - } -}); }; - -/** - * set enabled postgres trigger - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.requestBody updated postgres trigger enable - * @returns string postgres trigger enabled set - * @throws ApiError - */ -export const setPostgresTriggerEnabled = (data: SetPostgresTriggerEnabledData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/setenabled/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * test postgres connection - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody test postgres connection - * @returns string successfuly connected to postgres - * @throws ApiError - */ -export const testPostgresConnection = (data: TestPostgresConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/postgres_triggers/test', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list instance groups - * @returns InstanceGroup instance group list - * @throws ApiError - */ -export const listInstanceGroups = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/groups/list' -}); }; - -/** - * get instance group - * @param data The data for the request. - * @param data.name - * @returns InstanceGroup instance group - * @throws ApiError - */ -export const getInstanceGroup = (data: GetInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/groups/get/{name}', - path: { - name: data.name - } -}); }; - -/** - * create instance group - * @param data The data for the request. - * @param data.requestBody create instance group - * @returns string instance group created - * @throws ApiError - */ -export const createInstanceGroup = (data: CreateInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/groups/create', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update instance group - * @param data The data for the request. - * @param data.name - * @param data.requestBody update instance group - * @returns string instance group updated - * @throws ApiError - */ -export const updateInstanceGroup = (data: UpdateInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/groups/update/{name}', - path: { - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete instance group - * @param data The data for the request. - * @param data.name - * @returns string instance group deleted - * @throws ApiError - */ -export const deleteInstanceGroup = (data: DeleteInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/groups/delete/{name}', - path: { - name: data.name - } -}); }; - -/** - * add user to instance group - * @param data The data for the request. - * @param data.name - * @param data.requestBody user to add to instance group - * @returns string user added to instance group - * @throws ApiError - */ -export const addUserToInstanceGroup = (data: AddUserToInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/groups/adduser/{name}', - path: { - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * remove user from instance group - * @param data The data for the request. - * @param data.name - * @param data.requestBody user to remove from instance group - * @returns string user removed from instance group - * @throws ApiError - */ -export const removeUserFromInstanceGroup = (data: RemoveUserFromInstanceGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/groups/removeuser/{name}', - path: { - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * export instance groups - * @returns ExportedInstanceGroup exported instance groups - * @throws ApiError - */ -export const exportInstanceGroups = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/groups/export' -}); }; - -/** - * overwrite instance groups - * @param data The data for the request. - * @param data.requestBody overwrite instance groups - * @returns string success message - * @throws ApiError - */ -export const overwriteInstanceGroups = (data: OverwriteInstanceGroupsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/groups/overwrite', - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list groups - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns Group group list - * @throws ApiError - */ -export const listGroups = (data: ListGroupsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/groups/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * list group names - * @param data The data for the request. - * @param data.workspace - * @param data.onlyMemberOf only list the groups the user is member of (default false) - * @returns string group list - * @throws ApiError - */ -export const listGroupNames = (data: ListGroupNamesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/groups/listnames', - path: { - workspace: data.workspace - }, - query: { - only_member_of: data.onlyMemberOf - } -}); }; - -/** - * create group - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody create group - * @returns string group created - * @throws ApiError - */ -export const createGroup = (data: CreateGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/groups/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update group - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody updated group - * @returns string group updated - * @throws ApiError - */ -export const updateGroup = (data: UpdateGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/groups/update/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete group - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns string group deleted - * @throws ApiError - */ -export const deleteGroup = (data: DeleteGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/groups/delete/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * get group - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns Group group - * @throws ApiError - */ -export const getGroup = (data: GetGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/groups/get/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * add user to group - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody added user to group - * @returns string user added to group - * @throws ApiError - */ -export const addUserToGroup = (data: AddUserToGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/groups/adduser/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * remove user to group - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody added user to group - * @returns string user removed from group - * @throws ApiError - */ -export const removeUserToGroup = (data: RemoveUserToGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/groups/removeuser/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list folders - * @param data The data for the request. - * @param data.workspace - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns Folder folder list - * @throws ApiError - */ -export const listFolders = (data: ListFoldersData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/folders/list', - path: { - workspace: data.workspace - }, - query: { - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * list folder names - * @param data The data for the request. - * @param data.workspace - * @param data.onlyMemberOf only list the folders the user is member of (default false) - * @returns string folder list - * @throws ApiError - */ -export const listFolderNames = (data: ListFolderNamesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/folders/listnames', - path: { - workspace: data.workspace - }, - query: { - only_member_of: data.onlyMemberOf - } -}); }; - -/** - * create folder - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody create folder - * @returns string folder created - * @throws ApiError - */ -export const createFolder = (data: CreateFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/folders/create', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * update folder - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody update folder - * @returns string folder updated - * @throws ApiError - */ -export const updateFolder = (data: UpdateFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/folders/update/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * delete folder - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns string folder deleted - * @throws ApiError - */ -export const deleteFolder = (data: DeleteFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/folders/delete/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * get folder - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns Folder folder - * @throws ApiError - */ -export const getFolder = (data: GetFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/folders/get/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * get folder usage - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @returns unknown folder - * @throws ApiError - */ -export const getFolderUsage = (data: GetFolderUsageData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/folders/getusage/{name}', - path: { - workspace: data.workspace, - name: data.name - } -}); }; - -/** - * add owner to folder - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody owner user to folder - * @returns string owner added to folder - * @throws ApiError - */ -export const addOwnerToFolder = (data: AddOwnerToFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/folders/addowner/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * remove owner to folder - * @param data The data for the request. - * @param data.workspace - * @param data.name - * @param data.requestBody added owner to folder - * @returns string owner removed from folder - * @throws ApiError - */ -export const removeOwnerToFolder = (data: RemoveOwnerToFolderData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/folders/removeowner/{name}', - path: { - workspace: data.workspace, - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * list workers - * @param data The data for the request. - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.pingSince number of seconds the worker must have had a last ping more recent of (default to 300) - * @returns WorkerPing a list of workers - * @throws ApiError - */ -export const listWorkers = (data: ListWorkersData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/list', - query: { - page: data.page, - per_page: data.perPage, - ping_since: data.pingSince - } -}); }; - -/** - * exists worker with tag - * @param data The data for the request. - * @param data.tag - * @returns boolean whether a worker with the tag exists - * @throws ApiError - */ -export const existsWorkerWithTag = (data: ExistsWorkerWithTagData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/exists_worker_with_tag', - query: { - tag: data.tag - } -}); }; - -/** - * get queue metrics - * @returns unknown metrics - * @throws ApiError - */ -export const getQueueMetrics = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/queue_metrics' -}); }; - -/** - * get counts of jobs waiting for an executor per tag - * @returns number queue counts - * @throws ApiError - */ -export const getCountsOfJobsWaitingPerTag = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/workers/queue_counts' -}); }; - -/** - * list worker groups - * @returns unknown a list of worker group configs - * @throws ApiError - */ -export const listWorkerGroups = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/configs/list_worker_groups' -}); }; - -/** - * get config - * @param data The data for the request. - * @param data.name - * @returns unknown a config - * @throws ApiError - */ -export const getConfig = (data: GetConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/configs/get/{name}', - path: { - name: data.name - } -}); }; - -/** - * Update config - * @param data The data for the request. - * @param data.name - * @param data.requestBody worker group - * @returns string Update a worker group - * @throws ApiError - */ -export const updateConfig = (data: UpdateConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/configs/update/{name}', - path: { - name: data.name - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Delete Config - * @param data The data for the request. - * @param data.name - * @returns string Delete config - * @throws ApiError - */ -export const deleteConfig = (data: DeleteConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/configs/update/{name}', - path: { - name: data.name - } -}); }; - -/** - * list configs - * @returns Config list of configs - * @throws ApiError - */ -export const listConfigs = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/configs/list' -}); }; - -/** - * List autoscaling events - * @param data The data for the request. - * @param data.workerGroup - * @returns AutoscalingEvent List of autoscaling events - * @throws ApiError - */ -export const listAutoscalingEvents = (data: ListAutoscalingEventsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/configs/list_autoscaling_events/{worker_group}', - path: { - worker_group: data.workerGroup - } -}); }; - -/** - * get granular acls - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.kind - * @returns boolean acls - * @throws ApiError - */ -export const getGranularAcls = (data: GetGranularAclsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/acls/get/{kind}/{path}', - path: { - workspace: data.workspace, - path: data.path, - kind: data.kind - } -}); }; - -/** - * add granular acls - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.kind - * @param data.requestBody acl to add - * @returns string granular acl added - * @throws ApiError - */ -export const addGranularAcls = (data: AddGranularAclsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/acls/add/{kind}/{path}', - path: { - workspace: data.workspace, - path: data.path, - kind: data.kind - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * remove granular acls - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.kind - * @param data.requestBody acl to add - * @returns string granular acl removed - * @throws ApiError - */ -export const removeGranularAcls = (data: RemoveGranularAclsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/acls/remove/{kind}/{path}', - path: { - workspace: data.workspace, - path: data.path, - kind: data.kind - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set capture config - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody capture config - * @returns unknown capture config set - * @throws ApiError - */ -export const setCaptureConfig = (data: SetCaptureConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/capture/set_config', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * ping capture config - * @param data The data for the request. - * @param data.workspace - * @param data.triggerKind - * @param data.runnableKind - * @param data.path - * @returns unknown capture config pinged - * @throws ApiError - */ -export const pingCaptureConfig = (data: PingCaptureConfigData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}', - path: { - workspace: data.workspace, - trigger_kind: data.triggerKind, - runnable_kind: data.runnableKind, - path: data.path - } -}); }; - -/** - * get capture configs for a script or flow - * @param data The data for the request. - * @param data.workspace - * @param data.runnableKind - * @param data.path - * @returns CaptureConfig capture configs for a script or flow - * @throws ApiError - */ -export const getCaptureConfigs = (data: GetCaptureConfigsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/capture/get_configs/{runnable_kind}/{path}', - path: { - workspace: data.workspace, - runnable_kind: data.runnableKind, - path: data.path - } -}); }; - -/** - * list captures for a script or flow - * @param data The data for the request. - * @param data.workspace - * @param data.runnableKind - * @param data.path - * @param data.triggerKind - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns Capture list of captures for a script or flow - * @throws ApiError - */ -export const listCaptures = (data: ListCapturesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/capture/list/{runnable_kind}/{path}', - path: { - workspace: data.workspace, - runnable_kind: data.runnableKind, - path: data.path - }, - query: { - trigger_kind: data.triggerKind, - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * get a capture - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns Capture capture - * @throws ApiError - */ -export const getCapture = (data: GetCaptureData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/capture/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * delete a capture - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns unknown capture deleted - * @throws ApiError - */ -export const deleteCapture = (data: DeleteCaptureData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/capture/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * star item - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns unknown star item - * @throws ApiError - */ -export const star = (data: StarData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/favorites/star', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * unstar item - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody - * @returns unknown unstar item - * @throws ApiError - */ -export const unstar = (data: UnstarData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/favorites/unstar', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * List Inputs used in previously completed jobs - * @param data The data for the request. - * @param data.workspace - * @param data.runnableId - * @param data.runnableType - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.includePreview - * @returns Input Input history for completed jobs - * @throws ApiError - */ -export const getInputHistory = (data: GetInputHistoryData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/inputs/history', - path: { - workspace: data.workspace - }, - query: { - runnable_id: data.runnableId, - runnable_type: data.runnableType, - page: data.page, - per_page: data.perPage, - include_preview: data.includePreview - } -}); }; - -/** - * Get args from history or saved input - * @param data The data for the request. - * @param data.workspace - * @param data.jobOrInputId - * @param data.input - * @param data.allowLarge - * @returns unknown args - * @throws ApiError - */ -export const getArgsFromHistoryOrSavedInput = (data: GetArgsFromHistoryOrSavedInputData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/inputs/{jobOrInputId}/args', - path: { - workspace: data.workspace, - jobOrInputId: data.jobOrInputId - }, - query: { - input: data.input, - allow_large: data.allowLarge - } -}); }; - -/** - * List saved Inputs for a Runnable - * @param data The data for the request. - * @param data.workspace - * @param data.runnableId - * @param data.runnableType - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @returns Input Saved Inputs for a Runnable - * @throws ApiError - */ -export const listInputs = (data: ListInputsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/inputs/list', - path: { - workspace: data.workspace - }, - query: { - runnable_id: data.runnableId, - runnable_type: data.runnableType, - page: data.page, - per_page: data.perPage - } -}); }; - -/** - * Create an Input for future use in a script or flow - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody Input - * @param data.runnableId - * @param data.runnableType - * @returns string Input created - * @throws ApiError - */ -export const createInput = (data: CreateInputData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/inputs/create', - path: { - workspace: data.workspace - }, - query: { - runnable_id: data.runnableId, - runnable_type: data.runnableType - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Update an Input - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody UpdateInput - * @returns string Input updated - * @throws ApiError - */ -export const updateInput = (data: UpdateInputData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/inputs/update', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Delete a Saved Input - * @param data The data for the request. - * @param data.workspace - * @param data.input - * @returns string Input deleted - * @throws ApiError - */ -export const deleteInput = (data: DeleteInputData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/inputs/delete/{input}', - path: { - workspace: data.workspace, - input: data.input - } -}); }; - -/** - * Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody S3 resource to connect to - * @returns unknown Connection settings - * @throws ApiError - */ -export const duckdbConnectionSettings = (data: DuckdbConnectionSettingsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/duckdb_connection_settings', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Converts an S3 resource to the set of instructions necessary to connect DuckDB to an S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used - * @returns unknown Connection settings - * @throws ApiError - */ -export const duckdbConnectionSettingsV2 = (data: DuckdbConnectionSettingsV2Data): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/v2/duckdb_connection_settings', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody S3 resource to connect to - * @returns unknown Connection settings - * @throws ApiError - */ -export const polarsConnectionSettings = (data: PolarsConnectionSettingsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/polars_connection_settings', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Converts an S3 resource to the set of arguments necessary to connect Polars to an S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody S3 resource path to use to generate the connection settings. If empty, the S3 resource defined in the workspace settings will be used - * @returns unknown Connection settings - * @throws ApiError - */ -export const polarsConnectionSettingsV2 = (data: PolarsConnectionSettingsV2Data): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/v2/polars_connection_settings', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Returns the s3 resource associated to the provided path, or the workspace default S3 resource - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody S3 resource path to use. If empty, the S3 resource defined in the workspace settings will be used - * @returns S3Resource Connection settings - * @throws ApiError - */ -export const s3ResourceInfo = (data: S3ResourceInfoData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/v2/s3_resource_info', - path: { - workspace: data.workspace - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * Test connection to the workspace object storage - * @param data The data for the request. - * @param data.workspace - * @param data.storage - * @returns unknown Connection settings - * @throws ApiError - */ -export const datasetStorageTestConnection = (data: DatasetStorageTestConnectionData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/test_connection', - path: { - workspace: data.workspace - }, - query: { - storage: data.storage - } -}); }; - -/** - * List the file keys available in a workspace object storage - * @param data The data for the request. - * @param data.workspace - * @param data.maxKeys - * @param data.marker - * @param data.prefix - * @param data.storage - * @returns unknown List of file keys - * @throws ApiError - */ -export const listStoredFiles = (data: ListStoredFilesData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/list_stored_files', - path: { - workspace: data.workspace - }, - query: { - max_keys: data.maxKeys, - marker: data.marker, - prefix: data.prefix, - storage: data.storage - } -}); }; - -/** - * Load metadata of the file - * @param data The data for the request. - * @param data.workspace - * @param data.fileKey - * @param data.storage - * @returns WindmillFileMetadata FileMetadata - * @throws ApiError - */ -export const loadFileMetadata = (data: LoadFileMetadataData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/load_file_metadata', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - storage: data.storage - } -}); }; - -/** - * Load a preview of the file - * @param data The data for the request. - * @param data.workspace - * @param data.fileKey - * @param data.fileSizeInBytes - * @param data.fileMimeType - * @param data.csvSeparator - * @param data.csvHasHeader - * @param data.readBytesFrom - * @param data.readBytesLength - * @param data.storage - * @returns WindmillFilePreview FilePreview - * @throws ApiError - */ -export const loadFilePreview = (data: LoadFilePreviewData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/load_file_preview', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - file_size_in_bytes: data.fileSizeInBytes, - file_mime_type: data.fileMimeType, - csv_separator: data.csvSeparator, - csv_has_header: data.csvHasHeader, - read_bytes_from: data.readBytesFrom, - read_bytes_length: data.readBytesLength, - storage: data.storage - } -}); }; - -/** - * Load a preview of a parquet file - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.offset - * @param data.limit - * @param data.sortCol - * @param data.sortDesc - * @param data.searchCol - * @param data.searchTerm - * @param data.storage - * @returns unknown Parquet Preview - * @throws ApiError - */ -export const loadParquetPreview = (data: LoadParquetPreviewData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/load_parquet_preview/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - offset: data.offset, - limit: data.limit, - sort_col: data.sortCol, - sort_desc: data.sortDesc, - search_col: data.searchCol, - search_term: data.searchTerm, - storage: data.storage - } -}); }; - -/** - * Load the table row count - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.searchCol - * @param data.searchTerm - * @param data.storage - * @returns unknown Table count - * @throws ApiError - */ -export const loadTableRowCount = (data: LoadTableRowCountData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/load_table_count/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - search_col: data.searchCol, - search_term: data.searchTerm, - storage: data.storage - } -}); }; - -/** - * Load a preview of a csv file - * @param data The data for the request. - * @param data.workspace - * @param data.path - * @param data.offset - * @param data.limit - * @param data.sortCol - * @param data.sortDesc - * @param data.searchCol - * @param data.searchTerm - * @param data.storage - * @param data.csvSeparator - * @returns unknown Csv Preview - * @throws ApiError - */ -export const loadCsvPreview = (data: LoadCsvPreviewData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/load_csv_preview/{path}', - path: { - workspace: data.workspace, - path: data.path - }, - query: { - offset: data.offset, - limit: data.limit, - sort_col: data.sortCol, - sort_desc: data.sortDesc, - search_col: data.searchCol, - search_term: data.searchTerm, - storage: data.storage, - csv_separator: data.csvSeparator - } -}); }; - -/** - * Permanently delete file from S3 - * @param data The data for the request. - * @param data.workspace - * @param data.fileKey - * @param data.storage - * @returns unknown Confirmation - * @throws ApiError - */ -export const deleteS3File = (data: DeleteS3FileData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/w/{workspace}/job_helpers/delete_s3_file', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - storage: data.storage - } -}); }; - -/** - * Move a S3 file from one path to the other within the same bucket - * @param data The data for the request. - * @param data.workspace - * @param data.srcFileKey - * @param data.destFileKey - * @param data.storage - * @returns unknown Confirmation - * @throws ApiError - */ -export const moveS3File = (data: MoveS3FileData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/move_s3_file', - path: { - workspace: data.workspace - }, - query: { - src_file_key: data.srcFileKey, - dest_file_key: data.destFileKey, - storage: data.storage - } -}); }; - -/** - * Upload file to S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.requestBody File content - * @param data.fileKey - * @param data.fileExtension - * @param data.s3ResourcePath - * @param data.resourceType - * @param data.storage - * @param data.contentType - * @param data.contentDisposition - * @returns unknown File upload status - * @throws ApiError - */ -export const fileUpload = (data: FileUploadData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_helpers/upload_s3_file', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - file_extension: data.fileExtension, - s3_resource_path: data.s3ResourcePath, - resource_type: data.resourceType, - storage: data.storage, - content_type: data.contentType, - content_disposition: data.contentDisposition - }, - body: data.requestBody, - mediaType: 'application/octet-stream' -}); }; - -/** - * Download file to S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.fileKey - * @param data.s3ResourcePath - * @param data.resourceType - * @param data.storage - * @returns binary Chunk of the downloaded file - * @throws ApiError - */ -export const fileDownload = (data: FileDownloadData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/download_s3_file', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - s3_resource_path: data.s3ResourcePath, - resource_type: data.resourceType, - storage: data.storage - } -}); }; - -/** - * Download file to S3 bucket - * @param data The data for the request. - * @param data.workspace - * @param data.fileKey - * @param data.s3ResourcePath - * @param data.resourceType - * @returns string The downloaded file - * @throws ApiError - */ -export const fileDownloadParquetAsCsv = (data: FileDownloadParquetAsCsvData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_helpers/download_s3_parquet_file_as_csv', - path: { - workspace: data.workspace - }, - query: { - file_key: data.fileKey, - s3_resource_path: data.s3ResourcePath, - resource_type: data.resourceType - } -}); }; - -/** - * get job metrics - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody parameters for statistics retrieval - * @returns unknown job details - * @throws ApiError - */ -export const getJobMetrics = (data: GetJobMetricsData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_metrics/get/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * set job metrics - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @param data.requestBody parameters for statistics retrieval - * @returns unknown Job progress updated - * @throws ApiError - */ -export const setJobProgress = (data: SetJobProgressData): CancelablePromise => { return __request(OpenAPI, { - method: 'POST', - url: '/w/{workspace}/job_metrics/set_progress/{id}', - path: { - workspace: data.workspace, - id: data.id - }, - body: data.requestBody, - mediaType: 'application/json' -}); }; - -/** - * get job progress - * @param data The data for the request. - * @param data.workspace - * @param data.id - * @returns number job progress between 0 and 99 - * @throws ApiError - */ -export const getJobProgress = (data: GetJobProgressData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/job_metrics/get_progress/{id}', - path: { - workspace: data.workspace, - id: data.id - } -}); }; - -/** - * list log files ordered by timestamp - * @param data The data for the request. - * @param data.before filter on started before (inclusive) timestamp - * @param data.after filter on created after (exclusive) timestamp - * @param data.withError - * @returns unknown time - * @throws ApiError - */ -export const listLogFiles = (data: ListLogFilesData = {}): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/service_logs/list_files', - query: { - before: data.before, - after: data.after, - with_error: data.withError - } -}); }; - -/** - * get log file by path - * @param data The data for the request. - * @param data.path - * @returns string log stream - * @throws ApiError - */ -export const getLogFile = (data: GetLogFileData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/service_logs/get_log_file/{path}', - path: { - path: data.path - } -}); }; - -/** - * List all concurrency groups - * @returns ConcurrencyGroup all concurrency groups - * @throws ApiError - */ -export const listConcurrencyGroups = (): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/concurrency_groups/list' -}); }; - -/** - * Delete concurrency group - * @param data The data for the request. - * @param data.concurrencyId - * @returns unknown concurrency group removed - * @throws ApiError - */ -export const deleteConcurrencyGroup = (data: DeleteConcurrencyGroupData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/concurrency_groups/prune/{concurrency_id}', - path: { - concurrency_id: data.concurrencyId - } -}); }; - -/** - * Get the concurrency key for a job that has concurrency limits enabled - * @param data The data for the request. - * @param data.id - * @returns string concurrency key for given job - * @throws ApiError - */ -export const getConcurrencyKey = (data: GetConcurrencyKeyData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/concurrency_groups/{id}/key', - path: { - id: data.id - } -}); }; - -/** - * Get intervals of job runtime concurrency - * @param data The data for the request. - * @param data.workspace - * @param data.concurrencyKey - * @param data.rowLimit - * @param data.createdBy mask to filter exact matching user creator - * @param data.label mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') - * @param data.parentJob The parent job that is at the origin and responsible for the execution of this script if any - * @param data.scriptPathExact mask to filter exact matching path - * @param data.scriptPathStart mask to filter matching starting path - * @param data.schedulePath mask to filter by schedule path - * @param data.scriptHash mask to filter exact matching path - * @param data.startedBefore filter on started before (inclusive) timestamp - * @param data.startedAfter filter on started after (exclusive) timestamp - * @param data.createdOrStartedBefore filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp - * @param data.running filter on running jobs - * @param data.scheduledForBeforeNow filter on jobs scheduled_for before now (hence waitinf for a worker) - * @param data.createdOrStartedAfter filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp - * @param data.createdOrStartedAfterCompletedJobs filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs - * @param data.jobKinds filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - * @param data.args filter on jobs containing those args as a json subset (@> in postgres) - * @param data.tag filter on jobs with a given tag/worker group - * @param data.result filter on jobs containing those result as a json subset (@> in postgres) - * @param data.page which page to return (start at 1, default 1) - * @param data.perPage number of items to return for a given page (default 30, max 100) - * @param data.isSkipped is the job skipped - * @param data.isFlowStep is the job a flow step - * @param data.hasNullParent has null parent - * @param data.success filter on successful jobs - * @param data.allWorkspaces get jobs from all workspaces (only valid if request come from the `admins` workspace) - * @param data.isNotSchedule is not a scheduled job - * @returns ExtendedJobs time - * @throws ApiError - */ -export const listExtendedJobs = (data: ListExtendedJobsData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/w/{workspace}/concurrency_groups/list_jobs', - path: { - workspace: data.workspace - }, - query: { - concurrency_key: data.concurrencyKey, - row_limit: data.rowLimit, - created_by: data.createdBy, - label: data.label, - parent_job: data.parentJob, - script_path_exact: data.scriptPathExact, - script_path_start: data.scriptPathStart, - schedule_path: data.schedulePath, - script_hash: data.scriptHash, - started_before: data.startedBefore, - started_after: data.startedAfter, - created_or_started_before: data.createdOrStartedBefore, - running: data.running, - scheduled_for_before_now: data.scheduledForBeforeNow, - created_or_started_after: data.createdOrStartedAfter, - created_or_started_after_completed_jobs: data.createdOrStartedAfterCompletedJobs, - job_kinds: data.jobKinds, - args: data.args, - tag: data.tag, - result: data.result, - page: data.page, - per_page: data.perPage, - is_skipped: data.isSkipped, - is_flow_step: data.isFlowStep, - has_null_parent: data.hasNullParent, - success: data.success, - all_workspaces: data.allWorkspaces, - is_not_schedule: data.isNotSchedule - } -}); }; - -/** - * Search through jobs with a string query - * @param data The data for the request. - * @param data.workspace - * @param data.searchQuery - * @returns unknown search results - * @throws ApiError - */ -export const searchJobsIndex = (data: SearchJobsIndexData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/srch/w/{workspace}/index/search/job', - path: { - workspace: data.workspace - }, - query: { - search_query: data.searchQuery - } -}); }; - -/** - * Search through service logs with a string query - * @param data The data for the request. - * @param data.searchQuery - * @param data.mode - * @param data.hostname - * @param data.workerGroup - * @param data.minTs - * @param data.maxTs - * @returns unknown search results - * @throws ApiError - */ -export const searchLogsIndex = (data: SearchLogsIndexData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/srch/index/search/service_logs', - query: { - search_query: data.searchQuery, - mode: data.mode, - worker_group: data.workerGroup, - hostname: data.hostname, - min_ts: data.minTs, - max_ts: data.maxTs - } -}); }; - -/** - * Search and count the log line hits on every provided host - * @param data The data for the request. - * @param data.searchQuery - * @param data.minTs - * @param data.maxTs - * @returns unknown search results - * @throws ApiError - */ -export const countSearchLogsIndex = (data: CountSearchLogsIndexData): CancelablePromise => { return __request(OpenAPI, { - method: 'GET', - url: '/srch/index/search/count_service_logs', - query: { - search_query: data.searchQuery, - min_ts: data.minTs, - max_ts: data.maxTs - } -}); }; - -/** - * Restart container and delete the index to recreate it. - * @param data The data for the request. - * @param data.idxName - * @returns string idx to be deleted and container restarting - * @throws ApiError - */ -export const clearIndex = (data: ClearIndexData): CancelablePromise => { return __request(OpenAPI, { - method: 'DELETE', - url: '/srch/index/delete/{idx_name}', - path: { - idx_name: data.idxName - } -}); }; \ No newline at end of file diff --git a/cli/gen/types.gen.ts b/cli/gen/types.gen.ts deleted file mode 100644 index f0de8f56f7..0000000000 --- a/cli/gen/types.gen.ts +++ /dev/null @@ -1,7416 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type AIProvider = 'openai' | 'anthropic' | 'mistral' | 'deepseek' | 'googleai' | 'groq' | 'openrouter' | 'customai'; - -export type AIResource = { - path: string; - provider: AIProvider; -}; - -export type Script = { - workspace_id?: string; - hash: string; - path: string; - /** - * The first element is the direct parent of the script, the second is the parent of the first, etc - * - */ - parent_hashes?: Array<(string)>; - summary: string; - description: string; - content: string; - created_by: string; - created_at: string; - archived: boolean; - schema?: { - [key: string]: unknown; - }; - deleted: boolean; - is_template: boolean; - extra_perms: { - [key: string]: (boolean); - }; - lock?: string; - lock_error_logs?: string; - language: ScriptLang; - kind: 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor'; - starred: boolean; - tag?: string; - has_draft?: boolean; - draft_only?: boolean; - envs?: Array<(string)>; - concurrent_limit?: number; - concurrency_time_window_s?: number; - concurrency_key?: string; - cache_ttl?: number; - dedicated_worker?: boolean; - ws_error_handler_muted?: boolean; - priority?: number; - restart_unless_cancelled?: boolean; - timeout?: number; - delete_after_use?: boolean; - visible_to_runner_only?: boolean; - no_main_func: boolean; - codebase?: string; - has_preprocessor: boolean; - on_behalf_of_email?: string; -}; - -export type kind = 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor'; - -export type NewScript = { - path: string; - parent_hash?: string; - summary: string; - description: string; - content: string; - schema?: { - [key: string]: unknown; - }; - is_template?: boolean; - lock?: string; - language: ScriptLang; - kind?: 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor'; - tag?: string; - draft_only?: boolean; - envs?: Array<(string)>; - concurrent_limit?: number; - concurrency_time_window_s?: number; - cache_ttl?: number; - dedicated_worker?: boolean; - ws_error_handler_muted?: boolean; - priority?: number; - restart_unless_cancelled?: boolean; - timeout?: number; - delete_after_use?: boolean; - deployment_message?: string; - concurrency_key?: string; - visible_to_runner_only?: boolean; - no_main_func?: boolean; - codebase?: string; - has_preprocessor?: boolean; - on_behalf_of_email?: string; -}; - -export type NewScriptWithDraft = NewScript & { - draft?: NewScript; - hash: string; -}; - -export type ScriptHistory = { - script_hash: string; - deployment_msg?: string; -}; - -export type ScriptArgs = { - [key: string]: unknown; -}; - -export type Input = { - id: string; - name: string; - created_by: string; - created_at: string; - is_public: boolean; - success?: boolean; -}; - -export type CreateInput = { - name: string; - args: { - [key: string]: unknown; - }; -}; - -export type UpdateInput = { - id: string; - name: string; - is_public: boolean; -}; - -export type RunnableType = 'ScriptHash' | 'ScriptPath' | 'FlowPath'; - -export type QueuedJob = { - workspace_id?: string; - id: string; - parent_job?: string; - created_by?: string; - created_at?: string; - started_at?: string; - scheduled_for?: string; - running: boolean; - script_path?: string; - script_hash?: string; - args?: ScriptArgs; - logs?: string; - raw_code?: string; - canceled: boolean; - canceled_by?: string; - canceled_reason?: string; - last_ping?: string; - job_kind: 'script' | 'preview' | 'dependencies' | 'flowdependencies' | 'appdependencies' | 'flow' | 'flowpreview' | 'script_hub' | 'identity' | 'deploymentcallback' | 'singlescriptflow' | 'flowscript' | 'flownode' | 'appscript'; - schedule_path?: string; - /** - * The user (u/userfoo) or group (g/groupfoo) whom - * the execution of this script will be permissioned_as and by extension its DT_TOKEN. - * - */ - permissioned_as: string; - flow_status?: FlowStatus; - raw_flow?: FlowValue; - is_flow_step: boolean; - language?: ScriptLang; - email: string; - visible_to_owner: boolean; - mem_peak?: number; - tag: string; - priority?: number; - self_wait_time_ms?: number; - aggregate_wait_time_ms?: number; - suspend?: number; - preprocessed?: boolean; -}; - -export type job_kind = 'script' | 'preview' | 'dependencies' | 'flowdependencies' | 'appdependencies' | 'flow' | 'flowpreview' | 'script_hub' | 'identity' | 'deploymentcallback' | 'singlescriptflow' | 'flowscript' | 'flownode' | 'appscript'; - -export type CompletedJob = { - workspace_id?: string; - id: string; - parent_job?: string; - created_by: string; - created_at: string; - started_at: string; - duration_ms: number; - success: boolean; - script_path?: string; - script_hash?: string; - args?: ScriptArgs; - result?: unknown; - logs?: string; - deleted?: boolean; - raw_code?: string; - canceled: boolean; - canceled_by?: string; - canceled_reason?: string; - job_kind: 'script' | 'preview' | 'dependencies' | 'flow' | 'flowdependencies' | 'appdependencies' | 'flowpreview' | 'script_hub' | 'identity' | 'deploymentcallback' | 'singlescriptflow' | 'flowscript' | 'flownode' | 'appscript'; - schedule_path?: string; - /** - * The user (u/userfoo) or group (g/groupfoo) whom - * the execution of this script will be permissioned_as and by extension its DT_TOKEN. - * - */ - permissioned_as: string; - flow_status?: FlowStatus; - raw_flow?: FlowValue; - is_flow_step: boolean; - language?: ScriptLang; - is_skipped: boolean; - email: string; - visible_to_owner: boolean; - mem_peak?: number; - tag: string; - priority?: number; - labels?: Array<(string)>; - self_wait_time_ms?: number; - aggregate_wait_time_ms?: number; - preprocessed?: boolean; -}; - -export type ObscuredJob = { - typ?: string; - started_at?: string; - duration_ms?: number; -}; - -export type Job = (CompletedJob & { - type?: 'CompletedJob'; -}) | (QueuedJob & { - type?: 'QueuedJob'; -}); - -export type type = 'CompletedJob'; - -export type User = { - email: string; - username: string; - is_admin: boolean; - name?: string; - is_super_admin: boolean; - created_at: string; - operator: boolean; - disabled: boolean; - groups?: Array<(string)>; - folders: Array<(string)>; - folders_owners: Array<(string)>; -}; - -export type UserUsage = { - email?: string; - executions?: number; -}; - -export type Login = { - email: string; - password: string; -}; - -export type EditWorkspaceUser = { - is_admin?: boolean; - operator?: boolean; - disabled?: boolean; -}; - -export type TruncatedToken = { - label?: string; - expiration?: string; - token_prefix: string; - created_at: string; - last_used_at: string; - scopes?: Array<(string)>; - email?: string; -}; - -export type NewToken = { - label?: string; - expiration?: string; - scopes?: Array<(string)>; - workspace_id?: string; -}; - -export type NewTokenImpersonate = { - label?: string; - expiration?: string; - impersonate_email: string; - workspace_id?: string; -}; - -export type ListableVariable = { - workspace_id: string; - path: string; - value?: string; - is_secret: boolean; - description?: string; - account?: number; - is_oauth?: boolean; - extra_perms: { - [key: string]: (boolean); - }; - is_expired?: boolean; - refresh_error?: string; - is_linked?: boolean; - is_refreshed?: boolean; - expires_at?: string; -}; - -export type ContextualVariable = { - name: string; - value: string; - description: string; - is_custom: boolean; -}; - -export type CreateVariable = { - path: string; - value: string; - is_secret: boolean; - description: string; - account?: number; - is_oauth?: boolean; - expires_at?: string; -}; - -export type EditVariable = { - path?: string; - value?: string; - is_secret?: boolean; - description?: string; -}; - -export type AuditLog = { - id: number; - timestamp: string; - username: string; - operation: 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'ai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.login_failure' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.login_failure' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete'; - action_kind: 'Created' | 'Updated' | 'Delete' | 'Execute'; - resource?: string; - parameters?: { - [key: string]: unknown; - }; -}; - -export type operation = 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'ai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.login_failure' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.login_failure' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete'; - -export type action_kind = 'Created' | 'Updated' | 'Delete' | 'Execute'; - -export type MainArgSignature = { - type: 'Valid' | 'Invalid'; - error: string; - star_args: boolean; - star_kwargs?: boolean; - args: Array<{ - name: string; - typ: ('float' | 'int' | 'bool' | 'email' | 'unknown' | 'bytes' | 'dict' | 'datetime' | 'sql' | { - resource: (string) | null; -} | { - str: Array<(string)> | null; -} | { - object: Array<{ - key: string; - typ: ('float' | 'int' | 'bool' | 'email' | 'unknown' | 'bytes' | 'dict' | 'datetime' | 'sql' | { - str: unknown; -}); - }>; -} | { - list: (('float' | 'int' | 'bool' | 'email' | 'unknown' | 'bytes' | 'dict' | 'datetime' | 'sql' | { - str: unknown; -}) | null); -}); - has_default?: boolean; - default?: unknown; - }>; - no_main_func: (boolean) | null; - has_preprocessor: (boolean) | null; -}; - -export type type2 = 'Valid' | 'Invalid'; - -export type ScriptLang = 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp'; - -export type Preview = { - content?: string; - path?: string; - args: ScriptArgs; - language?: ScriptLang; - tag?: string; - kind?: 'code' | 'identity' | 'http'; - dedicated_worker?: boolean; - lock?: string; -}; - -export type kind2 = 'code' | 'identity' | 'http'; - -export type WorkflowTask = { - args: ScriptArgs; -}; - -export type WorkflowStatusRecord = { - [key: string]: WorkflowStatus; -}; - -export type WorkflowStatus = { - scheduled_for?: string; - started_at?: string; - duration_ms?: number; - name?: string; -}; - -export type CreateResource = { - path: string; - value: unknown; - description?: string; - resource_type: string; -}; - -export type EditResource = { - path?: string; - description?: string; - value?: unknown; -}; - -export type Resource = { - workspace_id?: string; - path: string; - description?: string; - resource_type: string; - value?: unknown; - is_oauth: boolean; - extra_perms?: { - [key: string]: (boolean); - }; - created_by?: string; - edited_at?: string; -}; - -export type ListableResource = { - workspace_id?: string; - path: string; - description?: string; - resource_type: string; - value?: unknown; - is_oauth: boolean; - extra_perms?: { - [key: string]: (boolean); - }; - is_expired?: boolean; - refresh_error?: string; - is_linked: boolean; - is_refreshed: boolean; - account?: number; - created_by?: string; - edited_at?: string; -}; - -export type ResourceType = { - workspace_id?: string; - name: string; - schema?: unknown; - description?: string; - created_by?: string; - edited_at?: string; - format_extension?: string; -}; - -export type EditResourceType = { - schema?: unknown; - description?: string; -}; - -export type Schedule = { - path: string; - edited_by: string; - edited_at: string; - schedule: string; - timezone: string; - enabled: boolean; - script_path: string; - is_flow: boolean; - args?: ScriptArgs; - extra_perms: { - [key: string]: (boolean); - }; - email: string; - error?: string; - on_failure?: string; - on_failure_times?: number; - on_failure_exact?: boolean; - on_failure_extra_args?: ScriptArgs; - on_recovery?: string; - on_recovery_times?: number; - on_recovery_extra_args?: ScriptArgs; - on_success?: string; - on_success_extra_args?: ScriptArgs; - ws_error_handler_muted?: boolean; - retry?: Retry; - summary?: string; - no_flow_overlap?: boolean; - tag?: string; - paused_until?: string; - cron_version?: string; -}; - -export type ScheduleWJobs = Schedule & { - jobs?: Array<{ - id: string; - success: boolean; - duration_ms: number; - }>; -}; - -export type NewSchedule = { - path: string; - schedule: string; - timezone: string; - script_path: string; - is_flow: boolean; - args: ScriptArgs; - enabled?: boolean; - on_failure?: string; - on_failure_times?: number; - on_failure_exact?: boolean; - on_failure_extra_args?: ScriptArgs; - on_recovery?: string; - on_recovery_times?: number; - on_recovery_extra_args?: ScriptArgs; - on_success?: string; - on_success_extra_args?: ScriptArgs; - ws_error_handler_muted?: boolean; - retry?: Retry; - no_flow_overlap?: boolean; - summary?: string; - tag?: string; - paused_until?: string; - cron_version?: string; -}; - -export type EditSchedule = { - schedule: string; - timezone: string; - args: ScriptArgs; - on_failure?: string; - on_failure_times?: number; - on_failure_exact?: boolean; - on_failure_extra_args?: ScriptArgs; - on_recovery?: string; - on_recovery_times?: number; - on_recovery_extra_args?: ScriptArgs; - on_success?: string; - on_success_extra_args?: ScriptArgs; - ws_error_handler_muted?: boolean; - retry?: Retry; - no_flow_overlap?: boolean; - summary?: string; - tag?: string; - paused_until?: string; - cron_version?: string; -}; - -export type TriggerExtraProperty = { - path: string; - script_path: string; - email: string; - extra_perms: { - [key: string]: (boolean); - }; - workspace_id: string; - edited_by: string; - edited_at: string; - is_flow: boolean; -}; - -export type HttpTrigger = TriggerExtraProperty & { - route_path: string; - static_asset_config?: { - s3: string; - storage?: string; - filename?: string; - }; - http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; - is_async: boolean; - requires_auth: boolean; - is_static_website: boolean; -}; - -export type http_method = 'get' | 'post' | 'put' | 'delete' | 'patch'; - -export type NewHttpTrigger = { - path: string; - script_path: string; - route_path: string; - static_asset_config?: { - s3: string; - storage?: string; - filename?: string; - }; - is_flow: boolean; - http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; - is_async: boolean; - requires_auth: boolean; - is_static_website: boolean; -}; - -export type EditHttpTrigger = { - path: string; - script_path: string; - route_path?: string; - static_asset_config?: { - s3: string; - storage?: string; - filename?: string; - }; - is_flow: boolean; - http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; - is_async: boolean; - requires_auth: boolean; - is_static_website: boolean; -}; - -export type TriggersCount = { - primary_schedule?: { - schedule?: string; - }; - schedule_count?: number; - http_routes_count?: number; - webhook_count?: number; - email_count?: number; - websocket_count?: number; - postgres_count?: number; - kafka_count?: number; - nats_count?: number; - mqtt_count?: number; - sqs_count?: number; -}; - -export type WebsocketTrigger = TriggerExtraProperty & { - url: string; - server_id?: string; - last_server_ping?: string; - error?: string; - enabled: boolean; - filters: Array<{ - key: string; - value: unknown; - }>; - initial_messages?: Array; - url_runnable_args?: ScriptArgs; - can_return_message: boolean; -}; - -export type NewWebsocketTrigger = { - path: string; - script_path: string; - is_flow: boolean; - url: string; - enabled?: boolean; - filters: Array<{ - key: string; - value: unknown; - }>; - initial_messages?: Array; - url_runnable_args?: ScriptArgs; - can_return_message: boolean; -}; - -export type EditWebsocketTrigger = { - url: string; - path: string; - script_path: string; - is_flow: boolean; - filters: Array<{ - key: string; - value: unknown; - }>; - initial_messages?: Array; - url_runnable_args?: ScriptArgs; - can_return_message: boolean; -}; - -export type WebsocketTriggerInitialMessage = { - raw_message: string; -} | { - runnable_result: { - path: string; - args: ScriptArgs; - is_flow: boolean; - }; -}; - -export type QoS = 0 | 1 | 2; - -export type CommonMqttConfig = { - will?: { - topic?: string; - message?: Array<(number)>; - qos?: QoS; - retain?: boolean; - }; -}; - -export type MqttV3Config = CommonMqttConfig & { - clean_session?: boolean; -}; - -export type MqttV5Config = CommonMqttConfig & { - clean_start?: boolean; - keep_alive?: number; - session_expiration?: number; - receive_maximum?: number; - maximum_packet_size?: number; -}; - -export type SubscribeTopic = { - qos: QoS; - topic: string; -}; - -export type MqttClientVersion = 'v3' | 'v5'; - -export type MqttTrigger = TriggerExtraProperty & { - mqtt_resource_path: string; - subscribe_topics: Array; - v3_config?: MqttV3Config; - v5_config?: MqttV5Config; - client_id?: string; - client_version?: MqttClientVersion; - server_id?: string; - last_server_ping?: string; - error?: string; - enabled: boolean; -}; - -export type NewMqttTrigger = { - mqtt_resource_path: string; - subscribe_topics: Array; - client_id?: string; - v3_config?: MqttV3Config; - v5_config?: MqttV5Config; - client_version?: MqttClientVersion; - path: string; - script_path: string; - is_flow: boolean; - enabled?: boolean; -}; - -export type EditMqttTrigger = { - mqtt_resource_path: string; - subscribe_topics: Array; - client_id?: string; - v3_config?: MqttV3Config; - v5_config?: MqttV5Config; - client_version?: MqttClientVersion; - path: string; - script_path: string; - is_flow: boolean; - enabled: boolean; -}; - -export type SqsTrigger = TriggerExtraProperty & { - queue_url: string; - aws_resource_path: string; - message_attributes?: Array<(string)>; - server_id?: string; - last_server_ping?: string; - error?: string; - enabled: boolean; -}; - -export type NewSqsTrigger = { - queue_url: string; - aws_resource_path: string; - message_attributes?: Array<(string)>; - path: string; - script_path: string; - is_flow: boolean; - enabled?: boolean; -}; - -export type EditSqsTrigger = { - queue_url: string; - aws_resource_path: string; - message_attributes?: Array<(string)>; - path: string; - script_path: string; - is_flow: boolean; - enabled: boolean; -}; - -export type Slot = { - name?: string; -}; - -export type SlotList = { - slot_name?: string; - active?: boolean; -}; - -export type PublicationData = { - table_to_track?: Array; - transaction_to_track: Array<(string)>; -}; - -export type TableToTrack = Array<{ - table_name: string; - columns_name?: Array<(string)>; - where_clause?: string; -}>; - -export type Relations = { - schema_name: string; - table_to_track: TableToTrack; -}; - -export type Language = 'Typescript'; - -export type TemplateScript = { - postgres_resource_path: string; - relations: Array; - language: Language; -}; - -export type PostgresTrigger = TriggerExtraProperty & { - enabled: boolean; - postgres_resource_path: string; - publication_name: string; - server_id?: string; - replication_slot_name: string; - error?: string; - last_server_ping?: string; -}; - -export type NewPostgresTrigger = { - replication_slot_name?: string; - publication_name?: string; - path: string; - script_path: string; - is_flow: boolean; - enabled: boolean; - postgres_resource_path: string; - publication?: PublicationData; -}; - -export type EditPostgresTrigger = { - replication_slot_name: string; - publication_name: string; - path: string; - script_path: string; - is_flow: boolean; - enabled: boolean; - postgres_resource_path: string; - publication?: PublicationData; -}; - -export type KafkaTrigger = TriggerExtraProperty & { - kafka_resource_path: string; - group_id: string; - topics: Array<(string)>; - server_id?: string; - last_server_ping?: string; - error?: string; - enabled: boolean; -}; - -export type NewKafkaTrigger = { - path: string; - script_path: string; - is_flow: boolean; - kafka_resource_path: string; - group_id: string; - topics: Array<(string)>; - enabled?: boolean; -}; - -export type EditKafkaTrigger = { - kafka_resource_path: string; - group_id: string; - topics: Array<(string)>; - path: string; - script_path: string; - is_flow: boolean; -}; - -export type NatsTrigger = TriggerExtraProperty & { - nats_resource_path: string; - use_jetstream: boolean; - stream_name?: string; - consumer_name?: string; - subjects: Array<(string)>; - server_id?: string; - last_server_ping?: string; - error?: string; - enabled: boolean; -}; - -export type NewNatsTrigger = { - path: string; - script_path: string; - is_flow: boolean; - nats_resource_path: string; - use_jetstream: boolean; - stream_name?: string; - consumer_name?: string; - subjects: Array<(string)>; - enabled?: boolean; -}; - -export type EditNatsTrigger = { - nats_resource_path: string; - use_jetstream: boolean; - stream_name?: string; - consumer_name?: string; - subjects: Array<(string)>; - path: string; - script_path: string; - is_flow: boolean; -}; - -export type Group = { - name: string; - summary?: string; - members?: Array<(string)>; - extra_perms?: { - [key: string]: (boolean); - }; -}; - -export type InstanceGroup = { - name: string; - summary?: string; - emails?: Array<(string)>; -}; - -export type Folder = { - name: string; - owners: Array<(string)>; - extra_perms: { - [key: string]: (boolean); - }; - summary?: string; - created_by?: string; - edited_at?: string; -}; - -export type WorkerPing = { - worker: string; - worker_instance: string; - last_ping?: number; - started_at: string; - ip: string; - jobs_executed: number; - custom_tags?: Array<(string)>; - worker_group: string; - wm_version: string; - last_job_id?: string; - last_job_workspace_id?: string; - occupancy_rate?: number; - occupancy_rate_15s?: number; - occupancy_rate_5m?: number; - occupancy_rate_30m?: number; - memory?: number; - vcpus?: number; - memory_usage?: number; - wm_memory_usage?: number; -}; - -export type UserWorkspaceList = { - email: string; - workspaces: Array<{ - id: string; - name: string; - username: string; - color: string; - operator_settings?: OperatorSettings; - }>; -}; - -export type CreateWorkspace = { - id: string; - name: string; - username?: string; - color?: string; -}; - -export type Workspace = { - id: string; - name: string; - owner: string; - domain?: string; - color?: string; -}; - -export type WorkspaceInvite = { - workspace_id: string; - email: string; - is_admin: boolean; - operator: boolean; -}; - -export type GlobalUserInfo = { - email: string; - login_type: 'password' | 'github'; - super_admin: boolean; - devops?: boolean; - verified: boolean; - name?: string; - company?: string; - username?: string; - operator_only?: boolean; -}; - -export type login_type = 'password' | 'github'; - -export type Flow = OpenFlow & FlowMetadata; - -export type ExtraPerms = { - [key: string]: (boolean); -}; - -export type FlowMetadata = { - workspace_id?: string; - path: string; - edited_by: string; - edited_at: string; - archived: boolean; - extra_perms: ExtraPerms; - starred?: boolean; - draft_only?: boolean; - tag?: string; - ws_error_handler_muted?: boolean; - priority?: number; - dedicated_worker?: boolean; - timeout?: number; - visible_to_runner_only?: boolean; - on_behalf_of_email?: string; -}; - -export type OpenFlowWPath = OpenFlow & { - path: string; - tag?: string; - ws_error_handler_muted?: boolean; - priority?: number; - dedicated_worker?: boolean; - timeout?: number; - visible_to_runner_only?: boolean; - on_behalf_of_email?: string; -}; - -export type FlowPreview = { - value: FlowValue; - path?: string; - args: ScriptArgs; - tag?: string; - restarted_from?: RestartedFrom; -}; - -export type RestartedFrom = { - flow_job_id?: string; - step_id?: string; - branch_or_iteration_n?: number; -}; - -export type Policy = { - triggerables?: { - [key: string]: { - [key: string]: unknown; - }; - }; - triggerables_v2?: { - [key: string]: { - [key: string]: unknown; - }; - }; - s3_inputs?: Array<{ - [key: string]: unknown; - }>; - execution_mode?: 'viewer' | 'publisher' | 'anonymous'; - on_behalf_of?: string; - on_behalf_of_email?: string; -}; - -export type execution_mode = 'viewer' | 'publisher' | 'anonymous'; - -export type ListableApp = { - id: number; - workspace_id: string; - path: string; - summary: string; - version: number; - extra_perms: { - [key: string]: (boolean); - }; - starred?: boolean; - edited_at: string; - execution_mode: 'viewer' | 'publisher' | 'anonymous'; -}; - -export type ListableRawApp = { - workspace_id: string; - path: string; - summary: string; - extra_perms: { - [key: string]: (boolean); - }; - starred?: boolean; - version: number; - edited_at: string; -}; - -export type AppWithLastVersion = { - id: number; - workspace_id: string; - path: string; - summary: string; - versions: Array<(number)>; - created_by: string; - created_at: string; - value: { - [key: string]: unknown; - }; - policy: Policy; - execution_mode: 'viewer' | 'publisher' | 'anonymous'; - extra_perms: { - [key: string]: (boolean); - }; - custom_path?: string; -}; - -export type AppWithLastVersionWDraft = AppWithLastVersion & { - draft_only?: boolean; - draft?: unknown; -}; - -export type AppHistory = { - version: number; - deployment_msg?: string; -}; - -export type FlowVersion = { - id: number; - created_at: string; - deployment_msg?: string; -}; - -export type SlackToken = { - access_token: string; - team_id: string; - team_name: string; - bot: { - bot_access_token?: string; - }; -}; - -export type TokenResponse = { - access_token: string; - expires_in?: number; - refresh_token?: string; - scope?: Array<(string)>; -}; - -export type HubScriptKind = unknown; - -export type PolarsClientKwargs = { - region_name: string; -}; - -export type LargeFileStorage = { - type?: 'S3Storage' | 'AzureBlobStorage' | 'AzureWorkloadIdentity' | 'S3AwsOidc'; - s3_resource_path?: string; - azure_blob_resource_path?: string; - public_resource?: boolean; - secondary_storage?: { - [key: string]: { - type?: 'S3Storage' | 'AzureBlobStorage' | 'AzureWorkloadIdentity' | 'S3AwsOidc'; - s3_resource_path?: string; - azure_blob_resource_path?: string; - public_resource?: boolean; - }; - }; -}; - -export type type3 = 'S3Storage' | 'AzureBlobStorage' | 'AzureWorkloadIdentity' | 'S3AwsOidc'; - -export type WindmillLargeFile = { - s3: string; -}; - -export type WindmillFileMetadata = { - mime_type?: string; - size_in_bytes?: number; - last_modified?: string; - expires?: string; - version_id?: string; -}; - -export type WindmillFilePreview = { - msg?: string; - content?: string; - content_type: 'RawText' | 'Csv' | 'Parquet' | 'Unknown'; -}; - -export type content_type = 'RawText' | 'Csv' | 'Parquet' | 'Unknown'; - -export type S3Resource = { - bucket: string; - region: string; - endPoint: string; - useSSL: boolean; - accessKey?: string; - secretKey?: string; - pathStyle: boolean; -}; - -export type WorkspaceGitSyncSettings = { - include_path?: Array<(string)>; - include_type?: Array<('script' | 'flow' | 'app' | 'folder' | 'resource' | 'variable' | 'secret' | 'resourcetype' | 'schedule' | 'user' | 'group')>; - repositories?: Array; -}; - -export type WorkspaceDeployUISettings = { - include_path?: Array<(string)>; - include_type?: Array<('script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret')>; -}; - -export type WorkspaceDefaultScripts = { - order?: Array<(string)>; - hidden?: Array<(string)>; - default_script_content?: { - [key: string]: (string); - }; -}; - -export type GitRepositorySettings = { - script_path: string; - git_repo_resource_path: string; - use_individual_branch?: boolean; - group_by_folder?: boolean; - exclude_types_override?: Array<('script' | 'flow' | 'app' | 'folder' | 'resource' | 'variable' | 'secret' | 'resourcetype' | 'schedule' | 'user' | 'group')>; -}; - -export type UploadFilePart = { - part_number: number; - tag: string; -}; - -export type MetricMetadata = { - id: string; - name?: string; -}; - -export type ScalarMetric = { - metric_id?: string; - value: number; -}; - -export type TimeseriesMetric = { - metric_id?: string; - values: Array; -}; - -export type MetricDataPoint = { - timestamp: string; - value: number; -}; - -export type RawScriptForDependencies = { - raw_code: string; - path: string; - language: ScriptLang; -}; - -export type ConcurrencyGroup = { - concurrency_key: string; - total_running: number; -}; - -export type ExtendedJobs = { - jobs: Array; - obscured_jobs: Array; - /** - * Obscured jobs omitted for security because of too specific filtering - */ - omitted_obscured_jobs?: boolean; -}; - -export type ExportedUser = { - email: string; - password_hash?: string; - super_admin: boolean; - verified: boolean; - name?: string; - company?: string; - first_time_user: boolean; - username?: string; -}; - -export type GlobalSetting = { - name: string; - value: { - [key: string]: unknown; - }; -}; - -export type Config = { - name: string; - config?: { - [key: string]: unknown; - }; -}; - -export type ExportedInstanceGroup = { - name: string; - summary?: string; - emails?: Array<(string)>; - id?: string; - scim_display_name?: string; - external_id?: string; -}; - -export type JobSearchHit = { - dancer?: string; -}; - -export type LogSearchHit = { - dancer?: string; -}; - -export type AutoscalingEvent = { - id?: number; - worker_group?: string; - event_type?: string; - desired_workers?: number; - reason?: string; - applied_at?: string; -}; - -export type CriticalAlert = { - /** - * Unique identifier for the alert - */ - id?: number; - /** - * Type of alert (e.g., critical_error) - */ - alert_type?: string; - /** - * The message content of the alert - */ - message?: string; - /** - * Time when the alert was created - */ - created_at?: string; - /** - * Acknowledgment status of the alert, can be true, false, or null if not set - */ - acknowledged?: (boolean) | null; - /** - * Workspace id if the alert is in the scope of a workspace - */ - workspace_id?: (string) | null; -}; - -export type CaptureTriggerKind = 'webhook' | 'http' | 'websocket' | 'kafka' | 'email' | 'nats' | 'postgres' | 'sqs' | 'mqtt'; - -export type Capture = { - trigger_kind: CaptureTriggerKind; - payload: unknown; - trigger_extra?: unknown; - id: number; - created_at: string; -}; - -export type CaptureConfig = { - trigger_config?: unknown; - trigger_kind: CaptureTriggerKind; - error?: string; - last_server_ping?: string; -}; - -export type OperatorSettings = { - /** - * Whether operators can view runs - */ - runs: boolean; - /** - * Whether operators can view schedules - */ - schedules: boolean; - /** - * Whether operators can view resources - */ - resources: boolean; - /** - * Whether operators can view variables - */ - variables: boolean; - /** - * Whether operators can view audit logs - */ - audit_logs: boolean; - /** - * Whether operators can view triggers - */ - triggers: boolean; - /** - * Whether operators can view groups page - */ - groups: boolean; - /** - * Whether operators can view folders page - */ - folders: boolean; - /** - * Whether operators can view workers page - */ - workers: boolean; -} | null; - -export type TeamInfo = { - /** - * The unique identifier of the Microsoft Teams team - */ - team_id: string; - /** - * The display name of the Microsoft Teams team - */ - team_name: string; - /** - * List of channels within the team - */ - channels: Array; -}; - -export type ChannelInfo = { - /** - * The unique identifier of the channel - */ - channel_id: string; - /** - * The display name of the channel - */ - channel_name: string; - /** - * The Microsoft Teams tenant identifier - */ - tenant_id: string; - /** - * The service URL for the channel - */ - service_url: string; -}; - -export type OpenFlow = { - summary: string; - description?: string; - value: FlowValue; - schema?: { - [key: string]: unknown; - }; -}; - -export type FlowValue = { - modules: Array; - failure_module?: FlowModule; - preprocessor_module?: FlowModule; - same_worker?: boolean; - concurrent_limit?: number; - concurrency_key?: string; - concurrency_time_window_s?: number; - skip_expr?: string; - cache_ttl?: number; - priority?: number; - early_return?: string; -}; - -export type Retry = { - constant?: { - attempts?: number; - seconds?: number; - }; - exponential?: { - attempts?: number; - multiplier?: number; - seconds?: number; - random_factor?: number; - }; -}; - -export type FlowModule = { - id: string; - value: FlowModuleValue; - stop_after_if?: { - skip_if_stopped?: boolean; - expr: string; - }; - stop_after_all_iters_if?: { - skip_if_stopped?: boolean; - expr: string; - }; - skip_if?: { - expr: string; - }; - sleep?: InputTransform; - cache_ttl?: number; - timeout?: number; - delete_after_use?: boolean; - summary?: string; - mock?: { - enabled?: boolean; - return_value?: unknown; - }; - suspend?: { - required_events?: number; - timeout?: number; - resume_form?: { - schema?: { - [key: string]: unknown; - }; - }; - user_auth_required?: boolean; - user_groups_required?: InputTransform; - self_approval_disabled?: boolean; - hide_cancel?: boolean; - continue_on_disapprove_timeout?: boolean; - }; - priority?: number; - continue_on_error?: boolean; - retry?: Retry; -}; - -export type InputTransform = StaticTransform | JavascriptTransform; - -export type StaticTransform = { - value?: unknown; - type: 'static'; -}; - -export type JavascriptTransform = { - expr: string; - type: 'javascript'; -}; - -export type FlowModuleValue = RawScript | PathScript | PathFlow | ForloopFlow | WhileloopFlow | BranchOne | BranchAll | Identity; - -export type RawScript = { - input_transforms: { - [key: string]: InputTransform; - }; - content: string; - language: 'deno' | 'bun' | 'python3' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'php'; - path?: string; - lock?: string; - type: 'rawscript'; - tag?: string; - concurrent_limit?: number; - concurrency_time_window_s?: number; - custom_concurrency_key?: string; - is_trigger?: boolean; -}; - -export type language = 'deno' | 'bun' | 'python3' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'php'; - -export type PathScript = { - input_transforms: { - [key: string]: InputTransform; - }; - path: string; - hash?: string; - type: 'script'; - tag_override?: string; - is_trigger?: boolean; -}; - -export type PathFlow = { - input_transforms: { - [key: string]: InputTransform; - }; - path: string; - type: 'flow'; -}; - -export type ForloopFlow = { - modules: Array; - iterator: InputTransform; - skip_failures: boolean; - type: 'forloopflow'; - parallel?: boolean; - parallelism?: number; -}; - -export type WhileloopFlow = { - modules: Array; - skip_failures: boolean; - type: 'whileloopflow'; - parallel?: boolean; - parallelism?: number; -}; - -export type BranchOne = { - branches: Array<{ - summary?: string; - expr: string; - modules: Array; - }>; - default: Array; - type: 'branchone'; -}; - -export type BranchAll = { - branches: Array<{ - summary?: string; - skip_failure?: boolean; - modules: Array; - }>; - type: 'branchall'; - parallel?: boolean; -}; - -export type Identity = { - type: 'identity'; - flow?: boolean; -}; - -export type FlowStatus = { - step: number; - modules: Array; - user_states?: { - [key: string]: unknown; - }; - preprocessor_module?: (FlowStatusModule); - failure_module: (FlowStatusModule & { - parent_module?: string; -}); - retry?: { - fail_count?: number; - failed_jobs?: Array<(string)>; - }; -}; - -export type FlowStatusModule = { - type: 'WaitingForPriorSteps' | 'WaitingForEvents' | 'WaitingForExecutor' | 'InProgress' | 'Success' | 'Failure'; - id?: string; - job?: string; - count?: number; - progress?: number; - iterator?: { - index?: number; - itered?: Array; - args?: unknown; - }; - flow_jobs?: Array<(string)>; - flow_jobs_success?: Array<(boolean)>; - branch_chosen?: { - type: 'branch' | 'default'; - branch?: number; - }; - branchall?: { - branch: number; - len: number; - }; - approvers?: Array<{ - resume_id: number; - approver: string; - }>; - failed_retries?: Array<(string)>; - skipped?: boolean; -}; - -export type type4 = 'WaitingForPriorSteps' | 'WaitingForEvents' | 'WaitingForExecutor' | 'InProgress' | 'Success' | 'Failure'; - -export type ParameterId = string; - -export type ParameterKey = string; - -export type ParameterWorkspaceId = string; - -export type ParameterPublicationName = string; - -export type ParameterVersionId = number; - -export type ParameterToken = string; - -export type ParameterAccountId = number; - -export type ParameterClientName = string; - -export type ParameterScriptPath = string; - -export type ParameterScriptHash = string; - -export type ParameterJobId = string; - -export type ParameterPath = string; - -export type ParameterCustomPath = string; - -export type ParameterPathId = number; - -export type ParameterPathVersion = number; - -export type ParameterName = string; - -/** - * which page to return (start at 1, default 1) - */ -export type ParameterPage = number; - -/** - * number of items to return for a given page (default 30, max 100) - */ -export type ParameterPerPage = number; - -/** - * order by desc order (default true) - */ -export type ParameterOrderDesc = boolean; - -/** - * mask to filter exact matching user creator - */ -export type ParameterCreatedBy = string; - -/** - * mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') - */ -export type ParameterLabel = string; - -/** - * The parent job that is at the origin and responsible for the execution of this script if any - */ -export type ParameterParentJob = string; - -/** - * Override the tag to use - */ -export type ParameterWorkerTag = string; - -/** - * Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl - */ -export type ParameterCacheTtl = string; - -/** - * The job id to assign to the created job. if missing, job is chosen randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) - */ -export type ParameterNewJobId = string; - -/** - * List of headers's keys (separated with ',') whove value are added to the args - * Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key - * - */ -export type ParameterIncludeHeader = string; - -/** - * The maximum size of the queue for which the request would get rejected if that job would push it above that limit - * - */ -export type ParameterQueueLimit = string; - -/** - * The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent - * `encodeURIComponent(btoa(JSON.stringify({a: 2})))` - * - */ -export type ParameterPayload = string; - -/** - * mask to filter matching starting path - */ -export type ParameterScriptStartPath = string; - -/** - * mask to filter by schedule path - */ -export type ParameterSchedulePath = string; - -/** - * mask to filter exact matching path - */ -export type ParameterScriptExactPath = string; - -/** - * mask to filter exact matching path - */ -export type ParameterScriptExactHash = string; - -/** - * filter on created before (inclusive) timestamp - */ -export type ParameterCreatedBefore = string; - -/** - * filter on created after (exclusive) timestamp - */ -export type ParameterCreatedAfter = string; - -/** - * filter on started before (inclusive) timestamp - */ -export type ParameterStartedBefore = string; - -/** - * filter on started after (exclusive) timestamp - */ -export type ParameterStartedAfter = string; - -/** - * filter on started before (inclusive) timestamp - */ -export type ParameterBefore = string; - -/** - * filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp - */ -export type ParameterCreatedOrStartedAfter = string; - -/** - * filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs - */ -export type ParameterCreatedOrStartedAfterCompletedJob = string; - -/** - * filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp - */ -export type ParameterCreatedOrStartedBefore = string; - -/** - * filter on successful jobs - */ -export type ParameterSuccess = boolean; - -/** - * filter on jobs scheduled_for before now (hence waitinf for a worker) - */ -export type ParameterScheduledForBeforeNow = boolean; - -/** - * filter on suspended jobs - */ -export type ParameterSuspended = boolean; - -/** - * filter on running jobs - */ -export type ParameterRunning = boolean; - -/** - * filter on jobs containing those args as a json subset (@> in postgres) - */ -export type ParameterArgsFilter = string; - -/** - * filter on jobs with a given tag/worker group - */ -export type ParameterTag = string; - -/** - * filter on jobs containing those result as a json subset (@> in postgres) - */ -export type ParameterResultFilter = string; - -/** - * filter on created after (exclusive) timestamp - */ -export type ParameterAfter = string; - -/** - * filter on exact username of user - */ -export type ParameterUsername = string; - -/** - * filter on exact or prefix name of operation - */ -export type ParameterOperation = string; - -/** - * filter on exact or prefix name of resource - */ -export type ParameterResourceName = string; - -/** - * filter on type of operation - */ -export type ParameterActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'; - -/** - * filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, - */ -export type ParameterJobKinds = string; - -export type ParameterRunnableId = string; - -export type ParameterRunnableTypeQuery = RunnableType; - -export type ParameterInputId = string; - -export type ParameterGetStarted = boolean; - -export type ParameterConcurrencyId = string; - -export type ParameterRunnableKind = 'script' | 'flow'; - -export type BackendVersionResponse = (string); - -export type BackendUptodateResponse = (string); - -export type GetLicenseIdResponse = (string); - -export type GetOpenApiYamlResponse = (string); - -export type GetAuditLogData = { - id: number; - workspace: string; -}; - -export type GetAuditLogResponse = (AuditLog); - -export type ListAuditLogsData = { - /** - * filter on type of operation - */ - actionKind?: 'Create' | 'Update' | 'Delete' | 'Execute'; - /** - * filter on created after (exclusive) timestamp - */ - after?: string; - /** - * filter on started before (inclusive) timestamp - */ - before?: string; - /** - * comma separated list of operations to exclude - */ - excludeOperations?: string; - /** - * filter on exact or prefix name of operation - */ - operation?: string; - /** - * comma separated list of exact operations to include - */ - operations?: string; - /** - * which page to return (start at 1, default 1) - */ - page?: number; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; - /** - * filter on exact or prefix name of resource - */ - resource?: string; - /** - * filter on exact username of user - */ - username?: string; - workspace: string; -}; - -export type ListAuditLogsResponse = (Array); - -export type LoginData = { - /** - * credentials - */ - requestBody: Login; -}; - -export type LoginResponse = (string); - -export type LogoutResponse = (string); - -export type GetUserData = { - username: string; - workspace: string; -}; - -export type GetUserResponse = (User); - -export type UpdateUserData = { - /** - * new user - */ - requestBody: EditWorkspaceUser; - username: string; - workspace: string; -}; - -export type UpdateUserResponse = (string); - -export type IsOwnerOfPathData = { - path: string; - workspace: string; -}; - -export type IsOwnerOfPathResponse = (boolean); - -export type SetPasswordData = { - /** - * set password - */ - requestBody: { - password: string; - }; -}; - -export type SetPasswordResponse = (string); - -export type SetPasswordForUserData = { - /** - * set password - */ - requestBody: { - password: string; - }; - user: string; -}; - -export type SetPasswordForUserResponse = (string); - -export type SetLoginTypeForUserData = { - /** - * set login type - */ - requestBody: { - login_type: string; - }; - user: string; -}; - -export type SetLoginTypeForUserResponse = (string); - -export type CreateUserGloballyData = { - /** - * user info - */ - requestBody: { - email: string; - password: string; - super_admin: boolean; - name?: string; - company?: string; - }; -}; - -export type CreateUserGloballyResponse = (string); - -export type GlobalUserUpdateData = { - email: string; - /** - * new user info - */ - requestBody: { - is_super_admin?: boolean; - is_devops?: boolean; - name?: string; - }; -}; - -export type GlobalUserUpdateResponse = (string); - -export type GlobalUsernameInfoData = { - email: string; -}; - -export type GlobalUsernameInfoResponse = ({ - username: string; - workspace_usernames: Array<{ - workspace_id: string; - username: string; - }>; -}); - -export type GlobalUserRenameData = { - email: string; - /** - * new username - */ - requestBody: { - new_username: string; - }; -}; - -export type GlobalUserRenameResponse = (string); - -export type GlobalUserDeleteData = { - email: string; -}; - -export type GlobalUserDeleteResponse = (string); - -export type GlobalUsersOverwriteData = { - /** - * List of users - */ - requestBody: Array; -}; - -export type GlobalUsersOverwriteResponse = (string); - -export type GlobalUsersExportResponse = (Array); - -export type DeleteUserData = { - username: string; - workspace: string; -}; - -export type DeleteUserResponse = (string); - -export type ListWorkspacesResponse = (Array); - -export type IsDomainAllowedResponse = (boolean); - -export type ListUserWorkspacesResponse = (UserWorkspaceList); - -export type ListWorkspacesAsSuperAdminData = { - /** - * which page to return (start at 1, default 1) - */ - page?: number; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; -}; - -export type ListWorkspacesAsSuperAdminResponse = (Array); - -export type CreateWorkspaceData = { - /** - * new token - */ - requestBody: CreateWorkspace; -}; - -export type CreateWorkspaceResponse = (string); - -export type ExistsWorkspaceData = { - /** - * id of workspace - */ - requestBody: { - id: string; - }; -}; - -export type ExistsWorkspaceResponse = (boolean); - -export type ExistsUsernameData = { - requestBody: { - id: string; - username: string; - }; -}; - -export type ExistsUsernameResponse = (boolean); - -export type GetGlobalData = { - key: string; -}; - -export type GetGlobalResponse = (unknown); - -export type SetGlobalData = { - key: string; - /** - * value set - */ - requestBody: { - value?: unknown; - }; -}; - -export type SetGlobalResponse = (string); - -export type GetLocalResponse = (unknown); - -export type TestSmtpData = { - /** - * test smtp payload - */ - requestBody: { - to: string; - smtp: { - host: string; - username: string; - password: string; - port: number; - from: string; - tls_implicit: boolean; - disable_tls: boolean; - }; - }; -}; - -export type TestSmtpResponse = (string); - -export type TestCriticalChannelsData = { - /** - * test critical channel payload - */ - requestBody: Array<{ - email?: string; - slack_channel?: string; - }>; -}; - -export type TestCriticalChannelsResponse = (string); - -export type GetCriticalAlertsData = { - acknowledged?: (boolean) | null; - page?: number; - pageSize?: number; -}; - -export type GetCriticalAlertsResponse = ({ - alerts?: Array; - /** - * Total number of rows matching the query. - */ - total_rows?: number; - /** - * Total number of pages based on the page size. - */ - total_pages?: number; -}); - -export type AcknowledgeCriticalAlertData = { - /** - * The ID of the critical alert to acknowledge - */ - id: number; -}; - -export type AcknowledgeCriticalAlertResponse = (string); - -export type AcknowledgeAllCriticalAlertsResponse = (string); - -export type TestLicenseKeyData = { - /** - * test license key - */ - requestBody: { - license_key: string; - }; -}; - -export type TestLicenseKeyResponse = (string); - -export type TestObjectStorageConfigData = { - /** - * test object storage config - */ - requestBody: { - [key: string]: unknown; - }; -}; - -export type TestObjectStorageConfigResponse = (string); - -export type SendStatsResponse = (string); - -export type GetLatestKeyRenewalAttemptResponse = ({ - result: string; - attempted_at: string; -} | null); - -export type RenewLicenseKeyData = { - licenseKey?: string; -}; - -export type RenewLicenseKeyResponse = (string); - -export type CreateCustomerPortalSessionData = { - licenseKey?: string; -}; - -export type CreateCustomerPortalSessionResponse = (string); - -export type TestMetadataData = { - /** - * test metadata - */ - requestBody: string; -}; - -export type TestMetadataResponse = (string); - -export type ListGlobalSettingsResponse = (Array); - -export type GetCurrentEmailResponse = (string); - -export type RefreshUserTokenData = { - ifExpiringInLessThanS?: number; -}; - -export type RefreshUserTokenResponse = (string); - -export type GetTutorialProgressResponse = ({ - progress?: number; -}); - -export type UpdateTutorialProgressData = { - /** - * progress update - */ - requestBody: { - progress?: number; - }; -}; - -export type UpdateTutorialProgressResponse = (string); - -export type LeaveInstanceResponse = (string); - -export type GetUsageResponse = (number); - -export type GetRunnableResponse = ({ - workspace: string; - endpoint_async: string; - endpoint_sync: string; - endpoint_openai_sync: string; - summary: string; - description?: string; - kind: string; -}); - -export type GlobalWhoamiResponse = (GlobalUserInfo); - -export type ListWorkspaceInvitesResponse = (Array); - -export type WhoamiData = { - workspace: string; -}; - -export type WhoamiResponse = (User); - -export type AcceptInviteData = { - /** - * accept invite - */ - requestBody: { - workspace_id: string; - username?: string; - }; -}; - -export type AcceptInviteResponse = (string); - -export type DeclineInviteData = { - /** - * decline invite - */ - requestBody: { - workspace_id: string; - }; -}; - -export type DeclineInviteResponse = (string); - -export type InviteUserData = { - /** - * WorkspaceInvite - */ - requestBody: { - email: string; - is_admin: boolean; - operator: boolean; - }; - workspace: string; -}; - -export type InviteUserResponse = (string); - -export type AddUserData = { - /** - * WorkspaceInvite - */ - requestBody: { - email: string; - is_admin: boolean; - username?: string; - operator: boolean; - }; - workspace: string; -}; - -export type AddUserResponse = (string); - -export type DeleteInviteData = { - /** - * WorkspaceInvite - */ - requestBody: { - email: string; - is_admin: boolean; - operator: boolean; - }; - workspace: string; -}; - -export type DeleteInviteResponse = (string); - -export type ArchiveWorkspaceData = { - workspace: string; -}; - -export type ArchiveWorkspaceResponse = (string); - -export type UnarchiveWorkspaceData = { - workspace: string; -}; - -export type UnarchiveWorkspaceResponse = (string); - -export type DeleteWorkspaceData = { - workspace: string; -}; - -export type DeleteWorkspaceResponse = (string); - -export type LeaveWorkspaceData = { - workspace: string; -}; - -export type LeaveWorkspaceResponse = (string); - -export type GetWorkspaceNameData = { - workspace: string; -}; - -export type GetWorkspaceNameResponse = (string); - -export type ChangeWorkspaceNameData = { - requestBody?: { - new_name?: string; - }; - workspace: string; -}; - -export type ChangeWorkspaceNameResponse = (string); - -export type ChangeWorkspaceIdData = { - requestBody?: { - new_id?: string; - new_name?: string; - }; - workspace: string; -}; - -export type ChangeWorkspaceIdResponse = (string); - -export type ChangeWorkspaceColorData = { - requestBody?: { - color?: string; - }; - workspace: string; -}; - -export type ChangeWorkspaceColorResponse = (string); - -export type WhoisData = { - username: string; - workspace: string; -}; - -export type WhoisResponse = (User); - -export type UpdateOperatorSettingsData = { - requestBody: OperatorSettings; - workspace: string; -}; - -export type UpdateOperatorSettingsResponse = (string); - -export type ExistsEmailData = { - email: string; -}; - -export type ExistsEmailResponse = (boolean); - -export type ListUsersAsSuperAdminData = { - /** - * filter only active users - */ - activeOnly?: boolean; - /** - * which page to return (start at 1, default 1) - */ - page?: number; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; -}; - -export type ListUsersAsSuperAdminResponse = (Array); - -export type ListPendingInvitesData = { - workspace: string; -}; - -export type ListPendingInvitesResponse = (Array); - -export type GetSettingsData = { - workspace: string; -}; - -export type GetSettingsResponse = ({ - workspace_id?: string; - slack_name?: string; - slack_team_id?: string; - slack_command_script?: string; - teams_team_id?: string; - teams_command_script?: string; - teams_team_name?: string; - auto_invite_domain?: string; - auto_invite_operator?: boolean; - auto_add?: boolean; - plan?: string; - automatic_billing: boolean; - customer_id?: string; - webhook?: string; - deploy_to?: string; - ai_resource?: AIResource; - code_completion_model?: string; - ai_models: Array<(string)>; - error_handler?: string; - error_handler_extra_args?: ScriptArgs; - error_handler_muted_on_cancel: boolean; - large_file_storage?: LargeFileStorage; - git_sync?: WorkspaceGitSyncSettings; - deploy_ui?: WorkspaceDeployUISettings; - default_app?: string; - default_scripts?: WorkspaceDefaultScripts; - mute_critical_alerts?: boolean; - color?: string; - operator_settings?: OperatorSettings; -}); - -export type GetDeployToData = { - workspace: string; -}; - -export type GetDeployToResponse = ({ - deploy_to?: string; -}); - -export type GetIsPremiumData = { - workspace: string; -}; - -export type GetIsPremiumResponse = (boolean); - -export type GetPremiumInfoData = { - workspace: string; -}; - -export type GetPremiumInfoResponse = ({ - premium: boolean; - usage?: number; - seats?: number; - automatic_billing: boolean; - owner: string; -}); - -export type SetAutomaticBillingData = { - /** - * automatic billing - */ - requestBody: { - automatic_billing: boolean; - seats?: number; - }; - workspace: string; -}; - -export type SetAutomaticBillingResponse = (string); - -export type GetThresholdAlertData = { - workspace: string; -}; - -export type GetThresholdAlertResponse = ({ - threshold_alert_amount?: number; - last_alert_sent?: string; -}); - -export type SetThresholdAlertData = { - /** - * threshold alert info - */ - requestBody: { - threshold_alert_amount?: number; - }; - workspace: string; -}; - -export type SetThresholdAlertResponse = (string); - -export type EditSlackCommandData = { - /** - * WorkspaceInvite - */ - requestBody: { - slack_command_script?: string; - }; - workspace: string; -}; - -export type EditSlackCommandResponse = (string); - -export type EditTeamsCommandData = { - /** - * WorkspaceInvite - */ - requestBody: { - slack_command_script?: string; - }; - workspace: string; -}; - -export type EditTeamsCommandResponse = (string); - -export type ListAvailableTeamsIdsData = { - workspace: string; -}; - -export type ListAvailableTeamsIdsResponse = (Array<{ - team_name?: string; - team_id?: string; -}>); - -export type ListAvailableTeamsChannelsData = { - workspace: string; -}; - -export type ListAvailableTeamsChannelsResponse = (Array<{ - channel_name?: string; - channel_id?: string; - service_url?: string; - tenant_id?: string; -}>); - -export type ConnectTeamsData = { - /** - * connect teams - */ - requestBody: { - team_id?: string; - team_name?: string; - }; - workspace: string; -}; - -export type ConnectTeamsResponse = (string); - -export type RunSlackMessageTestJobData = { - /** - * path to hub script to run and its corresponding args - */ - requestBody: { - hub_script_path?: string; - channel?: string; - test_msg?: string; - }; - workspace: string; -}; - -export type RunSlackMessageTestJobResponse = ({ - job_uuid?: string; -}); - -export type RunTeamsMessageTestJobData = { - /** - * path to hub script to run and its corresponding args - */ - requestBody: { - hub_script_path?: string; - channel?: string; - test_msg?: string; - }; - workspace: string; -}; - -export type RunTeamsMessageTestJobResponse = ({ - job_uuid?: string; -}); - -export type EditDeployToData = { - requestBody: { - deploy_to?: string; - }; - workspace: string; -}; - -export type EditDeployToResponse = (string); - -export type EditAutoInviteData = { - /** - * WorkspaceInvite - */ - requestBody: { - operator?: boolean; - invite_all?: boolean; - auto_add?: boolean; - }; - workspace: string; -}; - -export type EditAutoInviteResponse = (string); - -export type EditWebhookData = { - /** - * WorkspaceWebhook - */ - requestBody: { - webhook?: string; - }; - workspace: string; -}; - -export type EditWebhookResponse = (string); - -export type EditCopilotConfigData = { - /** - * WorkspaceCopilotConfig - */ - requestBody: { - ai_resource?: AIResource; - code_completion_model?: string; - ai_models: Array<(string)>; - }; - workspace: string; -}; - -export type EditCopilotConfigResponse = (string); - -export type GetCopilotInfoData = { - workspace: string; -}; - -export type GetCopilotInfoResponse = ({ - ai_provider?: AIProvider; - exists_ai_resource: boolean; - code_completion_model?: string; - ai_models: Array<(string)>; -}); - -export type EditErrorHandlerData = { - /** - * WorkspaceErrorHandler - */ - requestBody: { - error_handler?: string; - error_handler_extra_args?: ScriptArgs; - error_handler_muted_on_cancel?: boolean; - }; - workspace: string; -}; - -export type EditErrorHandlerResponse = (string); - -export type EditLargeFileStorageConfigData = { - /** - * LargeFileStorage info - */ - requestBody: { - large_file_storage?: LargeFileStorage; - }; - workspace: string; -}; - -export type EditLargeFileStorageConfigResponse = (unknown); - -export type EditWorkspaceGitSyncConfigData = { - /** - * Workspace Git sync settings - */ - requestBody: { - git_sync_settings?: WorkspaceGitSyncSettings; - }; - workspace: string; -}; - -export type EditWorkspaceGitSyncConfigResponse = (unknown); - -export type EditWorkspaceDeployUiSettingsData = { - /** - * Workspace deploy UI settings - */ - requestBody: { - deploy_ui_settings?: WorkspaceDeployUISettings; - }; - workspace: string; -}; - -export type EditWorkspaceDeployUiSettingsResponse = (unknown); - -export type EditWorkspaceDefaultAppData = { - /** - * Workspace default app - */ - requestBody: { - default_app_path?: string; - }; - workspace: string; -}; - -export type EditWorkspaceDefaultAppResponse = (string); - -export type EditDefaultScriptsData = { - /** - * Workspace default app - */ - requestBody?: WorkspaceDefaultScripts; - workspace: string; -}; - -export type EditDefaultScriptsResponse = (string); - -export type GetDefaultScriptsData = { - workspace: string; -}; - -export type GetDefaultScriptsResponse = (WorkspaceDefaultScripts); - -export type SetEnvironmentVariableData = { - /** - * Workspace default app - */ - requestBody: { - name: string; - value?: string; - }; - workspace: string; -}; - -export type SetEnvironmentVariableResponse = (string); - -export type GetWorkspaceEncryptionKeyData = { - workspace: string; -}; - -export type GetWorkspaceEncryptionKeyResponse = ({ - key: string; -}); - -export type SetWorkspaceEncryptionKeyData = { - /** - * New encryption key - */ - requestBody: { - new_key: string; - skip_reencrypt?: boolean; - }; - workspace: string; -}; - -export type SetWorkspaceEncryptionKeyResponse = (string); - -export type GetWorkspaceDefaultAppData = { - workspace: string; -}; - -export type GetWorkspaceDefaultAppResponse = ({ - default_app_path?: string; -}); - -export type GetLargeFileStorageConfigData = { - workspace: string; -}; - -export type GetLargeFileStorageConfigResponse = (LargeFileStorage); - -export type GetWorkspaceUsageData = { - workspace: string; -}; - -export type GetWorkspaceUsageResponse = (number); - -export type GetUsedTriggersData = { - workspace: string; -}; - -export type GetUsedTriggersResponse = ({ - http_routes_used: boolean; - websocket_used: boolean; - kafka_used: boolean; - nats_used: boolean; - postgres_used: boolean; - mqtt_used: boolean; - sqs_used: boolean; -}); - -export type ListUsersData = { - workspace: string; -}; - -export type ListUsersResponse = (Array); - -export type ListUsersUsageData = { - workspace: string; -}; - -export type ListUsersUsageResponse = (Array); - -export type ListUsernamesData = { - workspace: string; -}; - -export type ListUsernamesResponse = (Array<(string)>); - -export type UsernameToEmailData = { - username: string; - workspace: string; -}; - -export type UsernameToEmailResponse = (string); - -export type CreateTokenData = { - /** - * new token - */ - requestBody: NewToken; -}; - -export type CreateTokenResponse = (string); - -export type CreateTokenImpersonateData = { - /** - * new token - */ - requestBody: NewTokenImpersonate; -}; - -export type CreateTokenImpersonateResponse = (string); - -export type DeleteTokenData = { - tokenPrefix: string; -}; - -export type DeleteTokenResponse = (string); - -export type ListTokensData = { - excludeEphemeral?: boolean; - /** - * which page to return (start at 1, default 1) - */ - page?: number; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; -}; - -export type ListTokensResponse = (Array); - -export type GetOidcTokenData = { - audience: string; - workspace: string; -}; - -export type GetOidcTokenResponse = (string); - -export type CreateVariableData = { - alreadyEncrypted?: boolean; - /** - * new variable - */ - requestBody: CreateVariable; - workspace: string; -}; - -export type CreateVariableResponse = (string); - -export type EncryptValueData = { - /** - * new variable - */ - requestBody: string; - workspace: string; -}; - -export type EncryptValueResponse = (string); - -export type DeleteVariableData = { - path: string; - workspace: string; -}; - -export type DeleteVariableResponse = (string); - -export type UpdateVariableData = { - alreadyEncrypted?: boolean; - path: string; - /** - * updated variable - */ - requestBody: EditVariable; - workspace: string; -}; - -export type UpdateVariableResponse = (string); - -export type GetVariableData = { - /** - * ask to decrypt secret if this variable is secret - * (if not secret no effect, default: true) - * - */ - decryptSecret?: boolean; - /** - * ask to include the encrypted value if secret and decrypt secret is not true (default: false) - * - */ - includeEncrypted?: boolean; - path: string; - workspace: string; -}; - -export type GetVariableResponse = (ListableVariable); - -export type GetVariableValueData = { - path: string; - workspace: string; -}; - -export type GetVariableValueResponse = (string); - -export type ExistsVariableData = { - path: string; - workspace: string; -}; - -export type ExistsVariableResponse = (boolean); - -export type ListVariableData = { - /** - * which page to return (start at 1, default 1) - */ - page?: number; - pathStart?: string; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; - workspace: string; -}; - -export type ListVariableResponse = (Array); - -export type ListContextualVariablesData = { - workspace: string; -}; - -export type ListContextualVariablesResponse = (Array); - -export type WorkspaceGetCriticalAlertsData = { - acknowledged?: (boolean) | null; - page?: number; - pageSize?: number; - workspace: string; -}; - -export type WorkspaceGetCriticalAlertsResponse = ({ - alerts?: Array; - /** - * Total number of rows matching the query. - */ - total_rows?: number; - /** - * Total number of pages based on the page size. - */ - total_pages?: number; -}); - -export type WorkspaceAcknowledgeCriticalAlertData = { - /** - * The ID of the critical alert to acknowledge - */ - id: number; - workspace: string; -}; - -export type WorkspaceAcknowledgeCriticalAlertResponse = (string); - -export type WorkspaceAcknowledgeAllCriticalAlertsData = { - workspace: string; -}; - -export type WorkspaceAcknowledgeAllCriticalAlertsResponse = (string); - -export type WorkspaceMuteCriticalAlertsUiData = { - /** - * Boolean flag to mute critical alerts. - */ - requestBody: { - /** - * Whether critical alerts should be muted. - */ - mute_critical_alerts?: boolean; - }; - workspace: string; -}; - -export type WorkspaceMuteCriticalAlertsUiResponse = (string); - -export type LoginWithOauthData = { - clientName: string; - /** - * Partially filled script - */ - requestBody: { - code?: string; - state?: string; - }; -}; - -export type LoginWithOauthResponse = (string); - -export type ConnectSlackCallbackData = { - /** - * code endpoint - */ - requestBody: { - code: string; - state: string; - }; - workspace: string; -}; - -export type ConnectSlackCallbackResponse = (string); - -export type ConnectSlackCallbackInstanceData = { - /** - * code endpoint - */ - requestBody: { - code: string; - state: string; - }; -}; - -export type ConnectSlackCallbackInstanceResponse = (string); - -export type ConnectCallbackData = { - clientName: string; - /** - * code endpoint - */ - requestBody: { - code: string; - state: string; - }; -}; - -export type ConnectCallbackResponse = (TokenResponse); - -export type CreateAccountData = { - /** - * code endpoint - */ - requestBody: { - refresh_token?: string; - expires_in: number; - client: string; - }; - workspace: string; -}; - -export type CreateAccountResponse = (string); - -export type RefreshTokenData = { - id: number; - /** - * variable path - */ - requestBody: { - path: string; - }; - workspace: string; -}; - -export type RefreshTokenResponse = (string); - -export type DisconnectAccountData = { - id: number; - workspace: string; -}; - -export type DisconnectAccountResponse = (string); - -export type DisconnectSlackData = { - workspace: string; -}; - -export type DisconnectSlackResponse = (string); - -export type DisconnectTeamsData = { - workspace: string; -}; - -export type DisconnectTeamsResponse = (string); - -export type ListOauthLoginsResponse = ({ - oauth: Array<{ - type: string; - display_name?: string; - }>; - saml?: string; -}); - -export type ListOauthConnectsResponse = (Array<(string)>); - -export type GetOauthConnectData = { - /** - * client name - */ - client: string; -}; - -export type GetOauthConnectResponse = ({ - extra_params?: { - [key: string]: unknown; - }; - scopes?: Array<(string)>; -}); - -export type SyncTeamsResponse = (Array); - -export type SendMessageToConversationData = { - requestBody: { - /** - * The ID of the Teams conversation/activity - */ - conversation_id: string; - /** - * Used for styling the card conditionally - */ - success?: boolean; - /** - * The message text to be sent in the Teams card - */ - text: string; - /** - * The card block to be sent in the Teams card - */ - card_block?: { - [key: string]: unknown; - }; - }; -}; - -export type SendMessageToConversationResponse = (unknown); - -export type CreateResourceData = { - /** - * new resource - */ - requestBody: CreateResource; - updateIfExists?: boolean; - workspace: string; -}; - -export type CreateResourceResponse = (string); - -export type DeleteResourceData = { - path: string; - workspace: string; -}; - -export type DeleteResourceResponse = (string); - -export type UpdateResourceData = { - path: string; - /** - * updated resource - */ - requestBody: EditResource; - workspace: string; -}; - -export type UpdateResourceResponse = (string); - -export type UpdateResourceValueData = { - path: string; - /** - * updated resource - */ - requestBody: { - value?: unknown; - }; - workspace: string; -}; - -export type UpdateResourceValueResponse = (string); - -export type GetResourceData = { - path: string; - workspace: string; -}; - -export type GetResourceResponse = (Resource); - -export type GetResourceValueInterpolatedData = { - /** - * job id - */ - jobId?: string; - path: string; - workspace: string; -}; - -export type GetResourceValueInterpolatedResponse = (unknown); - -export type GetResourceValueData = { - path: string; - workspace: string; -}; - -export type GetResourceValueResponse = (unknown); - -export type ExistsResourceData = { - path: string; - workspace: string; -}; - -export type ExistsResourceResponse = (boolean); - -export type ListResourceData = { - /** - * which page to return (start at 1, default 1) - */ - page?: number; - pathStart?: string; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; - /** - * resource_types to list from, separated by ',', - */ - resourceType?: string; - /** - * resource_types to not list from, separated by ',', - */ - resourceTypeExclude?: string; - workspace: string; -}; - -export type ListResourceResponse = (Array); - -export type ListSearchResourceData = { - workspace: string; -}; - -export type ListSearchResourceResponse = (Array<{ - path: string; - value: unknown; -}>); - -export type ListResourceNamesData = { - name: string; - workspace: string; -}; - -export type ListResourceNamesResponse = (Array<{ - name: string; - path: string; -}>); - -export type CreateResourceTypeData = { - /** - * new resource_type - */ - requestBody: ResourceType; - workspace: string; -}; - -export type CreateResourceTypeResponse = (string); - -export type FileResourceTypeToFileExtMapData = { - workspace: string; -}; - -export type FileResourceTypeToFileExtMapResponse = (unknown); - -export type DeleteResourceTypeData = { - path: string; - workspace: string; -}; - -export type DeleteResourceTypeResponse = (string); - -export type UpdateResourceTypeData = { - path: string; - /** - * updated resource_type - */ - requestBody: EditResourceType; - workspace: string; -}; - -export type UpdateResourceTypeResponse = (string); - -export type GetResourceTypeData = { - path: string; - workspace: string; -}; - -export type GetResourceTypeResponse = (ResourceType); - -export type ExistsResourceTypeData = { - path: string; - workspace: string; -}; - -export type ExistsResourceTypeResponse = (boolean); - -export type ListResourceTypeData = { - workspace: string; -}; - -export type ListResourceTypeResponse = (Array); - -export type ListResourceTypeNamesData = { - workspace: string; -}; - -export type ListResourceTypeNamesResponse = (Array<(string)>); - -export type QueryResourceTypesData = { - /** - * query limit - */ - limit?: number; - /** - * query text - */ - text: string; - workspace: string; -}; - -export type QueryResourceTypesResponse = (Array<{ - name: string; - score: number; - schema?: unknown; -}>); - -export type ListHubIntegrationsData = { - /** - * query integrations kind - */ - kind?: string; -}; - -export type ListHubIntegrationsResponse = (Array<{ - name: string; -}>); - -export type ListHubFlowsResponse = ({ - flows?: Array<{ - id: number; - flow_id: number; - summary: string; - apps: Array<(string)>; - approved: boolean; - votes: number; - }>; -}); - -export type GetHubFlowByIdData = { - id: number; -}; - -export type GetHubFlowByIdResponse = ({ - flow?: OpenFlow; -}); - -export type ListHubAppsResponse = ({ - apps?: Array<{ - id: number; - app_id: number; - summary: string; - apps: Array<(string)>; - approved: boolean; - votes: number; - }>; -}); - -export type GetHubAppByIdData = { - id: number; -}; - -export type GetHubAppByIdResponse = ({ - app: { - summary: string; - value: unknown; - }; -}); - -export type GetPublicAppByCustomPathData = { - customPath: string; -}; - -export type GetPublicAppByCustomPathResponse = ((AppWithLastVersion & { - workspace_id?: string; -})); - -export type GetHubScriptContentByPathData = { - path: string; -}; - -export type GetHubScriptContentByPathResponse = (string); - -export type GetHubScriptByPathData = { - path: string; -}; - -export type GetHubScriptByPathResponse = ({ - content: string; - lockfile?: string; - schema?: unknown; - language: string; - summary?: string; -}); - -export type GetTopHubScriptsData = { - /** - * query scripts app - */ - app?: string; - /** - * query scripts kind - */ - kind?: string; - /** - * query limit - */ - limit?: number; -}; - -export type GetTopHubScriptsResponse = ({ - asks?: Array<{ - id: number; - ask_id: number; - summary: string; - app: string; - version_id: number; - kind: HubScriptKind; - votes: number; - views: number; - }>; -}); - -export type QueryHubScriptsData = { - /** - * query scripts app - */ - app?: string; - /** - * query scripts kind - */ - kind?: string; - /** - * query limit - */ - limit?: number; - /** - * query text - */ - text: string; -}; - -export type QueryHubScriptsResponse = (Array<{ - ask_id: number; - id: number; - version_id: number; - summary: string; - app: string; - kind: HubScriptKind; - score: number; -}>); - -export type ListSearchScriptData = { - workspace: string; -}; - -export type ListSearchScriptResponse = (Array<{ - path: string; - content: string; -}>); - -export type ListScriptsData = { - /** - * mask to filter exact matching user creator - */ - createdBy?: string; - /** - * mask to filter scripts whom first direct parent has exact hash - */ - firstParentHash?: string; - /** - * (default false) - * include scripts that have no deployed version - * - */ - includeDraftOnly?: boolean; - /** - * (default false) - * include scripts without an exported main function - * - */ - includeWithoutMain?: boolean; - /** - * (default regardless) - * if true show only the templates - * if false show only the non templates - * if not defined, show all regardless of if the script is a template - * - */ - isTemplate?: boolean; - /** - * (default regardless) - * script kinds to filter, split by comma - * - */ - kinds?: string; - /** - * mask to filter scripts whom last parent in the chain has exact hash. - * Beware that each script stores only a limited number of parents. Hence - * the last parent hash for a script is not necessarily its top-most parent. - * To find the top-most parent you will have to jump from last to last hash - * until finding the parent - * - */ - lastParentHash?: string; - /** - * order by desc order (default true) - */ - orderDesc?: boolean; - /** - * which page to return (start at 1, default 1) - */ - page?: number; - /** - * is the hash present in the array of stored parent hashes for this script. - * The same warning applies than for last_parent_hash. A script only store a - * limited number of direct parent - * - */ - parentHash?: string; - /** - * mask to filter exact matching path - */ - pathExact?: string; - /** - * mask to filter matching starting path - */ - pathStart?: string; - /** - * number of items to return for a given page (default 30, max 100) - */ - perPage?: number; - /** - * (default false) - * show only the archived files. - * when multiple archived hash share the same path, only the ones with the latest create_at - * are - * ed. - * - */ - showArchived?: boolean; - /** - * (default false) - * show only the starred items - * - */ - starredOnly?: boolean; - /** - * (default false) - * include deployment message - * - */ - withDeploymentMsg?: boolean; - workspace: string; -}; - -export type ListScriptsResponse = (Array - + {#snippet trigger()} - - + {/snippet} + {#snippet content()}
Add a new user Email - + {#if !automateUsernameCreation} Username - + {/if} Role - - - - + + {#snippet children({ item })} + + + + {/snippet}
-
+ {/snippet}
diff --git a/frontend/src/lib/components/AllFlowLogs.svelte b/frontend/src/lib/components/AllFlowLogs.svelte index 270ba2bffa..8be5872278 100644 --- a/frontend/src/lib/components/AllFlowLogs.svelte +++ b/frontend/src/lib/components/AllFlowLogs.svelte @@ -6,7 +6,11 @@ import type { DurationStatus } from './graph' import type { Writable } from 'svelte/store' - export let states: Writable> | undefined + interface Props { + states: Writable> | undefined + } + + let { states }: Props = $props()
diff --git a/frontend/src/lib/components/ApiConnectForm.svelte b/frontend/src/lib/components/ApiConnectForm.svelte index b2fc7410e1..3d3f3cda25 100644 --- a/frontend/src/lib/components/ApiConnectForm.svelte +++ b/frontend/src/lib/components/ApiConnectForm.svelte @@ -1,7 +1,6 @@ {#if !notFound} @@ -134,7 +153,7 @@ placement: 'bottom' }} > - + {#snippet trigger()} - - + {/snippet} + {#snippet content({ close })}
@@ -172,7 +191,7 @@ {/if}
- + {/snippet} {/if} {#if resourceType == 'postgresql' && supabaseWizard} @@ -185,6 +204,17 @@
Connect Supabase
{/if} + { + args = newArgs + rawCode = JSON.stringify(args, null, 2) + rawCodeEditor?.setCode(rawCode) + }} + onDescriptionUpdate={(newDescription) => (description = newDescription)} + />
{:else}

{error}{:else}

{/if} + >{:else}
{/if}
{#await import('$lib/components/SimpleEditor.svelte')} @@ -213,7 +243,7 @@
File content ({resourceTypeInfo.format_extension})
-
+
{#await import('$lib/components/SimpleEditor.svelte')} diff --git a/frontend/src/lib/components/AppConnectDrawer.svelte b/frontend/src/lib/components/AppConnectDrawer.svelte index 4f30737736..82d739129c 100644 --- a/frontend/src/lib/components/AppConnectDrawer.svelte +++ b/frontend/src/lib/components/AppConnectDrawer.svelte @@ -61,29 +61,31 @@ on:refresh express={expressOAuthSetup} /> -
- {#if step > 1} - - {/if} - {#if isGoogleSignin} - - {:else} - - {/if} -
+ {#snippet actions()} +
+ {#if step > 1} + + {/if} + {#if isGoogleSignin} + + {:else} + + {/if} +
+ {/snippet} diff --git a/frontend/src/lib/components/AppConnectInner.svelte b/frontend/src/lib/components/AppConnectInner.svelte index 02136a91dc..5d9bbcbb29 100644 --- a/frontend/src/lib/components/AppConnectInner.svelte +++ b/frontend/src/lib/components/AppConnectInner.svelte @@ -1,4 +1,6 @@ {#if !express} a.localeCompare(b)) - .map((key) => ({ - key - })) + ? connects.map((key) => ({ + key + })) : undefined} bind:filteredItems={filteredConnects} f={(x) => x.key} /> a[0].localeCompare(b[0]))} + items={connectsManual} bind:filteredItems={filteredConnectsManual} f={(x) => x[0]} /> @@ -480,13 +504,15 @@ {/if}
-
+
{#if filteredConnectsManual} {#each filteredConnectsManual as [key, _]} {#if !nativeLanguagesCategory.includes(key)}
{:else if description == undefined || description == ''}
No description provided
{:else} -
+
{/if}
{#key resourceTypeInfo}

Scopes

{#if step > 2} - + {/if} - {/if} @@ -702,24 +833,25 @@ args={otherArgs} {helperScript} bind:value - entrypoint={format.substring('dynselect_'.length)} + entrypoint={format?.substring('dynselect_'.length) ?? ''} /> {:else if inputCat == 'resource-object' && resourceTypes == undefined} Loading resource types... - {:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format.split('-').length > 1 && resourceTypes.includes(format.substring('resource-'.length))))} + {:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format && format?.split('-').length > 1 && resourceTypes.includes(format?.substring('resource-'.length))))} + { + onClear={() => { defaultValue = null }} {showSchemaExplorer} /> - {:else if inputCat == 'resource-object' && format.split('-').length > 1 && format + {:else if inputCat == 'resource-object' && format && format.split('-').length > 1 && format .replace('resource-', '') .replace('_', '') .toLowerCase() == 's3object'} @@ -748,18 +880,6 @@ }} /> {/await} - {:else} {/if} +
{:else if inputCat == 'object' || inputCat == 'resource-object' || isListJson} {#if oneOf && oneOf.length >= 2} @@ -791,14 +923,24 @@ selected={oneOfSelected} on:selected={({ detail }) => { oneOfSelected = detail - value = { label: detail } - redraw += 1 + const prevValueKeys = Object.keys( + oneOf?.find((o) => o.title == detail)?.properties ?? {} + ) + const toKeep = {} + for (const key of prevValueKeys) { + toKeep[key] = value[key] + } + const tagKey = oneOf.find((o) => Object.keys(o.properties ?? {}).includes('kind')) + ? 'kind' + : 'label' + value = { ...toKeep, [tagKey]: detail } }} - let:item > - {#each oneOf as obj} - - {/each} + {#snippet children({ item })} + {#each oneOf as obj} + + {/each} + {/snippet} {#if oneOfSelected} {@const objIdx = oneOf.findIndex((o) => o.title === oneOfSelected)} @@ -812,25 +954,27 @@ {onlyMaskPassword} {disablePortal} {disabled} - schema={{ - properties: obj.properties, - order: obj.order, - $schema: '', - required: obj.required ?? [], - type: 'object' - }} + bind:schema={ + () => ({ + properties: obj.properties ?? {}, + order: obj.order, + $schema: '', + required: obj.required ?? [], + type: 'object' + }), + () => { + dispatch('nestedChange') + } + } bind:args={value} dndType={`nested-${title}`} - schemaSkippedValues={['label']} + hiddenArgs={['label', 'kind']} on:reorder={(e) => { if (oneOf && oneOf[objIdx]) { const keys = e.detail oneOf[objIdx].order = keys } }} - on:change={() => { - dispatch('nestedChange') - }} on:nestedChange {shouldDispatchChanges} /> @@ -840,7 +984,7 @@ {onlyMaskPassword} {disablePortal} {disabled} - schemaSkippedValues={['label']} + hiddenArgs={['label', 'kind']} schema={{ properties: obj.properties, order: obj.order, @@ -848,7 +992,12 @@ required: obj.required ?? [], type: 'object' }} - bind:args={value} + bind:args={ + () => value, + (v) => { + value = { ...v, [tagKey]: oneOfSelected } + } + } {shouldDispatchChanges} on:change={() => { dispatch('nestedChange') @@ -859,7 +1008,7 @@
{/key} {:else if disabled} - {:else} {#await import('$lib/components/JsonEditor.svelte')} @@ -881,7 +1030,7 @@ {/if} {/if} {:else if disabled} - {:else} {#await import('$lib/components/JsonEditor.svelte')} @@ -910,22 +1059,24 @@ {onlyMaskPassword} {disablePortal} {disabled} - schema={{ - properties, - $schema: '', - required: nestedRequired ?? [], - type: 'object', - order - }} + bind:schema={ + () => ({ + properties, + $schema: '', + required: nestedRequired ?? [], + type: 'object', + order + }), + (newSchema) => { + dispatch('nestedChange') + } + } bind:args={value} dndType={`nested-${title}`} on:reorder={(e) => { const keys = e.detail order = keys }} - on:change={() => { - dispatch('nestedChange') - }} diff={diffStatus && typeof diffStatus.diff === 'object' ? diffStatus.diff : {}} on:acceptChange={(e) => { dispatch('acceptChange', e.detail) @@ -968,7 +1119,7 @@ {/if}
{:else if disabled} - {:else} {#await import('$lib/components/JsonEditor.svelte')} @@ -1004,10 +1155,9 @@ {:else if inputCat == 'enum'}
+ {:else}
{#await import('$lib/components/SimpleEditor.svelte')} @@ -1058,7 +1208,7 @@ fileChanged(x, (val) => (value = val))} + onchange={(x) => fileChanged(x, (val) => (value = val))} multiple={false} /> {#if value?.length} @@ -1083,8 +1233,8 @@ {:else if inputCat == 'email'} { + onfocus={(e) => { dispatch('focus') }} - on:blur={(e) => { + onblur={(e) => { dispatch('blur') }} use:autosize - on:keydown={onKeyDown} + onkeydown={onKeyDown} {disabled} class={twMerge( 'w-full', @@ -1133,13 +1283,13 @@ )} placeholder={placeholder ?? defaultValue ?? ''} bind:value - /> + > {/key} {#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true} - + {/if} @@ -1163,7 +1313,7 @@ {/if}
{/if} - + {@render actions?.()}
{#if !compact || (error && error != '')} @@ -1175,7 +1325,7 @@ {/if}
{:else if !noMargin} -
+
{/if}
diff --git a/frontend/src/lib/components/ArrayTypeNarrowing.svelte b/frontend/src/lib/components/ArrayTypeNarrowing.svelte index 8e2fe827c3..86b4fb6c10 100644 --- a/frontend/src/lib/components/ArrayTypeNarrowing.svelte +++ b/frontend/src/lib/components/ArrayTypeNarrowing.svelte @@ -8,46 +8,55 @@ import Alert from './common/alert/Alert.svelte' import EditableSchemaDrawer from './schema/EditableSchemaDrawer.svelte' import type { SchemaProperty } from '$lib/common' + import Toggle from './Toggle.svelte' + import { tick } from 'svelte' - export let canEditResourceType: boolean = false - export let originalType: string | undefined = undefined - export let itemsType: - | { - type?: 'string' | 'number' | 'bytes' | 'object' | 'resource' - contentEncoding?: 'base64' - enum?: string[] - resourceType?: string - properties?: { [name: string]: SchemaProperty } - } - | undefined + interface Props { + canEditResourceType?: boolean + originalType?: string | undefined + itemsType: + | { + type?: 'string' | 'number' | 'bytes' | 'object' | 'resource' + contentEncoding?: 'base64' + enum?: string[] + resourceType?: string + properties?: { [name: string]: SchemaProperty } + } + | undefined + } - let selected: 'string' | 'number' | 'object' | 'bytes' | 'enum' | 'resource' | undefined = + let { + canEditResourceType = false, + originalType = undefined, + itemsType = $bindable() + }: Props = $props() + + let selected: + | 'string' + | 'number' + | 'object' + | 'bytes' + | 'enum' + | 'resource' + | 's3object' + | undefined = $state( itemsType?.type != 'string' - ? itemsType?.type + ? itemsType?.type == 'object' && itemsType?.resourceType == 's3object' + ? 's3object' + : itemsType?.type : Array.isArray(itemsType?.enum) - ? 'enum' - : 'string' - - let schema = { - properties: itemsType?.properties || {}, - order: Object.keys(itemsType?.properties || {}), - required: Object.values(itemsType?.properties || {}).map((p) => p.required) - } - - function updateItemsType() { - itemsType = { - ...itemsType, - properties: schema.properties, - type: 'object' - } - } + ? 'enum' + : itemsType?.contentEncoding == 'base64' + ? 'bytes' + : 'string' + ) {#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'}