22#
33# This workflow runs integration tests that require Databricks secrets.
44#
5- # For testing external contributions (PRs from forks):
5+ # For testing external contributions (PRs from forks) or batching multiple PRs :
66# 1. Go to Actions tab -> Integration Tests -> Run workflow
7- # 2. Enter the PR number in the 'pr_number' field
7+ # 2. Enter one PR number (e.g. "100") OR a comma-separated list (e.g. "100,200,300") in 'pr_numbers'
8+ # - For batch dispatches, matrix max-parallel caps in-run parallelism.
89# 3. Click "Run workflow"
910#
1011# This approach is secure because:
2425 - " .github/workflows/stale.yml"
2526
2627 workflow_dispatch :
27- # Manual triggering for external contributions and ad-hoc testing
28+ # Manual triggering for external contributions and ad-hoc / batch testing
2829 inputs :
29- pr_number :
30- description : " PR number to test ( for external contributions )"
30+ pr_numbers :
31+ description : " PR number(s) to test — single PR or comma-separated for batch (e.g. '100' or '100,200,300' )"
3132 required : false
3233 type : string
3334 git_ref :
34- description : " Git ref (branch/tag/commit) to test"
35+ description : " Git ref (branch/tag/commit) to test — used only when pr_numbers is empty "
3536 required : false
3637 type : string
3738
3839permissions :
3940 id-token : write
4041 contents : read
4142
43+ # Target-aware concurrency:
44+ # - Different PRs / batches don't cancel each other.
45+ # - Re-push to the same PR (or re-dispatch of the same batch) cancels the stale run.
4246concurrency :
43- group : ${{ github.workflow }}-${{ github.ref }}
47+ group : ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.pr_numbers || github.event.inputs.git_ref || github. ref }}
4448 cancel-in-progress : true
4549
4650jobs :
51+ prepare :
52+ runs-on :
53+ group : databricks-protected-runner-group
54+ labels : linux-ubuntu-latest
55+ # Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures.
56+ # Downstream jobs inherit this gate via `needs: prepare`.
57+ if : github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
58+ outputs :
59+ targets : ${{ steps.parse.outputs.targets }}
60+ steps :
61+ - name : Parse targets
62+ id : parse
63+ shell : bash
64+ env :
65+ EVENT_NAME : ${{ github.event_name }}
66+ PR_NUMBER : ${{ github.event.pull_request.number }}
67+ PR_SHA : ${{ github.event.pull_request.head.sha }}
68+ INPUT_PR_NUMBERS : ${{ github.event.inputs.pr_numbers }}
69+ INPUT_GIT_REF : ${{ github.event.inputs.git_ref }}
70+ DEFAULT_REF : ${{ github.ref }}
71+ run : |
72+ set -euo pipefail
73+ entry() { printf '{"pr":"%s","ref":"%s"}' "$1" "$2"; }
74+ targets="["
75+ if [[ "$EVENT_NAME" == "pull_request" ]]; then
76+ targets+=$(entry "$PR_NUMBER" "$PR_SHA")
77+ elif [[ -n "${INPUT_PR_NUMBERS//[[:space:]]/}" ]]; then
78+ first=1
79+ IFS=',' read -ra prs <<< "$INPUT_PR_NUMBERS"
80+ for pr in "${prs[@]}"; do
81+ pr_trimmed="${pr//[[:space:]]/}"
82+ [[ -z "$pr_trimmed" ]] && continue
83+ if [[ ! "$pr_trimmed" =~ ^[0-9]+$ ]]; then
84+ echo "::error::Invalid PR number '$pr_trimmed' in pr_numbers='$INPUT_PR_NUMBERS' — expected digits, comma-separated."
85+ exit 1
86+ fi
87+ [[ $first -eq 0 ]] && targets+=","
88+ first=0
89+ targets+=$(entry "$pr_trimmed" "refs/pull/$pr_trimmed/head")
90+ done
91+ elif [[ -n "${INPUT_GIT_REF//[[:space:]]/}" ]]; then
92+ targets+=$(entry "manual" "$INPUT_GIT_REF")
93+ else
94+ targets+=$(entry "manual" "$DEFAULT_REF")
95+ fi
96+ targets+="]"
97+ echo "targets=$targets" >> "$GITHUB_OUTPUT"
98+ echo "Parsed targets: $targets"
99+
47100 run-uc-cluster-e2e-tests :
101+ # Do not add `if: always()` / `if: !cancelled()` here or on sibling test jobs —
102+ # `needs: prepare` propagates the external-fork skip cleanly, and forcing
103+ # evaluation would make `fromJSON(needs.prepare.outputs.targets)` fail on an
104+ # empty output. Matrix shape contract: {pr, ref} — defined in the `prepare` job.
105+ needs : prepare
106+ strategy :
107+ fail-fast : false
108+ max-parallel : 2
109+ matrix :
110+ target : ${{ fromJSON(needs.prepare.outputs.targets) }}
48111 runs-on :
49112 group : databricks-protected-runner-group
50113 labels : linux-ubuntu-latest
51114 environment : azure-prod
52- # Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
53- if : github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
54115 env :
55116 DBT_DATABRICKS_HOST_NAME : ${{ secrets.DATABRICKS_HOST }}
56117 DBT_DATABRICKS_CLIENT_ID : ${{ secrets.TEST_PECO_SP_ID }}
@@ -63,17 +124,15 @@ jobs:
63124 - name : Check out repository
64125 uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
65126 with :
66- # For pull_request: checkout the PR head commit
67- # For workflow_dispatch with pr_number: checkout that PR's head
68- # For workflow_dispatch with git_ref: checkout that ref
69- # Otherwise: checkout current branch
70- ref : ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
71- # Fetch enough history for PR testing
72- fetch-depth : 0
73-
74- - name : Setup JFrog PyPI Proxy
75- uses : ./.github/actions/setup-jfrog-pypi
127+ ref : ${{ matrix.target.ref }}
76128
129+ - name : Setup Python Dependencies
130+ id : deps
131+ uses : ./.github/actions/setup-python-deps
132+
133+ - name : Setup JFrog PyPI Proxy (fallback)
134+ if : steps.deps.outputs.cache-hit != 'true'
135+ uses : ./.github/actions/setup-jfrog-pypi
77136
78137 - name : Set up python
79138 id : setup-python
87146
88147 - name : Install uv
89148 uses : astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
149+ with :
150+ cache-local-path : ~/.cache/uv
90151
91152 - name : Install Hatch
92153 id : install-dependencies
@@ -99,17 +160,21 @@ jobs:
99160 if : always()
100161 uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
101162 with :
102- name : uc-cluster-test-logs
163+ name : uc-cluster-test-logs-${{ matrix.target.pr }}
103164 path : logs/
104165 retention-days : 5
105166
106167 run-sqlwarehouse-e2e-tests :
168+ needs : prepare
169+ strategy :
170+ fail-fast : false
171+ max-parallel : 2
172+ matrix :
173+ target : ${{ fromJSON(needs.prepare.outputs.targets) }}
107174 runs-on :
108175 group : databricks-protected-runner-group
109176 labels : linux-ubuntu-latest
110177 environment : azure-prod
111- # Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
112- if : github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
113178 env :
114179 DBT_DATABRICKS_HOST_NAME : ${{ secrets.DATABRICKS_HOST }}
115180 DBT_DATABRICKS_CLIENT_ID : ${{ secrets.TEST_PECO_SP_ID }}
@@ -123,17 +188,15 @@ jobs:
123188 - name : Check out repository
124189 uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
125190 with :
126- # For pull_request: checkout the PR head commit
127- # For workflow_dispatch with pr_number: checkout that PR's head
128- # For workflow_dispatch with git_ref: checkout that ref
129- # Otherwise: checkout current branch
130- ref : ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
131- # Fetch enough history for PR testing
132- fetch-depth : 0
133-
134- - name : Setup JFrog PyPI Proxy
135- uses : ./.github/actions/setup-jfrog-pypi
191+ ref : ${{ matrix.target.ref }}
136192
193+ - name : Setup Python Dependencies
194+ id : deps
195+ uses : ./.github/actions/setup-python-deps
196+
197+ - name : Setup JFrog PyPI Proxy (fallback)
198+ if : steps.deps.outputs.cache-hit != 'true'
199+ uses : ./.github/actions/setup-jfrog-pypi
137200
138201 - name : Set up python
139202 id : setup-python
@@ -147,6 +210,8 @@ jobs:
147210
148211 - name : Install uv
149212 uses : astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
213+ with :
214+ cache-local-path : ~/.cache/uv
150215
151216 - name : Install Hatch
152217 id : install-dependencies
@@ -159,17 +224,21 @@ jobs:
159224 if : always()
160225 uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
161226 with :
162- name : sql-endpoint-test-logs
227+ name : sql-endpoint-test-logs-${{ matrix.target.pr }}
163228 path : logs/
164229 retention-days : 5
165230
166231 run-cluster-e2e-tests :
232+ needs : prepare
233+ strategy :
234+ fail-fast : false
235+ max-parallel : 2
236+ matrix :
237+ target : ${{ fromJSON(needs.prepare.outputs.targets) }}
167238 runs-on :
168239 group : databricks-protected-runner-group
169240 labels : linux-ubuntu-latest
170241 environment : azure-prod
171- # Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
172- if : github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
173242 env :
174243 DBT_DATABRICKS_HOST_NAME : ${{ secrets.DATABRICKS_HOST }}
175244 DBT_DATABRICKS_CLIENT_ID : ${{ secrets.TEST_PECO_SP_ID }}
@@ -181,17 +250,15 @@ jobs:
181250 - name : Check out repository
182251 uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
183252 with :
184- # For pull_request: checkout the PR head commit
185- # For workflow_dispatch with pr_number: checkout that PR's head
186- # For workflow_dispatch with git_ref: checkout that ref
187- # Otherwise: checkout current branch
188- ref : ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
189- # Fetch enough history for PR testing
190- fetch-depth : 0
191-
192- - name : Setup JFrog PyPI Proxy
193- uses : ./.github/actions/setup-jfrog-pypi
253+ ref : ${{ matrix.target.ref }}
194254
255+ - name : Setup Python Dependencies
256+ id : deps
257+ uses : ./.github/actions/setup-python-deps
258+
259+ - name : Setup JFrog PyPI Proxy (fallback)
260+ if : steps.deps.outputs.cache-hit != 'true'
261+ uses : ./.github/actions/setup-jfrog-pypi
195262
196263 - name : Set up python
197264 id : setup-python
@@ -205,6 +272,8 @@ jobs:
205272
206273 - name : Install uv
207274 uses : astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
275+ with :
276+ cache-local-path : ~/.cache/uv
208277
209278 - name : Install Hatch
210279 id : install-dependencies
@@ -217,6 +286,6 @@ jobs:
217286 if : always()
218287 uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
219288 with :
220- name : cluster-test-logs
289+ name : cluster-test-logs-${{ matrix.target.pr }}
221290 path : logs/
222291 retention-days : 5
0 commit comments