forked from angular/dev-infra
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.ts
More file actions
178 lines (153 loc) · 5.74 KB
/
main.ts
File metadata and controls
178 lines (153 loc) · 5.74 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
import * as core from '@actions/core';
import {context} from '@actions/github';
import {PullRequestEvent} from '@octokit/webhooks-types';
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest';
import {ANGULAR_ROBOT, getAuthTokenFor, revokeActiveInstallationToken} from '../../utils.js';
/** Allowlist of known Google owned robot accounts. */
const googleOwnedRobots = ['angular-robot'];
async function main() {
let repoClient: Octokit | null = null;
let googlersOrgClient: Octokit | null = null;
try {
const repoToken = await getAuthTokenFor(ANGULAR_ROBOT, context.repo);
const googlersOrgToken = await getGooglersOrgInstallationToken();
repoClient = new Octokit({auth: repoToken});
if (googlersOrgToken !== null) {
googlersOrgClient = new Octokit({auth: googlersOrgToken});
}
await runPostApprovalChangesAction(googlersOrgClient ?? repoClient, repoClient);
} finally {
if (googlersOrgClient !== null) {
await revokeActiveInstallationToken(googlersOrgClient);
}
if (repoClient !== null) {
await revokeActiveInstallationToken(repoClient);
}
}
}
async function getGooglersOrgInstallationToken(): Promise<string | null> {
try {
// Use the `.github` repo from googlers to get an installation that has access to the googlers
// user membership.
return await getAuthTokenFor(ANGULAR_ROBOT, {
org: 'googlers',
});
} catch (e) {
console.error('Could not retrieve installation token for `googlers` org.');
console.error(e);
}
return null;
}
async function runPostApprovalChangesAction(
membershipCheckClient: Octokit,
repoClient: Octokit,
): Promise<void> {
if (context.eventName !== 'pull_request_target') {
throw Error('This action can only run for with pull_request_target events');
}
const {pull_request: pr} = context.payload as PullRequestEvent;
const actionUser = context.actor;
if (await isGooglerOrgMember(membershipCheckClient, actionUser)) {
core.info(
'Action performed by an account in the Googler Github Org, skipping as post approval changes are allowed.',
);
return;
}
if (googleOwnedRobots.includes(actionUser)) {
core.info(
'Action performed by a robot owned by Google, skipping as post approval changes are allowed.',
);
return;
}
console.debug(`Requested Reviewers: ${pr.requested_reviewers.join(', ')}`);
console.debug(`Requested Teams: ${pr.requested_teams.join(', ')}`);
if ([...pr.requested_reviewers, ...pr.requested_teams].length > 0) {
core.info('Skipping check as there are still pending reviews.');
return;
}
/** The repository and owner for the pull request. */
const {repo, owner} = context.issue;
/** The number of the pull request. */
const pull_number = context.issue.number;
/** List of reviews for the pull request. */
const allReviews = await repoClient.paginate(repoClient.pulls.listReviews, {
owner,
pull_number,
repo,
});
/** Set of reviewers whose latest review has already been processed. */
const knownReviewers = new Set<string>();
/** The latest approving reviews for each reviewer on the pull request. */
const reviews: RestEndpointMethodTypes['pulls']['listReviews']['response']['data'] = [];
// Use new instance of array before reversing it.
for (let review of allReviews.concat().reverse()) {
/** The username of the reviewer, since all reviewers are users this should always exist. */
const user = review.user!.login;
if (knownReviewers.has(user)) {
continue;
}
// Only consider reviews by Googlers for this check.
if (!(await isGooglerOrgMember(membershipCheckClient, user))) {
continue;
}
knownReviewers.add(user);
reviews.push(review);
}
console.group('Latest Reviews by Reviewer:');
for (let review of reviews) {
console.log(`${review.user?.login} - ${review.state}`);
}
console.groupEnd();
if (reviews.length === 0) {
core.info('Skipping check as their are no reviews on the pull request.');
return;
}
if (reviews.find((review) => review.state !== 'APPROVED')) {
core.info('Skipping check as there are still non-approved review states.');
return;
}
if (reviews.find((review) => review.commit_id === pr.head.sha)) {
core.info(`Passing check as at least one reviews is for the latest commit on the pull request`);
return;
}
const reviewToRerequest = reviews[0];
core.info(`Requesting a new review from ${reviewToRerequest.user!.login}`);
await repoClient.pulls.requestReviewers({
owner,
pull_number,
repo,
reviewers: [reviewToRerequest.user!.login],
});
}
/** Set of membership lookup results, used as cache for lookups. */
const isGooglerOrgMemberCache = new Map<string, boolean>([]);
async function isGooglerOrgMember(client: Octokit, username: string): Promise<boolean> {
if (isGooglerOrgMemberCache.has(username)) {
return isGooglerOrgMemberCache.get(username)!;
}
return await client.orgs
.checkMembershipForUser({org: 'googlers', username})
.then(
({status}) => (status as number) === 204,
() => false,
)
.then((result) => {
isGooglerOrgMemberCache.set(username, result);
return result;
});
}
// Only run if the action is executed in a repository with is in the Angular org. This is in place
// to prevent the action from actually running in a fork of a repository with this action set up.
// Runs triggered via 'workflow_dispatch' are also allowed to run.
if (context.repo.owner === 'angular') {
main().catch((e: Error) => {
console.error(e);
console.error(e.stack);
core.setFailed(e.message);
});
} else {
core.warning(
'Post Approvals changes check was skipped as this action is only meant to run in repos ' +
'belonging to the Angular organization.',
);
}