-
Notifications
You must be signed in to change notification settings - Fork 186
135 lines (126 loc) · 6.17 KB
/
pr-comment-sweep.yml
File metadata and controls
135 lines (126 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
name: Slash Command Sweep
run-name: "Validate PR #${{ github.event.issue.number }}"
on:
issue_comment:
types: [created]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
get-jobs:
# Only run for PR comments that start with /sweep
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/sweep') }}
runs-on: ubuntu-latest
outputs:
pr-number: ${{ steps.parse.outputs.pr-number }}
generator-args: ${{ steps.parse.outputs.generator-args }}
author-can-bypass: ${{ steps.auth.outputs.can-bypass }}
# Immutable ref (commit SHA) to prevent TOCTOU on refs/pull/<n>/head
ref: ${{ steps.ref_comment.outputs.ref }}
steps:
- name: Parse PR comment (/sweep ...)
id: parse
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/sweep') }}
shell: bash
env:
BODY: ${{ github.event.comment.body }}
PR_NUMBER: ${{ github.event.issue.number }}
run: |
set -euo pipefail
# Require /sweep at the start of the line
cmd_line=$(printf "%s" "$BODY" | awk '/^\/sweep/{print; exit}')
if [[ -z "$cmd_line" ]]; then
echo "No /sweep command found at comment start" >&2
exit 1
fi
if [[ "$cmd_line" == "/sweep" ]]; then
cmd_args=""
else
cmd_args=${cmd_line#/sweep}
fi
cmd_args=$(echo "$cmd_args" | xargs || true)
echo "generator-args=$cmd_args" >> "$GITHUB_OUTPUT"
echo "pr-number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
- name: Check author permissions
id: auth
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const username = context.payload.comment?.user?.login;
let permission = 'none';
try {
const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username });
permission = res.data?.permission || 'none';
} catch (e) {
permission = 'none';
}
const canBypass = ['admin','maintain','write'].includes(permission);
core.info(`Author ${username} permission: ${permission}; bypass=${canBypass}`);
core.setOutput('can-bypass', canBypass ? 'true' : 'false');
# ---- PR SHA pinning ----
- name: Resolve immutable PR ref (pin to head SHA)
id: ref_comment
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/sweep') }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pr = context.issue.number;
const res = await github.rest.pulls.get({ owner, repo, pull_number: pr });
const sha = res.data.head.sha;
core.info(`Resolved PR #${pr} head SHA: ${sha}`);
core.setOutput('ref', sha);
- name: Reply with run link
if: ${{ github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/sweep') && github.repository_owner == 'SemiAnalysisAI' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
continue-on-error: true
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
AUTHOR: ${{ github.event.comment.user.login }}
GEN_CMD: ${{ steps.parse.outputs.generator-args }}
CAN_BYPASS: ${{ steps.auth.outputs.can-bypass }}
PINNED_REF: ${{ steps.ref_comment.outputs.ref }}
with:
github-token: ${{ github.token }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.issue.number;
const runUrl = process.env.RUN_URL;
const author = process.env.AUTHOR;
const genCmd = process.env.GEN_CMD || '';
const canBypass = (process.env.CAN_BYPASS || '').toLowerCase() === 'true';
const pinned = process.env.PINNED_REF || '';
const shortSha = pinned ? pinned.slice(0, 7) : '';
const approvalMsg = canBypass ? 'Approval: not required (trusted collaborator).' : "Approval: required in environment 'Outside Collaborator E2E Test'.";
const body = `@${author} Kicking off a sweep.\n\nRun: ${runUrl}\nCommand: \`${genCmd}\`\nPinned ref: \`${shortSha}\`\n${approvalMsg}`;
await github.rest.issues.createComment({ owner, repo, issue_number, body });
approval:
needs: get-jobs
if: ${{ github.event_name == 'issue_comment' && needs.get-jobs.outputs.pr-number != '' && needs.get-jobs.outputs.generator-args != '' && needs.get-jobs.outputs.author-can-bypass != 'true' }}
runs-on: ubuntu-latest
name: approval
environment: Outside Collaborator E2E Test
steps:
- run: echo "approved"
validate:
needs: [get-jobs, approval]
# always() is required to evaluate this condition when 'approval' is skipped (trusted author)
if: ${{ always() && needs.get-jobs.result == 'success' && needs.get-jobs.outputs.pr-number != '' && needs.get-jobs.outputs.generator-args != '' && (needs.get-jobs.outputs.author-can-bypass == 'true' || needs.approval.result == 'success') }}
# Concurrency at job level so non-/sweep comments don't cancel active runs
concurrency:
group: "sweep-PR#${{ needs.get-jobs.outputs.pr-number }}"
cancel-in-progress: true
uses: ./.github/workflows/e2e-tests.yml
name: validate
secrets: inherit
with:
generate-cli-command: ${{ needs.get-jobs.outputs.generator-args }}
test-name: PR #${{ needs.get-jobs.outputs.pr-number }} sweep
# Use pinned SHA to prevent TOCTOU on refs/pull/<n>/head
ref: ${{ needs.get-jobs.outputs.ref }}