diff --git a/.github/workflows/trigger-integration-tests.yml b/.github/workflows/trigger-integration-tests.yml new file mode 100644 index 00000000..ac3adf0e --- /dev/null +++ b/.github/workflows/trigger-integration-tests.yml @@ -0,0 +1,363 @@ +name: Trigger Integration Tests + +# Dispatches the proxy-based Node.js integration suite in +# databricks/databricks-driver-test to run against this PR's commit. +# +# Matches the label-gated / merge-queue pattern used by the Python connector: +# normal PR events get an immediate green check, maintainers can preview with +# the `integration-test` label, and merge queue runs the real required gate. +# +# Required external setup: +# +# 1. `integration-test` label exists in this repo. +# 2. `INTEGRATION_TEST_APP_ID` / `INTEGRATION_TEST_PRIVATE_KEY` repo secrets +# are installed in this repo for the dispatcher GitHub App. +# 3. The app is installed/granted on `databricks-driver-test` so this workflow +# can send `repository_dispatch`. +# 4. The same app is installed/granted on `databricks-sql-nodejs` with +# checks:write so driver-test can report the final `Node.js Integration +# Tests` check back to this PR/merge-queue commit. +# 5. Merge queue branch protection lists `Node.js Integration Tests` as a +# required status check. + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + merge_group: + +jobs: + remove-label-on-new-commit: + if: github.event_name == 'pull_request' && github.event.action == 'synchronize' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + permissions: + pull-requests: write + issues: write + steps: + - name: Check if integration-test label exists + id: check-label + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const labels = context.payload.pull_request.labels.map((label) => label.name); + const hasLabel = labels.includes('integration-test'); + console.log(`integration-test label exists: ${hasLabel}`); + return hasLabel; + + - name: Remove integration-test label + if: steps.check-label.outputs.result == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: 'integration-test' + }); + console.log('Removed integration-test label'); + } catch (error) { + if (error.status === 404) { + console.log('Label already removed or does not exist'); + } else { + throw error; + } + } + + - name: Comment on PR about label removal + if: steps.check-label.outputs.result == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const pr = context.payload.pull_request; + const isFromFork = pr.head.repo.full_name !== pr.base.repo.full_name; + const repoType = isFromFork ? '**fork PR**' : 'PR'; + + const body = [ + 'Integration test approval reset.', + '', + `New commits were pushed to this ${repoType}. The \`integration-test\` label has been automatically removed for security.`, + '', + '**A maintainer must re-review the changes and re-add the label to trigger tests again.**', + '', + `Latest commit: ${pr.head.sha.substring(0, 7)}` + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + + skip-integration-tests-pr: + if: github.event_name == 'pull_request' && github.event.action != 'labeled' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + permissions: + checks: write + steps: + - name: Skip Node.js Integration Tests + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Node.js Integration Tests', + head_sha: context.payload.pull_request.head.sha, + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + output: { + title: 'Skipped on PR - runs in merge queue', + summary: 'Node.js integration tests are skipped on ordinary PR events and run as a required gate in the merge queue. Add the `integration-test` label to preview them on this PR.' + } + }); + + trigger-tests-pr: + if: | + github.event_name == 'pull_request' && + github.event.action == 'labeled' && + contains(github.event.pull_request.labels.*.name, 'integration-test') + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + permissions: + issues: write + pull-requests: write + checks: write + steps: + - name: Detect changed driver paths + id: changed + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100 + }); + + const names = files.map((file) => file.filename); + const sourceChanged = names.some((file) => + file.startsWith('bin/') || + file.startsWith('lib/') || + file.startsWith('spec/') || + file.startsWith('thrift/') || + file.startsWith('tests/e2e/') || + file.startsWith('tests/integration/') || + file === 'package.json' || + file === 'package-lock.json' || + file === 'tsconfig.json' || + file === 'tsconfig.build.json' + ); + const workflowChanged = names.some((file) => file.startsWith('.github/workflows/')); + const runNode = sourceChanged || workflowChanged; + + if (workflowChanged) console.log('Workflow files changed - triggering Node.js integration tests'); + if (sourceChanged) console.log('Driver source files changed - triggering Node.js integration tests'); + core.setOutput('nodejs', runNode.toString()); + + - name: Generate GitHub App Token (driver-test repo) + id: app-token + if: steps.changed.outputs.nodejs == 'true' + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} + private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} + owner: databricks + repositories: databricks-driver-test + + - name: Sanitize PR title + id: sanitize + if: steps.changed.outputs.nodejs == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + result-encoding: string + script: | + const title = context.payload.pull_request.title || ''; + return title.replace(/[\\"\n\r\t]/g, ' ').substring(0, 200); + + - name: Dispatch Node.js tests to driver-test + if: steps.changed.outputs.nodejs == 'true' + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + with: + token: ${{ steps.app-token.outputs.token }} + repository: databricks/databricks-driver-test + event-type: nodejs-pr-test + client-payload: | + { + "pr_number": "${{ github.event.pull_request.number }}", + "commit_sha": "${{ github.event.pull_request.head.sha }}", + "pr_repo": "${{ github.repository }}", + "pr_url": "${{ github.event.pull_request.html_url }}", + "pr_title": "${{ steps.sanitize.outputs.result }}", + "pr_author": "${{ github.event.pull_request.user.login }}" + } + + - name: Pass Node.js Integration Tests check (no driver changes) + if: steps.changed.outputs.nodejs != 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Node.js Integration Tests', + head_sha: context.payload.pull_request.head.sha, + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + output: { + title: 'Skipped - no driver changes', + summary: 'No Node.js driver source files changed; skipping integration tests.' + } + }); + + - name: Fail check on dispatch error + if: failure() && steps.changed.outputs.nodejs == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Node.js Integration Tests', + head_sha: context.payload.pull_request.head.sha, + status: 'completed', + conclusion: 'failure', + completed_at: new Date().toISOString(), + output: { + title: 'Failed - error dispatching tests', + summary: 'An error occurred while dispatching Node.js integration tests. Check this workflow run for details.' + } + }); + + - name: Comment on PR + if: steps.changed.outputs.nodejs == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: 'Node.js integration tests triggered. [View workflow run](https://github.com/databricks/databricks-driver-test/actions/workflows/databricks-nodejs-integration-tests.yml).' + }); + + merge-queue-nodejs: + if: github.event_name == 'merge_group' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + permissions: + contents: read + checks: write + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Check if driver files changed + id: changed + env: + BASE_SHA: ${{ github.event.merge_group.base_sha }} + HEAD_SHA: ${{ github.event.merge_group.head_sha }} + run: | + CHANGED=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + if echo "$CHANGED" | grep -qE "^(bin/|lib/|spec/|thrift/|tests/e2e/|tests/integration/|package\.json|package-lock\.json|tsconfig(\.build)?\.json|\.github/workflows/)"; then + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "Driver files changed - will dispatch Node.js integration tests" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "No driver files changed - will auto-pass" + fi + + - name: Auto-pass (no driver changes) + if: steps.changed.outputs.changed != 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Node.js Integration Tests', + head_sha: '${{ github.event.merge_group.head_sha }}', + status: 'completed', + conclusion: 'success', + completed_at: new Date().toISOString(), + output: { + title: 'Skipped - no driver changes', + summary: 'No Node.js driver source files changed.' + } + }); + + - name: Extract PR number from merge queue ref + if: steps.changed.outputs.changed == 'true' + id: extract-pr + env: + MERGE_QUEUE_REF: ${{ github.event.merge_group.head_ref }} + run: | + if [[ "$MERGE_QUEUE_REF" =~ pr-([0-9]+) ]]; then + echo "pr_number=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" + else + echo "Error: failed to extract PR number from merge group ref: '$MERGE_QUEUE_REF'" >&2 + exit 1 + fi + + - name: Generate GitHub App Token (driver-test repo) + if: steps.changed.outputs.changed == 'true' + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }} + private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }} + owner: databricks + repositories: databricks-driver-test + + - name: Dispatch Node.js tests + if: steps.changed.outputs.changed == 'true' + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + with: + token: ${{ steps.app-token.outputs.token }} + repository: databricks/databricks-driver-test + event-type: nodejs-pr-test + client-payload: | + { + "pr_number": "${{ steps.extract-pr.outputs.pr_number }}", + "commit_sha": "${{ github.event.merge_group.head_sha }}", + "pr_repo": "${{ github.repository }}", + "pr_url": "${{ github.server_url }}/${{ github.repository }}/pull/${{ steps.extract-pr.outputs.pr_number }}", + "pr_title": "Merge queue validation", + "pr_author": "merge-queue" + } + + - name: Fail check on dispatch error + if: failure() && steps.changed.outputs.changed == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'Node.js Integration Tests', + head_sha: '${{ github.event.merge_group.head_sha }}', + status: 'completed', + conclusion: 'failure', + completed_at: new Date().toISOString(), + output: { + title: 'Failed - error dispatching tests', + summary: 'An error occurred while dispatching Node.js integration tests. Check this workflow run for details.' + } + });