Skip to content

Commit 3a9476a

Browse files
committed
Merge main: reconcile with #1407's batched matrix foundation
Main's integration.yml was substantially rewritten by #1407 to introduce a `prepare` job that emits a targets JSON array for a matrix strategy across the three integration jobs. Reconciling the slash-command + nightly work on top: - Drop the separate `check-nightly-needed` gate; fold the SHA-skip into `prepare` itself (emits targets=[] to skip via an empty matrix). Uses the GitHub API via curl+jq to check for prior successful runs on the current main SHA. - Drop the `pull_request` trigger + the fork-repo `if:` on `prepare` (dead after removing pull_request). Prepare now runs unconditionally. - Keep the existing `prepare` batch/git_ref/default branches; just prepend a `schedule` branch at the top. - Rename `pr_number` → `pr_numbers` in the dispatcher (integration-trigger.yml) so the slash command targets the new input. - `report-status` now guards on `!contains(inputs.pr_numbers, ',')` — batch dispatches don't get auto-comments (matrix jobs produce aggregate results, so per-PR reporting would need a larger rework). - Retain main's note against `!cancelled()` on matrix jobs — the skip-via- empty-targets approach avoids that trap entirely.
2 parents 3464994 + 59c69f6 commit 3a9476a

2 files changed

Lines changed: 134 additions & 90 deletions

File tree

.github/workflows/integration-trigger.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
repo: context.repo.repo,
5454
workflow_id: 'integration.yml',
5555
ref: 'main',
56-
inputs: { pr_number: prNumber },
56+
inputs: { pr_numbers: prNumber },
5757
});
5858
const actionsUrl =
5959
`https://github.com/${context.repo.owner}/${context.repo.repo}` +

.github/workflows/integration.yml

Lines changed: 133 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
# The integration-trigger workflow validates the comment author and
1010
# dispatches this workflow with the PR number. The run posts a result
1111
# comment back to the PR when the matrix completes.
12-
# 2. Manually from the Actions tab (workflow_dispatch) with pr_number or
13-
# git_ref, for ad-hoc testing.
14-
# 3. Nightly on `main` at 03:00 IST (21:30 UTC). The schedule is skipped if
15-
# the current main SHA has already had a successful integration run.
12+
# 2. Manually from the Actions tab (workflow_dispatch):
13+
# - One PR (e.g. "100") OR comma-separated list ("100,200,300") in pr_numbers.
14+
# - Or a git_ref for ad-hoc testing.
15+
# 3. Nightly on `main` at 03:00 IST (21:30 UTC). The `prepare` job short-circuits
16+
# if the current main SHA has already had a successful integration run,
17+
# emitting an empty targets array so the matrix jobs skip cleanly.
1618
#
1719
# Security: PR-triggered runs are gated on maintainer comment authorization;
1820
# fork-PR code runs in the main repo context (access to secrets) only because
@@ -21,12 +23,12 @@ name: Integration Tests
2123
on:
2224
workflow_dispatch:
2325
inputs:
24-
pr_number:
25-
description: "PR number to test (for external contributions and ad-hoc runs)"
26+
pr_numbers:
27+
description: "PR number(s) to test — single PR or comma-separated for batch (e.g. '100' or '100,200,300')"
2628
required: false
2729
type: string
2830
git_ref:
29-
description: "Git ref (branch/tag/commit) to test"
31+
description: "Git ref (branch/tag/commit) to test — used only when pr_numbers is empty"
3032
required: false
3133
type: string
3234

@@ -37,49 +39,82 @@ permissions:
3739
id-token: write
3840
contents: read
3941

42+
# Target-aware concurrency:
43+
# - Different PRs / batches don't cancel each other.
44+
# - Re-dispatch of the same PR / batch cancels the stale run.
45+
# - Schedule runs share the main-ref group.
4046
concurrency:
41-
# Each PR and each ad-hoc git_ref gets its own concurrency group so parallel
42-
# dispatches don't cancel each other. Schedule runs share the main group.
43-
group: ${{ github.workflow }}-${{ inputs.pr_number || inputs.git_ref || github.ref }}
47+
group: ${{ github.workflow }}-${{ github.event.inputs.pr_numbers || github.event.inputs.git_ref || github.ref }}
4448
cancel-in-progress: true
4549

