Skip to content

Commit 24f694f

Browse files
committed
ci(e2e): gate non-GPU E2E behind copy-pr-bot
Move `Branch E2E Checks` to the same `pull-request/<N>` + `get-pr-info` pattern used by GPU tests so self-hosted runners only execute code that has been explicitly mirrored by copy-pr-bot (automatically for trusted PRs, via `/ok to test <SHA>` for untrusted ones). A `test:e2e` label on the PR is still required. Also: - Extract the shared gate logic into `.github/actions/pr-gate` so `branch-e2e.yml` and `test-gpu.yml` no longer duplicate the SHA/label check. The required label is passed as an input. - Scope permissions per-job in both workflows. Workflow-level defaults to `permissions: {}`; builds grant `packages: write`, E2E gets `packages: read`, `pr_metadata` gets `contents: read` + `pull-requests: read`. - Document the new flow (label + re-run) in CONTRIBUTING.md. Release integration is unchanged: `release-tag.yml` and `release-dev.yml` continue to call `e2e-test.yml` directly on trusted refs (main / tags). Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
1 parent 78b685e commit 24f694f

4 files changed

Lines changed: 134 additions & 47 deletions

File tree

.github/actions/pr-gate/action.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: PR Gate
2+
description: >
3+
Resolve PR metadata for a `pull-request/<N>` push from copy-pr-bot and decide
4+
whether the workflow should run. Sets `should-run=true` only when the pushed
5+
SHA still matches the PR head SHA and the PR carries the required label. For
6+
non-`push` events (e.g. `workflow_dispatch`), always sets `should-run=true`.
7+
8+
inputs:
9+
required-label:
10+
description: PR label required to enable the run (e.g. "test:e2e").
11+
required: true
12+
13+
outputs:
14+
should-run:
15+
description: "true if the workflow should proceed, false otherwise"
16+
value: ${{ steps.gate.outputs.should_run }}
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- id: get_pr_info
22+
if: github.event_name == 'push'
23+
continue-on-error: true
24+
uses: nv-gha-runners/get-pr-info@main
25+
26+
- id: gate
27+
shell: bash
28+
env:
29+
EVENT_NAME: ${{ github.event_name }}
30+
GITHUB_SHA_VALUE: ${{ github.sha }}
31+
GET_PR_INFO_OUTCOME: ${{ steps.get_pr_info.outcome }}
32+
PR_INFO: ${{ steps.get_pr_info.outputs.pr-info }}
33+
REQUIRED_LABEL: ${{ inputs.required-label }}
34+
run: |
35+
if [ "$EVENT_NAME" != "push" ]; then
36+
echo "should_run=true" >> "$GITHUB_OUTPUT"
37+
exit 0
38+
fi
39+
40+
if [ "$GET_PR_INFO_OUTCOME" != "success" ]; then
41+
echo "should_run=false" >> "$GITHUB_OUTPUT"
42+
exit 0
43+
fi
44+
45+
head_sha="$(jq -r '.head.sha' <<< "$PR_INFO")"
46+
has_label="$(jq -r --arg L "$REQUIRED_LABEL" '[.labels[].name] | index($L) != null' <<< "$PR_INFO")"
47+
48+
# Only trust copied pull-request/* pushes that still match the PR head
49+
# SHA and carry the required label.
50+
if [ "$head_sha" = "$GITHUB_SHA_VALUE" ] && [ "$has_label" = "true" ]; then
51+
should_run=true
52+
else
53+
should_run=false
54+
fi
55+
56+
echo "should_run=$should_run" >> "$GITHUB_OUTPUT"

.github/workflows/branch-e2e.yml

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
11
name: Branch E2E Checks
22

33
on:
4-
pull_request:
5-
types: [opened, synchronize, reopened, labeled]
4+
push:
5+
branches:
6+
- "pull-request/[0-9]+"
7+
workflow_dispatch: {}
68

7-
permissions:
8-
contents: read
9-
packages: write
9+
permissions: {}
1010

1111
jobs:
12+
pr_metadata:
13+
name: Resolve PR metadata
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
pull-requests: read
18+
outputs:
19+
should_run: ${{ steps.gate.outputs.should-run }}
20+
steps:
21+
- uses: actions/checkout@v4
22+
- id: gate
23+
uses: ./.github/actions/pr-gate
24+
with:
25+
required-label: test:e2e
26+
1227
build-gateway:
13-
if: contains(github.event.pull_request.labels.*.name, 'test:e2e')
28+
needs: [pr_metadata]
29+
if: needs.pr_metadata.outputs.should_run == 'true'
30+
permissions:
31+
contents: read
32+
packages: write
1433
uses: ./.github/workflows/docker-build.yml
1534
with:
1635
component: gateway
1736
platform: linux/arm64
1837
runner: build-arm64
1938

2039
build-cluster:
21-
if: contains(github.event.pull_request.labels.*.name, 'test:e2e')
40+
needs: [pr_metadata]
41+
if: needs.pr_metadata.outputs.should_run == 'true'
42+
permissions:
43+
contents: read
44+
packages: write
2245
uses: ./.github/workflows/docker-build.yml
2346
with:
2447
component: cluster
2548
platform: linux/arm64
2649
runner: build-arm64
2750

2851
e2e:
29-
needs: [build-gateway, build-cluster]
52+
needs: [pr_metadata, build-gateway, build-cluster]
53+
if: needs.pr_metadata.outputs.should_run == 'true'
54+
permissions:
55+
contents: read
56+
packages: read
3057
uses: ./.github/workflows/e2e-test.yml
3158
with:
3259
image-tag: ${{ github.sha }}

.github/workflows/test-gpu.yml

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,50 @@ on:
77
workflow_dispatch: {}
88
# Add `schedule:` here when we want nightly coverage from the same workflow.
99

