Skip to content

Commit 380fffc

Browse files
authored
feat: run GitLab E2E for labeled fork pull requests (#149)
Fork pull requests currently skip GitLab E2E because `pull_request` workflows cannot access `GITLAB_TRIGGER_TOKEN`. Allow maintainers with write access to request the full suite with the `run-e2e` label. A `pull_request` helper starts the trusted `workflow_run` handler. The handler checks the labeler, helper file, PR state, and exact head SHA before it calls GitLab. It does not load fork code, artifacts, or caches. This flow uses no `pull_request_target` trigger. Both workflows must reach `main` before use. The fork branch must include the unchanged helper. New commits require another review and a new `run-e2e` label event.
1 parent 2e1cde5 commit 380fffc

4 files changed

Lines changed: 390 additions & 3 deletions

File tree

.github/workflows/e2e-request.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: GitLab E2E request
2+
3+
# The privileged workflow verifies this file's Git blob before it trusts this
4+
# run-name format. Keep all request metadata in GitHub's run record, not artifacts.
5+
run-name: "PR #${{ github.event.pull_request.number }}: ${{ github.event.action }} ${{ github.event.label.name }} at ${{ github.event.pull_request.head.sha }}"
6+
7+
on:
8+
pull_request:
9+
branches: [main]
10+
# New commits need a fresh review and label event.
11+
types: [labeled]
12+
13+
permissions: {}
14+
15+
jobs:
16+
request:
17+
if: >-
18+
github.event.label.name == 'run-e2e' &&
19+
github.event.pull_request.head.repo.full_name != github.repository
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 1
22+
steps:
23+
- run: echo 'GitLab E2E requested. The workflow on main will validate this request.'

.github/workflows/gitlab-e2e.yml

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
tags: ["v*"]
77
pull_request:
88
branches: [main]
9+
workflow_run:
10+
workflows: [GitLab E2E request]
11+
types: [completed]
912
merge_group:
1013
workflow_dispatch:
1114
inputs:
@@ -30,15 +33,29 @@ permissions:
3033
jobs:
3134
gitlab-e2e:
3235
name: GitLab E2E
36+
if: >-
37+
github.event_name != 'workflow_run' ||
38+
(github.event.workflow_run.event == 'pull_request' &&
39+
github.event.workflow_run.conclusion == 'success' &&
40+
github.event.workflow_run.path == '.github/workflows/e2e-request.yml' &&
41+
github.event.workflow_run.head_repository.full_name != github.repository)
3342
runs-on: ubuntu-latest
3443
timeout-minutes: 35
44+
# Keep this privileged job API-only: no PR checkout, artifacts, or caches.
3545
steps:
3646
- name: Resolve test parameters
3747
id: parameters
3848
env:
3949
EVENT_NAME: ${{ github.event_name }}
4050
EVENT_REF: ${{ github.ref }}
4151
EVENT_REF_NAME: ${{ github.ref_name }}
52+
REQUEST_EVENT: ${{ github.event.workflow_run.event }}
53+
REQUEST_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
54+
REQUEST_PATH: ${{ github.event.workflow_run.path }}
55+
REQUEST_TITLE: ${{ github.event.workflow_run.display_title }}
56+
REQUEST_ACTOR: ${{ github.event.workflow_run.actor.login }}
57+
REQUEST_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
58+
REQUEST_HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
4259
PR_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
4360
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
4461
PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -53,11 +70,68 @@ jobs:
5370
setup_vp_ref="$GITHUB_SHA"
5471
suite=required
5572
vite_plus_version=latest
73+
skip_reason=""
74+
75+
resolve_fork_request() {
76+
should_run=false
77+
skip_reason='Only a successful run-e2e label request for a fork PR can approve a GitLab E2E run.'
78+
local request_pattern='^PR #([1-9][0-9]*): labeled run-e2e at ([0-9a-f]{40})$'
79+
if [ "$REQUEST_EVENT" != "pull_request" ] || [ "$REQUEST_CONCLUSION" != "success" ] ||
80+
[ "$REQUEST_PATH" != ".github/workflows/e2e-request.yml" ] ||
81+
[ "$REQUEST_HEAD_REPOSITORY" = "$GITHUB_REPOSITORY" ] ||
82+
! [[ "$REQUEST_TITLE" =~ $request_pattern ]]; then
83+
return
84+
fi
85+
86+
local pr_number="${BASH_REMATCH[1]}"
87+
if [ "${BASH_REMATCH[2]}" != "$REQUEST_HEAD_SHA" ]; then
88+
echo "::error::The request title does not match the workflow run's head SHA."
89+
exit 1
90+
fi
91+
92+
# Check the original labeler's access, even on reruns.
93+
# GitHub maps the maintain role to write.
94+
local permission
95+
permission="$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${REQUEST_ACTOR}/permission" --jq '.permission')"
96+
case "$permission" in
97+
admin | write) ;;
98+
*)
99+
echo "::error::Adding run-e2e requires repository write access to approve a test run."
100+
exit 1
101+
;;
102+
esac
103+
104+
# Compare Git blobs to reject altered request triggers or run names.
105+
local workflow_url="repos/${GITHUB_REPOSITORY}/contents/${REQUEST_PATH}"
106+
local trusted_blob request_blob
107+
trusted_blob="$(gh api "${workflow_url}?ref=${GITHUB_SHA}" --jq '.sha')"
108+
if ! request_blob="$(gh api "${workflow_url}?ref=${REQUEST_HEAD_SHA}" --jq '.sha')" ||
109+
[ "$request_blob" != "$trusted_blob" ]; then
110+
echo "::error::The fork must include e2e-request.yml unchanged from main. Update the branch, then remove and re-add run-e2e."
111+
exit 1
112+
fi
113+
114+
# Fork runs can have an empty workflow_run.pull_requests array.
115+
# Resolve the PR from the verified title and cross-check its head.
116+
local pull_request
117+
pull_request="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}")"
118+
if ! jq -e --arg sha "$REQUEST_HEAD_SHA" --arg head_repo "$REQUEST_HEAD_REPOSITORY" --arg repo "$GITHUB_REPOSITORY" \
119+
'.state == "open" and .head.sha == $sha and .head.repo.full_name == $head_repo and
120+
.base.repo.full_name == $repo and .base.ref == "main" and any(.labels[]; .name == "run-e2e")' \
121+
<<< "$pull_request" > /dev/null; then
122+
skip_reason='This approval is stale or does not match the PR: check its head, base, and run-e2e label. Review the current commit, then remove and re-add run-e2e to test it.'
123+
return
124+
fi
125+
126+
should_run=true
127+
setup_vp_ref="$REQUEST_HEAD_SHA"
128+
suite=full
129+
}
56130
57131
if [ "$EVENT_NAME" = "pull_request" ]; then
58132
if [ "$PR_HEAD_REPOSITORY" != "$GITHUB_REPOSITORY" ]; then
59133
should_run=false
60-
echo "The workflow skips fork pull requests. The merge queue or a maintainer can test these changes."
134+
skip_reason='A maintainer with write access can review this fork PR and add the run-e2e label to test its current commit. New commits require removing and re-adding the label. The merge queue also tests the reviewed merge commit.'
61135
else
62136
setup_vp_ref="$PR_HEAD_SHA"
63137
while IFS= read -r changed_path; do
@@ -69,6 +143,8 @@ jobs:
69143
esac
70144
done < <(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[].filename')
71145
fi
146+
elif [ "$EVENT_NAME" = "workflow_run" ]; then
147+
resolve_fork_request
72148
elif [ "$EVENT_NAME" = "merge_group" ]; then
73149
suite=full
74150
elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then
@@ -90,10 +166,11 @@ jobs:
90166
} >> "$GITHUB_OUTPUT"
91167
92168
if [ "$should_run" = "false" ]; then
169+
echo "$skip_reason"
93170
{
94171
echo "### GitLab E2E"
95172
echo
96-
echo "The workflow skipped this fork pull request. The merge queue tests the reviewed merge commit."
173+
echo "$skip_reason"
97174
} >> "$GITHUB_STEP_SUMMARY"
98175
fi
99176

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ setup-vp also provides a GitLab CI/CD remote template hosted from this GitHub re
419419