4650
jobs:
47-
# Schedule-only gate: skip the nightly run if the current main SHA has
48-
# already had a successful integration run. Does not run for
49-
# workflow_dispatch, so PR-triggered dispatches skip the ~30s runner spin.
50-
check-nightly-needed:
51-
if: github.event_name == 'schedule'
51+
prepare:
5252
runs-on:
5353
group: databricks-protected-runner-group
5454
labels: linux-ubuntu-latest
5555
outputs:
56-
should_run: ${{ steps.decide.outputs.should_run }}
56+
targets: ${{ steps.parse.outputs.targets }}
5757
steps:
58-
- name: Decide whether to run
59-
id: decide
60-
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
61-
with:
62-
script: |
63-
const runs = await github.rest.actions.listWorkflowRuns({
64-
owner: context.repo.owner,
65-
repo: context.repo.repo,
66-
workflow_id: 'integration.yml',
67-
branch: 'main',
68-
status: 'success',
69-
head_sha: context.sha,
70-
});
71-
core.info(`main SHA=${context.sha}, prior successful runs=${runs.data.total_count}`);
72-
core.setOutput('should_run', runs.data.total_count > 0 ? 'false' : 'true');
58+
- name: Parse targets
59+
id: parse
60+
shell: bash
61+
env:
62+
EVENT_NAME: ${{ github.event_name }}
63+
INPUT_PR_NUMBERS: ${{ github.event.inputs.pr_numbers }}
64+
INPUT_GIT_REF: ${{ github.event.inputs.git_ref }}
65+
DEFAULT_REF: ${{ github.ref }}
66+
GH_TOKEN: ${{ github.token }}
67+
run: |
68+
set -euo pipefail
69+
entry() { printf '{"pr":"%s","ref":"%s"}' "$1" "$2"; }
70+
targets="["
71+
if [[ "$EVENT_NAME" == "schedule" ]]; then
72+
# Nightly skip-if-unchanged: if this main SHA already has a green
73+
# integration run, emit empty targets so the matrix jobs skip.
74+
already_tested=$(curl -sfS \
75+
-H "Authorization: Bearer $GH_TOKEN" \
76+
-H "Accept: application/vnd.github+json" \
77+
"https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/integration.yml/runs?branch=main&status=success&head_sha=$GITHUB_SHA" \
78+
| jq -r '.total_count // 0')
79+
if [[ "$already_tested" -gt 0 ]]; then
80+
echo "Nightly skip: main @ $GITHUB_SHA already has $already_tested successful run(s)."
81+
else
82+
targets+=$(entry "nightly" "$DEFAULT_REF")
83+
fi
84+
elif [[ -n "${INPUT_PR_NUMBERS//[[:space:]]/}" ]]; then
85+
first=1
86+
IFS=',' read -ra prs <<< "$INPUT_PR_NUMBERS"
87+
for pr in "${prs[@]}"; do
88+
pr_trimmed="${pr//[[:space:]]/}"
89+
[[ -z "$pr_trimmed" ]] && continue
90+
if [[ ! "$pr_trimmed" =~ ^[0-9]+$ ]]; then
91+
echo "::error::Invalid PR number '$pr_trimmed' in pr_numbers='$INPUT_PR_NUMBERS' — expected digits, comma-separated."
92+
exit 1
93+
fi
94+
[[ $first -eq 0 ]] && targets+=","
95+
first=0
96+
targets+=$(entry "$pr_trimmed" "refs/pull/$pr_trimmed/head")
97+
done
98+
elif [[ -n "${INPUT_GIT_REF//[[:space:]]/}" ]]; then
99+
targets+=$(entry "manual" "$INPUT_GIT_REF")
100+
else
101+
targets+=$(entry "manual" "$DEFAULT_REF")
102+
fi
103+
targets+="]"
104+
echo "targets=$targets" >> "$GITHUB_OUTPUT"
105+
echo "Parsed targets: $targets"
73106
74107
run-uc-cluster-e2e-tests:
75-
needs: check-nightly-needed
76-
# On workflow_dispatch the gate is skipped (schedule-only), so use !cancelled()
77-
# to let the job run; on schedule it runs iff the gate says should_run.
78-
if: |
79-
!cancelled() && (
80-
github.event_name != 'schedule' ||
81-
needs.check-nightly-needed.outputs.should_run == 'true'
82-
)
108+
# Do not add `if: always()` / `if: !cancelled()` here or on sibling test jobs —
109+
# `needs: prepare` propagates the external-fork skip cleanly, and forcing
110+
# evaluation would make `fromJSON(needs.prepare.outputs.targets)` fail on an
111+
# empty output. Matrix shape contract: {pr, ref} — defined in the `prepare` job.
112+
needs: prepare
113+
strategy:
114+
fail-fast: false
115+
max-parallel: 2
116+
matrix:
117+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
83118
runs-on:
84119
group: databricks-protected-runner-group
85120
labels: linux-ubuntu-latest
@@ -96,16 +131,15 @@ jobs:
96131
- name: Check out repository
97132
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
98133
with:
99-
# For workflow_dispatch with pr_number: checkout that PR's head (internal or fork)
100-
# For workflow_dispatch with git_ref: checkout that ref
101-
# For schedule: falls through to github.ref (refs/heads/main)
102-
ref: ${{ (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
103-
# Fetch enough history for PR testing
104-
fetch-depth: 0
105-
106-
- name: Setup JFrog PyPI Proxy
107-
uses: ./.github/actions/setup-jfrog-pypi
134+
ref: ${{ matrix.target.ref }}
108135

136+
- name: Setup Python Dependencies
137+
id: deps
138+
uses: ./.github/actions/setup-python-deps
139+
140+
- name: Setup JFrog PyPI Proxy (fallback)
141+
if: steps.deps.outputs.cache-hit != 'true'
142+
uses: ./.github/actions/setup-jfrog-pypi
109143

110144
- name: Set up python
111145
id: setup-python
@@ -119,6 +153,8 @@ jobs:
119153

120154
- name: Install uv
121155
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
156+
with:
157+
cache-local-path: ~/.cache/uv
122158

123159
- name: Install Hatch
124160
id: install-dependencies
@@ -131,19 +167,17 @@ jobs:
131167
if: always()
132168
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
133169
with:
134-
name: uc-cluster-test-logs
170+
name: uc-cluster-test-logs-${{ matrix.target.pr }}
135171
path: logs/
136172
retention-days: 5
137173

138174
run-sqlwarehouse-e2e-tests:
139-
needs: check-nightly-needed
140-
# On workflow_dispatch the gate is skipped (schedule-only), so use !cancelled()
141-
# to let the job run; on schedule it runs iff the gate says should_run.
142-
if: |
143-
!cancelled() && (
144-
github.event_name != 'schedule' ||
145-
needs.check-nightly-needed.outputs.should_run == 'true'
146-
)
175+
needs: prepare
176+
strategy:
177+
fail-fast: false
178+
max-parallel: 2
179+
matrix:
180+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
147181
runs-on:
148182
group: databricks-protected-runner-group
149183
labels: linux-ubuntu-latest
@@ -161,16 +195,15 @@ jobs:
161195
- name: Check out repository
162196
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
163197
with:
164-
# For workflow_dispatch with pr_number: checkout that PR's head (internal or fork)
165-
# For workflow_dispatch with git_ref: checkout that ref
166-
# For schedule: falls through to github.ref (refs/heads/main)
167-
ref: ${{ (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
168-
# Fetch enough history for PR testing
169-
fetch-depth: 0
170-
171-
- name: Setup JFrog PyPI Proxy
172-
uses: ./.github/actions/setup-jfrog-pypi
198+
ref: ${{ matrix.target.ref }}
199+
200+
- name: Setup Python Dependencies
201+
id: deps
202+
uses: ./.github/actions/setup-python-deps
173203

204+
- name: Setup JFrog PyPI Proxy (fallback)
205+
if: steps.deps.outputs.cache-hit != 'true'
206+
uses: ./.github/actions/setup-jfrog-pypi
174207

175208
- name: Set up python
176209
id: setup-python
@@ -184,6 +217,8 @@ jobs:
184217

185218
- name: Install uv
186219
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
220+
with:
221+
cache-local-path: ~/.cache/uv
187222

188223
- name: Install Hatch
189224
id: install-dependencies
@@ -196,19 +231,17 @@ jobs:
196231
if: always()
197232
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
198233
with:
199-
name: sql-endpoint-test-logs
234+
name: sql-endpoint-test-logs-${{ matrix.target.pr }}
200235
path: logs/
201236
retention-days: 5
202237

203238
run-cluster-e2e-tests:
204-
needs: check-nightly-needed
205-
# On workflow_dispatch the gate is skipped (schedule-only), so use !cancelled()
206-
# to let the job run; on schedule it runs iff the gate says should_run.
207-
if: |
208-
!cancelled() && (
209-
github.event_name != 'schedule' ||
210-
needs.check-nightly-needed.outputs.should_run == 'true'
211-
)
239+
needs: prepare
240+
strategy:
241+
fail-fast: false
242+
max-parallel: 2
243+
matrix:
244+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
212245
runs-on:
213246
group: databricks-protected-runner-group
214247
labels: linux-ubuntu-latest
@@ -224,16 +257,15 @@ jobs:
224257
- name: Check out repository
225258
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
226259
with:
227-
# For workflow_dispatch with pr_number: checkout that PR's head (internal or fork)
228-
# For workflow_dispatch with git_ref: checkout that ref
229-
# For schedule: falls through to github.ref (refs/heads/main)
230-
ref: ${{ (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
231-
# Fetch enough history for PR testing
232-
fetch-depth: 0
233-
234-
- name: Setup JFrog PyPI Proxy
235-
uses: ./.github/actions/setup-jfrog-pypi
260+
ref: ${{ matrix.target.ref }}
261+
262+
- name: Setup Python Dependencies
263+
id: deps
264+
uses: ./.github/actions/setup-python-deps
236265

266+
- name: Setup JFrog PyPI Proxy (fallback)
267+
if: steps.deps.outputs.cache-hit != 'true'
268+
uses: ./.github/actions/setup-jfrog-pypi
237269

238270
- name: Set up python
239271
id: setup-python
@@ -247,6 +279,8 @@ jobs:
247279

248280
- name: Install uv
249281
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
282+
with:
283+
cache-local-path: ~/.cache/uv
250284

251285
- name: Install Hatch
252286
id: install-dependencies
@@ -259,16 +293,25 @@ jobs:
259293
if: always()
260294
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
261295
with:
262-
name: cluster-test-logs
296+
name: cluster-test-logs-${{ matrix.target.pr }}
263297
path: logs/
264298
retention-days: 5
265299

300+
# Posts a per-job pass/fail summary comment back to the PR when dispatched
301+
# with a single PR number (the slash-command path). Skipped for batch
302+
# dispatches (pr_numbers contains a comma) and for schedule / git_ref runs.
303+
# Matrix jobs' result fields are aggregated across cells, which is why this
304+
# only runs for single-PR dispatches.
266305
report-status:
267306
needs:
268307
- run-uc-cluster-e2e-tests
269308
- run-sqlwarehouse-e2e-tests
270309
- run-cluster-e2e-tests
271-
if: always() && github.event_name == 'workflow_dispatch' && inputs.pr_number != ''
310+
if: |
311+
always() &&
312+
github.event_name == 'workflow_dispatch' &&
313+
inputs.pr_numbers != '' &&
314+
!contains(inputs.pr_numbers, ',')
272315
runs-on:
273316
group: databricks-protected-runner-group
274317
labels: linux-ubuntu-latest
@@ -278,7 +321,7 @@ jobs:
278321
- name: Post result comment
279322
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
280323
env:
281-
PR_NUMBER: ${{ inputs.pr_number }}
324+
PR_NUMBER: ${{ inputs.pr_numbers }}
282325
UC_RESULT: ${{ needs.run-uc-cluster-e2e-tests.result }}
283326
SQLW_RESULT: ${{ needs.run-sqlwarehouse-e2e-tests.result }}
284327
CLUSTER_RESULT: ${{ needs.run-cluster-e2e-tests.result }}
@@ -296,9 +339,10 @@ jobs:
296339
const runUrl =
297340
`https://github.com/${context.repo.owner}/${context.repo.repo}` +
298341
`/actions/runs/${context.runId}`;
342+
const prNumber = process.env.PR_NUMBER.trim();
299343
await github.rest.issues.createComment({
300344
owner: context.repo.owner,
301345
repo: context.repo.repo,
302-
issue_number: parseInt(process.env.PR_NUMBER, 10),
303-
body: `Integration results for PR #${process.env.PR_NUMBER} — ${line}\n\n[Run details](${runUrl}).`,
346+
issue_number: parseInt(prNumber, 10),
347+
body: `Integration results for PR #${prNumber} — ${line}\n\n[Run details](${runUrl}).`,
304348
});

0 commit comments

Comments
 (0)