Skip to content

Commit b90fb0a

Browse files
authored
ci(min-deps): promote min-deps to first-class CI alongside latest-deps (#1480)
## Summary Promotes min-deps to a first-class CI citizen alongside latest-deps so lower-bound regressions surface pre-merge on every PR — including forks. Shared dep cache now covers both resolutions; min-deps unit + build run on every PR; min-deps integration runs nightly + on `/integration-test min-deps`. ## Test plan - [x] Warm-cache populates new combined-key shape and lowest-direct wheel closure. - [x] Cache restore wired correctly (branch-scope dispatch run hits cache, JFrog skipped). - [x] PR checks green on `main.yml` + `main-min-deps.yml`. - [x] `/integration-test` still routes to `integration.yml`. **Post-merge:** dispatch `warmDepsCache.yml` on `main` once to populate main-scope cache immediately. Need to check `/integration-test min-deps` routing
1 parent f558805 commit b90fb0a

5 files changed

Lines changed: 189 additions & 47 deletions

File tree

.github/actions/setup-python-deps/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ runs:
1919
~/.cache/uv
2020
~/.cache/pip
2121
~/.cache/pip-wheelhouse
22-
key: python-deps-${{ hashFiles('uv.lock', 'pyproject.toml') }}-latest
23-
restore-keys: python-deps-${{ hashFiles('uv.lock', 'pyproject.toml') }}-
22+
key: python-deps-${{ hashFiles('uv.lock', 'requirements.lowest-direct.txt', 'pyproject.toml') }}-latest
23+
restore-keys: python-deps-${{ hashFiles('uv.lock', 'requirements.lowest-direct.txt', 'pyproject.toml') }}-
2424

2525
- name: Restore pre-commit cache
2626
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4

.github/workflows/min-deps-test-slow.yml renamed to .github/workflows/integration-min-deps.yml

Lines changed: 135 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
1-
# Min-Deps Integration Tests (slow)
1+
# Integration Tests (Min-Deps) for dbt-databricks
22
#
33
# Sharded functional tests against the lowest-direct dependency resolution
44
# committed in requirements.lowest-direct.txt. Mirrors integration.yml's
55
# fan-out (skip-if-unchanged on schedule, prepare-shards via LPT historical
66
# timing, sharded execution per profile, gather-shards verification) but
77
# routes every pytest invocation through the `min-deps:` hatch env.
88
#
9-
# Triggering: schedule only.
10-
11-
name: Min-Deps Tests (slow)
9+
# Triggering:
10+
# 1. On a PR (internal OR fork): a maintainer comments `/integration-test min-deps`.
11+
# The integration-trigger workflow validates the comment author and
12+
# dispatches this workflow with the PR number. The run posts a result
13+
# comment back to the PR when the matrix completes.
14+
# 2. Manually from the Actions tab (workflow_dispatch):
15+
# - One PR (e.g. "100") OR comma-separated list ("100,200,300") in pr_numbers.
16+
# - Or a git_ref for ad-hoc testing.
17+
# 3. Nightly on `main` at 19:30 UTC (2h before integration.yml's 21:30 nightly).
18+
# The `prepare` job short-circuits if the current main SHA has already had a
19+
# successful min-deps integration run, emitting an empty targets array so
20+
# the matrix jobs skip cleanly.
21+
22+
name: Integration Tests (Min-Deps)
1223
on:
24+
workflow_dispatch:
25+
inputs:
26+
pr_numbers:
27+
description: "PR number(s) to test — single PR or comma-separated for batch (e.g. '100' or '100,200,300')"
28+
required: false
29+
type: string
30+
git_ref:
31+
description: "Git ref (branch/tag/commit) to test — used only when pr_numbers is empty"
32+
required: false
33+
type: string
34+
1335
schedule:
1436
- cron: "30 19 * * *" # 19:30 UTC, 2h before integration.yml's nightly
1537

@@ -18,7 +40,7 @@ permissions:
1840
contents: read
1941

2042
concurrency:
21-
group: ${{ github.workflow }}-${{ github.ref }}
43+
group: ${{ github.workflow }}-${{ github.event.inputs.pr_numbers || github.event.inputs.git_ref || github.ref }}
2244
cancel-in-progress: true
2345

2446
defaults:
@@ -43,23 +65,46 @@ jobs:
4365
id: parse
4466
shell: bash
4567
env:
68+
EVENT_NAME: ${{ github.event_name }}
69+
INPUT_PR_NUMBERS: ${{ github.event.inputs.pr_numbers }}
70+
INPUT_GIT_REF: ${{ github.event.inputs.git_ref }}
4671
DEFAULT_REF: ${{ github.ref }}
4772
GH_TOKEN: ${{ github.token }}
4873
run: |
4974
set -euo pipefail
5075
entry() { printf '{"pr":"%s","ref":"%s"}' "$1" "$2"; }
5176
targets="["
52-
# Nightly skip-if-unchanged: if this main SHA already has a green
53-
# min-deps-slow run, emit empty targets so the matrix jobs skip.
54-
already_tested=$(curl -sfS \
55-
-H "Authorization: Bearer $GH_TOKEN" \
56-
-H "Accept: application/vnd.github+json" \
57-
"https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/min-deps-test-slow.yml/runs?branch=main&status=success&head_sha=$GITHUB_SHA" \
58-
| jq -r '.total_count // 0')
59-
if [[ "$already_tested" -gt 0 ]]; then
60-
echo "Nightly skip: main @ $GITHUB_SHA already has $already_tested successful run(s)."
77+
if [[ "$EVENT_NAME" == "schedule" ]]; then
78+
# Nightly skip-if-unchanged: if this main SHA already has a green
79+
# min-deps integration run, emit empty targets so the matrix jobs skip.
80+
already_tested=$(curl -sfS \
81+
-H "Authorization: Bearer $GH_TOKEN" \
82+
-H "Accept: application/vnd.github+json" \
83+
"https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/integration-min-deps.yml/runs?branch=main&status=success&head_sha=$GITHUB_SHA" \
84+
| jq -r '.total_count // 0')
85+
if [[ "$already_tested" -gt 0 ]]; then
86+
echo "Nightly skip: main @ $GITHUB_SHA already has $already_tested successful run(s)."
87+
else
88+
targets+=$(entry "nightly" "$DEFAULT_REF")
89+
fi
90+
elif [[ -n "${INPUT_PR_NUMBERS//[[:space:]]/}" ]]; then
91+
first=1
92+
IFS=',' read -ra prs <<< "$INPUT_PR_NUMBERS"
93+
for pr in "${prs[@]}"; do
94+
pr_trimmed="${pr//[[:space:]]/}"
95+
[[ -z "$pr_trimmed" ]] && continue
96+
if [[ ! "$pr_trimmed" =~ ^[0-9]+$ ]]; then
97+
echo "::error::Invalid PR number '$pr_trimmed' in pr_numbers='$INPUT_PR_NUMBERS' — expected digits, comma-separated."
98+
exit 1
99+
fi
100+
[[ $first -eq 0 ]] && targets+=","
101+
first=0
102+
targets+=$(entry "$pr_trimmed" "refs/pull/$pr_trimmed/head")
103+
done
104+
elif [[ -n "${INPUT_GIT_REF//[[:space:]]/}" ]]; then
105+
targets+=$(entry "manual" "$INPUT_GIT_REF")
61106
else
62-
targets+=$(entry "nightly" "$DEFAULT_REF")
107+
targets+=$(entry "manual" "$DEFAULT_REF")
63108
fi
64109
targets+="]"
65110
echo "targets=$targets" >> "$GITHUB_OUTPUT"
@@ -97,7 +142,12 @@ jobs:
97142
with:
98143
ref: ${{ matrix.target.ref }}
99144

100-
- name: Setup JFrog PyPI Proxy
145+
- name: Setup Python Dependencies
146+
id: deps
147+
uses: ./.github/actions/setup-python-deps
148+
149+
- name: Setup JFrog PyPI Proxy (fallback)
150+
if: steps.deps.outputs.cache-hit != 'true'
101151
uses: ./.github/actions/setup-jfrog-pypi
102152

103153
- name: Set up python
@@ -193,7 +243,12 @@ jobs:
193243
with:
194244
ref: ${{ matrix.target.ref }}
195245

196-
- name: Setup JFrog PyPI Proxy
246+
- name: Setup Python Dependencies
247+
id: deps
248+
uses: ./.github/actions/setup-python-deps
249+
250+
- name: Setup JFrog PyPI Proxy (fallback)
251+
if: steps.deps.outputs.cache-hit != 'true'
197252
uses: ./.github/actions/setup-jfrog-pypi
198253

199254
- name: Set up python
@@ -287,7 +342,12 @@ jobs:
287342
with:
288343
ref: ${{ matrix.target.ref }}
289344

290-
- name: Setup JFrog PyPI Proxy
345+
- name: Setup Python Dependencies
346+
id: deps
347+
uses: ./.github/actions/setup-python-deps
348+
349+
- name: Setup JFrog PyPI Proxy (fallback)
350+
if: steps.deps.outputs.cache-hit != 'true'
291351
uses: ./.github/actions/setup-jfrog-pypi
292352

293353
- name: Set up python
@@ -379,7 +439,12 @@ jobs:
379439
with:
380440
ref: ${{ matrix.target.ref }}
381441

382-
- name: Setup JFrog PyPI Proxy
442+
- name: Setup Python Dependencies
443+
id: deps
444+
uses: ./.github/actions/setup-python-deps
445+
446+
- name: Setup JFrog PyPI Proxy (fallback)
447+
if: steps.deps.outputs.cache-hit != 'true'
383448
uses: ./.github/actions/setup-jfrog-pypi
384449

385450
- name: Set up python
@@ -511,3 +576,54 @@ jobs:
511576
echo "::error::One or more shard-verification invariants failed."
512577
exit 1
513578
fi
579+
580+
# Posts a per-job pass/fail summary comment back to the PR when dispatched
581+
# with a single PR number (the slash-command path). Skipped for batch
582+
# dispatches and for schedule / git_ref runs.
583+
report-status:
584+
needs:
585+
- run-uc-cluster-e2e-tests
586+
- run-sqlwarehouse-e2e-tests
587+
- run-cluster-e2e-tests
588+
- gather-shards
589+
if: |
590+
always() &&
591+
github.event_name == 'workflow_dispatch' &&
592+
inputs.pr_numbers != '' &&
593+
!contains(inputs.pr_numbers, ',')
594+
runs-on:
595+
group: databricks-protected-runner-group
596+
labels: linux-ubuntu-latest
597+
permissions:
598+
pull-requests: write
599+
steps:
600+
- name: Post result comment
601+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
602+
env:
603+
PR_NUMBER: ${{ inputs.pr_numbers }}
604+
UC_RESULT: ${{ needs.run-uc-cluster-e2e-tests.result }}
605+
SQLW_RESULT: ${{ needs.run-sqlwarehouse-e2e-tests.result }}
606+
CLUSTER_RESULT: ${{ needs.run-cluster-e2e-tests.result }}
607+
GATHER_RESULT: ${{ needs.gather-shards.result }}
608+
with:
609+
script: |
610+
const ICONS = { success: ':white_check_mark:', skipped: ':fast_forward:' };
611+
const results = {
612+
'UC cluster': process.env.UC_RESULT,
613+
'SQL warehouse': process.env.SQLW_RESULT,
614+
'All-purpose cluster': process.env.CLUSTER_RESULT,
615+
'Shard coverage': process.env.GATHER_RESULT,
616+
};
617+
const line = Object.entries(results)
618+
.map(([name, r]) => `${name} ${ICONS[r] || ':x:'} ${r}`)
619+
.join(' · ');
620+
const runUrl =
621+
`https://github.com/${context.repo.owner}/${context.repo.repo}` +
622+
`/actions/runs/${context.runId}`;
623+
const prNumber = process.env.PR_NUMBER.trim();
624+
await github.rest.issues.createComment({
625+
owner: context.repo.owner,
626+
repo: context.repo.repo,
627+
issue_number: parseInt(prNumber, 10),
628+
body: `Min-deps integration results for PR #${prNumber} — ${line}\n\n[Run details](${runUrl}).`,
629+
});

.github/workflows/integration-trigger.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Integration Test Trigger
22
#
3-
# Listens for `/integration-test` PR comments and dispatches the integration
4-
# test workflow against the PR head. Works uniformly for internal and forked
5-
# PRs because the dispatch runs in the main repo context (which has access to
6-
# the Databricks secrets).
3+
# Listens for `/integration-test` PR comments and dispatches the appropriate
4+
# integration test workflow against the PR head. Works uniformly for internal
5+
# and forked PRs because the dispatch runs in the main repo context (which
6+
# has access to the Databricks secrets).
7+
#
8+
# Routing on the first whitespace-separated arg after `/integration-test`:
9+
# /integration-test → integration.yml (latest deps)
10+
# /integration-test min-deps → integration-min-deps.yml (lowest-direct lock)
11+
# /integration-test <anything else> → integration.yml (trailing text
12+
# is free-form context, e.g. retry notes)
713
#
814
# Authorization: only users whose author_association is OWNER, MEMBER, or
915
# COLLABORATOR can trigger a run. Anyone else gets a reply comment explaining
@@ -22,8 +28,7 @@ permissions:
2228
jobs:
2329
dispatch:
2430
# Exact command match, or the command followed by whitespace (so maintainers
25-
# can add context like `/integration-test please retry the sqlw flake`) —
26-
# never matches `/integration-test-foo`.
31+
# can add context or modifiers) — never matches `/integration-test-foo`.
2732
if: |
2833
github.event.issue.pull_request &&
2934
(github.event.comment.body == '/integration-test' || startsWith(github.event.comment.body, '/integration-test ')) &&
@@ -47,22 +52,26 @@ jobs:
4752
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
4853
with:
4954
script: |
55+
const rest = context.payload.comment.body.slice('/integration-test'.length).trim();
56+
const isMinDeps = rest === 'min-deps' || rest.startsWith('min-deps ');
57+
const workflowId = isMinDeps ? 'integration-min-deps.yml' : 'integration.yml';
58+
const label = isMinDeps ? 'Min-deps integration tests' : 'Integration tests';
5059
const prNumber = String(context.payload.issue.number);
5160
await github.rest.actions.createWorkflowDispatch({
5261
owner: context.repo.owner,
5362
repo: context.repo.repo,
54-
workflow_id: 'integration.yml',
63+
workflow_id: workflowId,
5564
ref: 'main',
5665
inputs: { pr_numbers: prNumber },
5766
});
5867
const actionsUrl =
5968
`https://github.com/${context.repo.owner}/${context.repo.repo}` +
60-
`/actions/workflows/integration.yml`;
69+
`/actions/workflows/${workflowId}`;
6170
await github.rest.issues.createComment({
6271
owner: context.repo.owner,
6372
repo: context.repo.repo,
6473
issue_number: context.payload.issue.number,
65-
body: `Integration tests dispatched for PR #${prNumber} by @${context.payload.comment.user.login}. ` +
74+
body: `${label} dispatched for PR #${prNumber} by @${context.payload.comment.user.login}. ` +
6675
`Track progress in the [Actions tab](${actionsUrl}).`,
6776
});
6877
Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
# Min-deps variant of main.yml's Tests and Code Checks workflow. Runs
22
# unit tests + a build+verify+smoke-parse job against the committed
3-
# lowest-direct lock. Fires on every merge to main / *.latest (always)
4-
# and on internal PRs (early signal); fork PRs are skipped at the job
5-
# level because they cannot mint the JFrog OIDC token the protected
6-
# runner needs.
7-
#
8-
# Sharded functional coverage lives in the companion min-deps-test-slow.yml
9-
# on a nightly schedule.
3+
# lowest-direct lock. Fires on every push to main / *.latest and on
4+
# every PR (including forks) — dependencies are served from the
5+
# pre-populated cache (see warmDepsCache.yml) when available.
106

117
name: Tests and Code Checks (Min-Deps)
128

@@ -15,26 +11,35 @@ on:
1511
branches:
1612
- "main"
1713
- "*.latest"
14+
- "releases/*"
15+
paths-ignore:
16+
- "**.MD"
17+
- "**.md"
1818
pull_request:
19+
paths-ignore:
20+
- "**.MD"
21+
- "**.md"
1922
workflow_dispatch:
2023

2124
permissions:
2225
id-token: write
2326
contents: read
2427

28+
concurrency:
29+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.ref || github.sha }}
30+
cancel-in-progress: true
31+
2532
defaults:
2633
run:
2734
shell: bash
2835

