ci: add label-gated proxy-test dispatch to databricks-driver-test #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Trigger Integration Tests | |
| # Dispatches the proxy-based integration test suite in | |
| # databricks/databricks-driver-test to run against this PR's commit. | |
| # | |
| # Mirrors the canonical pattern in adbc-drivers/databricks. The model: | |
| # | |
| # - On a normal PR event (open / push / reopen / non-IT label) we | |
| # post a `success` Python Proxy Tests check immediately so the | |
| # required check doesn't block the PR. The real tests are gated | |
| # in the merge queue. | |
| # - When a maintainer adds the `integration-test` label we dispatch | |
| # the suite as a preview — useful for catching regressions before | |
| # merge queue time. | |
| # - Pushing new commits to the PR auto-removes the label so a | |
| # subsequent labelled run requires a fresh maintainer review. | |
| # - On the `merge_group` event the suite runs as the real required | |
| # gate. Only PRs whose tests dispatch (or auto-pass when no driver | |
| # files changed) can proceed to `main`. | |
| # | |
| # Required external setup (outside this workflow): | |
| # | |
| # 1. `integration-test` label exists in this repo (one-off; created | |
| # separately). | |
| # 2. `INTEGRATION_TEST_APP_ID` / `INTEGRATION_TEST_PRIVATE_KEY` repo | |
| # secrets installed for the dispatcher GitHub App (write access | |
| # to databricks/databricks-driver-test). | |
| # 3. Merge queue enabled on `main` branch protection AND | |
| # `Python Proxy Tests` listed as a required status check. Without | |
| # this the merge-queue job is dead code and ITs run only on | |
| # explicit label. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled] | |
| merge_group: # Trigger when added to merge queue | |
| jobs: | |
| # ============================================================================= | |
| # Security: Auto-remove label when new commits are pushed | |
| # ============================================================================= | |
| 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(l => l.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: body | |
| }); | |
| # ============================================================================= | |
| # For PRs: Always pass the Python Proxy Tests check on non-label events. | |
| # The real run happens in the merge queue (or via explicit label preview). | |
| # Without this, a required `Python Proxy Tests` check would block every | |
| # PR that doesn't bother labelling. | |
| # ============================================================================= | |
| 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 Python Proxy 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: 'Python Proxy 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: 'Python Proxy Tests are skipped on PRs and run as a required gate in the merge queue. Add the `integration-test` label to preview them on this PR.' | |
| } | |
| }); | |
| # ============================================================================= | |
| # For PRs: Dispatch real tests when integration-test label is added. | |
| # Only dispatches when driver source files changed; otherwise posts | |
| # an auto-pass check so the gate isn't artificially red. | |
| # ============================================================================= | |
| 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(f => f.filename); | |
| // Driver source + the workflow itself + pyproject.toml | |
| // (dep bumps can break the integration suite). Anything | |
| // under tests/unit/ doesn't need IT, but tests/e2e/ does. | |
| const sourceChanged = names.some(f => | |
| f.startsWith('src/') || | |
| f.startsWith('tests/e2e/') || | |
| f === 'pyproject.toml' || | |
| f === 'poetry.lock' | |
| ); | |
| const workflowChanged = names.some(f => | |
| f.startsWith('.github/workflows/') | |
| ); | |
| const runPython = sourceChanged || workflowChanged; | |
| if (workflowChanged) console.log('Workflow files changed — triggering ITs'); | |
| if (sourceChanged) console.log('Driver source files changed — triggering ITs'); | |
| core.setOutput('python', runPython.toString()); | |
| - name: Generate GitHub App Token (internal repo) | |
| 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: Generate GitHub App Token (public repo) | |
| id: public-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-sql-python | |
| - name: Sanitize PR title | |
| id: sanitize | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| result-encoding: string | |
| script: | | |
| // Remove characters that could break the dispatch JSON or | |
| // enable injection of extra fields via a crafted title. | |
| const title = context.payload.pull_request.title || ''; | |
| return title.replace(/[\\"\n\r\t]/g, ' ').substring(0, 200); | |
| - name: Dispatch Python tests to internal repo | |
| if: steps.changed.outputs.python == 'true' | |
| uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: databricks/databricks-driver-test | |
| event-type: python-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 Python Proxy Tests check (no driver changes) | |
| if: steps.changed.outputs.python != 'true' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| github-token: ${{ steps.public-token.outputs.token }} | |
| script: | | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Python Proxy 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 Python driver source files changed; skipping integration tests.' | |
| } | |
| }); | |
| - name: Fail check on dispatch error | |
| if: failure() && steps.changed.outputs.python == 'true' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| github-token: ${{ steps.public-token.outputs.token }} | |
| script: | | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Python Proxy 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 Python integration tests. Check the workflow run logs.' | |
| } | |
| }); | |
| - name: Comment on PR | |
| if: steps.changed.outputs.python == '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: 'Integration tests triggered. [View workflow run](https://github.com/databricks/databricks-driver-test/actions/workflows/python-proxy-tests.yml).' | |
| }); | |
| # ============================================================================= | |
| # For Merge Queue: Real gate. Dispatch tests when driver files changed; | |
| # otherwise auto-pass so the queue isn't blocked. | |
| # ============================================================================= | |
| merge-queue-python: | |
| 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 "^(src/|tests/e2e/|pyproject\.toml|poetry\.lock|\.github/workflows/)"; then | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| echo "Driver files changed — will dispatch tests" | |
| else | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "No driver files changed — will auto-pass" | |
| fi | |
| - name: Generate GitHub App Token (public repo) | |
| id: public-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-sql-python | |
| - name: Auto-pass (no driver changes) | |
| if: steps.changed.outputs.changed != 'true' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| github-token: ${{ steps.public-token.outputs.token }} | |
| script: | | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Python Proxy 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 Python 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: | | |
| # GitHub names the queue branch as | |
| # `gh-readonly-queue/<base>/pr-<N>-<sha>` — extract N so the | |
| # dispatched payload links back to the originating PR. | |
| 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 (internal 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 Python 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: python-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: ${{ steps.public-token.outputs.token }} | |
| script: | | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Python Proxy 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 Python integration tests. Check the workflow run logs.' | |
| } | |
| }); |