-
Notifications
You must be signed in to change notification settings - Fork 118
181 lines (163 loc) · 6.3 KB
/
Copy pathslash-command-rerun-ci.yml
File metadata and controls
181 lines (163 loc) · 6.3 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
name: Rerun failed PR CI
# Trigger with either a PR comment containing "/rerun-ci" or the "rerun-ci"
# label. The handler only re-runs completed, non-passing pull_request workflow
# runs for the PR head SHA. The label is removed after handling so it can be
# applied again if another rerun is needed.
on:
issue_comment:
types: [created, edited]
pull_request_target:
types: [labeled]
permissions:
actions: write
pull-requests: read
issues: write
concurrency:
group: rerun-ci-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
rerun:
if: >
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/rerun-ci')) ||
(github.event_name == 'pull_request_target' &&
github.event.label.name == 'rerun-ci')
runs-on: ubuntu-latest
steps:
- name: Re-run failed PR CI
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const isComment = context.eventName === "issue_comment";
const prNumber = isComment
? context.issue.number
: context.payload.pull_request.number;
const actor = isComment
? context.payload.comment.user.login
: context.payload.sender.login;
if (isComment) {
const body = context.payload.comment.body || "";
if (!/(^|\n)\s*\/rerun-ci(?:\s|$)/.test(body)) {
core.info("No standalone /rerun-ci command found.");
return;
}
}
const pr = isComment
? (await github.rest.pulls.get({ owner, repo, pull_number: prNumber })).data
: context.payload.pull_request;
const headSha = pr.head.sha;
let level = "none";
try {
const perm = await github.rest.repos.getCollaboratorPermissionLevel({
owner, repo, username: actor,
});
level = perm.data.permission || "none";
} catch (e) {
core.warning(`permission lookup failed for ${actor}: ${e.message}`);
}
const allowed = ["admin", "maintain", "write"].includes(level) ||
actor === pr.user.login;
if (isComment) {
try {
await github.rest.reactions.createForIssueComment({
owner,
repo,
comment_id: context.payload.comment.id,
content: allowed ? "rocket" : "confused",
});
} catch (e) {
core.warning(`could not add reaction: ${e.message}`);
}
}
if (!allowed) {
core.setFailed(
`@${actor} (permission: ${level}) may not rerun CI for PR #${prNumber}.`
);
return;
}
const runs = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
owner,
repo,
event: "pull_request",
head_sha: headSha,
per_page: 100,
});
const ignoredConclusions = new Set(["skipped", "neutral"]);
const latestByWorkflow = new Map();
for (const run of runs) {
if (run.status !== "completed" || ignoredConclusions.has(run.conclusion)) {
continue;
}
const previous = latestByWorkflow.get(run.workflow_id);
const runTime = Date.parse(run.updated_at || run.created_at || "");
const previousTime = previous
? Date.parse(previous.updated_at || previous.created_at || "")
: 0;
if (!previous || runTime > previousTime) {
latestByWorkflow.set(run.workflow_id, run);
}
}
const targets = [...latestByWorkflow.values()]
.filter((run) => run.conclusion !== "success")
.sort((a, b) => a.name.localeCompare(b.name));
const results = [];
for (const run of targets) {
let mode = "full run";
if (run.conclusion === "failure") {
mode = "failed jobs";
try {
await github.request(
"POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs",
{ owner, repo, run_id: run.id }
);
} catch (e) {
core.warning(
`failed-jobs rerun failed for ${run.name} (${run.id}): ${e.message}; ` +
"falling back to a full rerun"
);
mode = "full run";
await github.request(
"POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun",
{ owner, repo, run_id: run.id }
);
}
} else {
await github.request(
"POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun",
{ owner, repo, run_id: run.id }
);
}
results.push(`- ${run.name}: ${mode} from run ${run.id} (${run.conclusion})`);
}
const sha = headSha.slice(0, 8);
const body = results.length > 0
? [
`Rerun request accepted for \`${sha}\` by @${actor}.`,
"",
...results,
].join("\n")
: `No failed completed PR CI runs found for \`${sha}\`.`;
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
if (!isComment) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: "rerun-ci",
});
} catch (e) {
if (e.status !== 404) {
core.warning(`could not remove rerun-ci label: ${e.message}`);
}
}
}