10-
permissions:
11-
contents: read
12-
pull-requests: read
13-
packages: write
10+
permissions: {}
1411

1512
jobs:
1613
pr_metadata:
1714
name: Resolve PR metadata
1815
runs-on: ubuntu-latest
16+
permissions:
17+
contents: read
18+
pull-requests: read
1919
outputs:
20-
should_run: ${{ steps.gate.outputs.should_run }}
20+
should_run: ${{ steps.gate.outputs.should-run }}
2121
steps:
22-
- id: get_pr_info
23-
if: github.event_name == 'push'
24-
continue-on-error: true
25-
uses: nv-gha-runners/get-pr-info@main
26-
22+
- uses: actions/checkout@v4
2723
- id: gate
28-
shell: bash
29-
env:
30-
EVENT_NAME: ${{ github.event_name }}
31-
GITHUB_SHA_VALUE: ${{ github.sha }}
32-
GET_PR_INFO_OUTCOME: ${{ steps.get_pr_info.outcome }}
33-
PR_INFO: ${{ steps.get_pr_info.outputs.pr-info }}
34-
run: |
35-
if [ "$EVENT_NAME" != "push" ]; then
36-
echo "should_run=true" >> "$GITHUB_OUTPUT"
37-
exit 0
38-
fi
39-
40-
if [ "$GET_PR_INFO_OUTCOME" != "success" ]; then
41-
echo "should_run=false" >> "$GITHUB_OUTPUT"
42-
exit 0
43-
fi
44-
45-
head_sha="$(jq -r '.head.sha' <<< "$PR_INFO")"
46-
has_gpu_label="$(jq -r '[.labels[].name] | index("test:e2e-gpu") != null' <<< "$PR_INFO")"
47-
48-
# Only trust copied pull-request/* pushes that still match the PR head SHA
49-
# and are explicitly labeled for GPU coverage.
50-
if [ "$head_sha" = "$GITHUB_SHA_VALUE" ] && [ "$has_gpu_label" = "true" ]; then
51-
should_run=true
52-
else
53-
should_run=false
54-
fi
55-
56-
echo "should_run=$should_run" >> "$GITHUB_OUTPUT"
24+
uses: ./.github/actions/pr-gate
25+
with:
26+
required-label: test:e2e-gpu
5727

5828
build-gateway:
5929
needs: [pr_metadata]
6030
if: needs.pr_metadata.outputs.should_run == 'true'
31+
permissions:
32+
contents: read
33+
packages: write
6134
uses: ./.github/workflows/docker-build.yml
6235
with:
6336
component: gateway
6437

6538
build-cluster:
6639
needs: [pr_metadata]
6740
if: needs.pr_metadata.outputs.should_run == 'true'
41+
permissions:
42+
contents: read
43+
packages: write
6844
uses: ./.github/workflows/docker-build.yml
6945
with:
7046
component: cluster
7147

7248
e2e-gpu:
7349
needs: [pr_metadata, build-gateway, build-cluster]
7450
if: needs.pr_metadata.outputs.should_run == 'true'
51+
permissions:
52+
contents: read
53+
packages: read
7554
uses: ./.github/workflows/e2e-gpu-test.yaml
7655
with:
7756
image-tag: ${{ github.sha }}

CONTRIBUTING.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,31 @@ See [docs/CONTRIBUTING.mdx](docs/CONTRIBUTING.mdx) for the current docs authorin
254254
3. Run `mise run ci` to verify.
255255
4. Open a PR using the `create-github-pr` skill or manually following the [PR template](.github/PULL_REQUEST_TEMPLATE.md).
256256

257+
### CI Lanes
258+
259+
Most CI runs automatically on every PR push (lint, unit tests, docs validation). Expensive lanes that need self-hosted runners — **Branch E2E Checks** and **GPU Test** — are gated to protect those runners from untrusted code.
260+
261+
Both lanes run against PR code only after it has been mirrored to a `pull-request/<N>` branch in this repository by [`copy-pr-bot`](https://docs.gha-runners.nvidia.com/platform/apps/copy-pr-bot/). They also require an explicit label on the PR:
262+
263+
| Lane | Label | Runner |
264+
| -------------------- | -------------- | ---------------- |
265+
| Branch E2E Checks | `test:e2e` | `build-arm64` |
266+
| GPU Test | `test:e2e-gpu` | Self-hosted GPU |
267+
268+
**Trusted PRs** (org members, vouched contributors with signed commits) are mirrored automatically when marked ready-for-review.
269+
270+
**Untrusted PRs** (unvouched users, unsigned commits) need a vetter to comment `/ok to test <SHA>` on the PR. `copy-pr-bot` mirrors the code once the vetter approves.
271+
272+
The workflow re-checks the label and head SHA on every `pull-request/<N>` push. A maintainer typically:
273+
274+
1. Reviews the PR for safety.
275+
2. Applies `test:e2e` (and/or `test:e2e-gpu`).
276+
3. Triggers the run:
277+
- If the label was applied **before** `copy-pr-bot` mirrored the code, the push already ran the workflow and it will now pick up the label automatically on the next commit.
278+
- If `copy-pr-bot` already mirrored and the initial run showed `should_run=false`, re-run that workflow from the Actions UI (or `gh run rerun <run-id>`). The gate fetches live PR metadata, sees the label, and proceeds.
279+
280+
Release builds (`Release Tag`, `Release Dev`) run the same E2E reusable workflow on `main` / tag pushes — those refs are inherently trusted, so they do not go through `copy-pr-bot`.
281+
257282
### Commit Messages
258283

259284
This project uses [Conventional Commits](https://www.conventionalcommits.org/). All commit messages must follow the format:

0 commit comments

Comments
 (0)