2936
jobs:
3037
unit:
3138
name: unit test / python 3.10
32-
# Fork PRs cannot mint the JFrog OIDC token; skip them on pull_request.
33-
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
3439
runs-on:
3540
group: databricks-protected-runner-group
3641
labels: linux-ubuntu-latest
37-
timeout-minutes: 25
42+
timeout-minutes: 15
3843

3944
env:
4045
UV_FROZEN: "1"
@@ -43,7 +48,12 @@ jobs:
4348
- name: Check out the repository
4449
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
4550

46-
- name: Setup JFrog PyPI Proxy
51+
- name: Setup Python Dependencies
52+
id: deps
53+
uses: ./.github/actions/setup-python-deps
54+
55+
- name: Setup JFrog PyPI Proxy (fallback)
56+
if: steps.deps.outputs.cache-hit != 'true'
4757
uses: ./.github/actions/setup-jfrog-pypi
4858

4959
- name: Set up Python
@@ -64,12 +74,9 @@ jobs:
6474

6575
build:
6676
name: Build and Verify Packages
67-
# Fork PRs cannot mint the JFrog OIDC token; skip them on pull_request.
68-
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
6977
runs-on:
7078
group: databricks-protected-runner-group
7179
labels: linux-ubuntu-latest
72-
timeout-minutes: 15
7380

7481
env:
7582
UV_FROZEN: "1"
@@ -78,7 +85,12 @@ jobs:
7885
- name: Check out the repository
7986
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
8087

81-
- name: Setup JFrog PyPI Proxy
88+
- name: Setup Python Dependencies
89+
id: deps
90+
uses: ./.github/actions/setup-python-deps
91+
92+
- name: Setup JFrog PyPI Proxy (fallback)
93+
if: steps.deps.outputs.cache-hit != 'true'
8294
uses: ./.github/actions/setup-jfrog-pypi
8395

8496
- name: Set up Python

0 commit comments

Comments
 (0)