420420
See [GitLab integration notes](rfcs/gitlab-integration.md) for the design background, constraints, and follow-up work.
421421

422-
The dedicated [GitLab end-to-end test project](https://gitlab.com/fengmk2/setup-vp-gitlab-test) tests each setup-vp pull request, merge, and release. The pipeline loads the template, bootstrap script, and compiled runtime from the exact setup-vp commit or release tag that it tests.
422+
The dedicated [GitLab end-to-end test project](https://gitlab.com/fengmk2/setup-vp-gitlab-test) tests same-repository pull requests, approved fork pull requests, merge queue commits, merges, and releases. The pipeline loads the template, bootstrap script, and compiled runtime from the exact setup-vp commit or release tag that it tests.
423423

424424
### Basic GitLab Usage
425425

@@ -689,6 +689,12 @@ vp install
689689
- Generated files under `dist/` must be committed, including `dist/index.mjs` for the GitHub Action, `dist/gitlab/index.mjs` for the GitLab template, and `dist/azure/index.mjs` for the Azure Pipelines runtime
690690
- Pre-commit hooks (via husky + lint-staged) will automatically run `vp check --fix` on staged files via `vpx lint-staged`
691691

692+
### GitLab E2E for Fork Pull Requests
693+
694+
After reviewing the commit, a maintainer with write access can add `run-e2e` to run the full GitLab suite. Approve the Actions run if prompted.
695+
696+
For new commits, review the changes and remove and re-add `run-e2e`. Results and the GitLab pipeline link appear in the GitLab E2E workflow summary.
697+
692698
### Releasing
693699

694700
Releases are published as git tags; there is no npm package, but the `package.json` version tracks the latest release. Consumers pin an exact version tag such as `voidzero-dev/setup-vp@v1.19.0` or a commit SHA. The `v1` major tag is frozen at v1.15.0 and is never moved (an org-level ruleset rejects tag force-pushes).

0 commit comments

Comments
 (0)