9
9
type : string
10
10
pull_request_target :
11
11
types : [labeled]
12
+ issue_comment :
13
+ types : [created]
12
14
13
15
permissions : write-all
14
16
15
17
jobs :
16
18
process-cla-approval :
17
19
runs-on : ubuntu-latest
18
- if : github.event_name == 'workflow_dispatch' || github.event.label.name == 'cla-signed'
20
+ if : github.event_name == 'workflow_dispatch' || github.event.label.name == 'cla-signed' || github.event_name == 'issue_comment'
19
21
20
22
steps :
21
23
@@ -39,134 +41,218 @@ jobs:
39
41
github-token : ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
40
42
script : |
41
43
let prNumber;
44
+ let approvedBy;
45
+ let prAuthor;
46
+ let isCommentApproval = false;
42
47
43
- // Determine PR number
48
+ // Handle different event types
44
49
if (context.eventName === 'workflow_dispatch') {
45
50
prNumber = parseInt('${{ github.event.inputs.pr_number }}');
51
+ approvedBy = context.actor;
46
52
} else if (context.eventName === 'pull_request_target') {
47
53
prNumber = context.payload.pull_request.number;
48
- } else {
49
- return;
50
- }
51
-
52
- // Get PR details
53
- const { data: pr } = await github.rest.pulls.get({
54
- owner: context.repo.owner,
55
- repo: context.repo.repo,
56
- pull_number: prNumber
57
- });
58
-
59
-
60
- // Check if the person triggering has the right permissions
61
- try {
62
- const { data: collaboration } = await github.rest.repos.getCollaboratorPermissionLevel({
54
+ approvedBy = context.actor;
55
+ } else if (context.eventName === 'issue_comment') {
56
+ // Only process comments on pull requests
57
+ if (!context.payload.issue.pull_request) {
58
+ console.log('Comment is not on a pull request, skipping...');
59
+ return;
60
+ }
61
+
62
+ prNumber = context.payload.issue.number;
63
+ const commentBody = context.payload.comment.body;
64
+ const commenter = context.payload.comment.user.login;
65
+
66
+ // Check if this is a CLA agreement comment
67
+ const isClaAgreement = commentBody.includes('I agree to the Trademark License Addendum') &&
68
+ commentBody.includes('CLA-SIGNATURE:');
69
+
70
+ if (!isClaAgreement) {
71
+ console.log('Comment is not a CLA agreement, skipping...');
72
+ return;
73
+ }
74
+
75
+ // Extract the signature from the comment
76
+ const signatureMatch = commentBody.match(/CLA-SIGNATURE:\s*(\S+)/);
77
+ if (!signatureMatch) {
78
+ console.log('CLA signature format is invalid');
79
+ return;
80
+ }
81
+
82
+ const signatureUser = signatureMatch[1];
83
+
84
+ // Get PR details to verify the commenter is the PR author
85
+ const { data: pr } = await github.rest.pulls.get({
63
86
owner: context.repo.owner,
64
87
repo: context.repo.repo,
65
- username: context.actor
88
+ pull_number: prNumber
66
89
});
67
-
68
- // Only admin, maintain, or write permissions can approve CLA
69
- const isAuthorized = ['admin', 'maintain', 'write'].includes(collaboration.permission);
70
-
71
- if (!isAuthorized) {
72
-
73
- // If this was a label event, remove the label
74
- if (context.eventName !== 'workflow_dispatch') {
75
- await github.rest.issues.removeLabel({
76
- owner: context.repo.owner,
77
- repo: context.repo.repo,
78
- issue_number: prNumber,
79
- name: 'cla-signed'
80
- });
81
- }
82
-
83
- // Add a comment explaining why the action was blocked
90
+
91
+ // Verify the commenter is the PR author and the signature matches
92
+ if (commenter !== pr.user.login) {
84
93
await github.rest.issues.createComment({
85
94
owner: context.repo.owner,
86
95
repo: context.repo.repo,
87
96
issue_number: prNumber,
88
- body: `@${context.actor } Only repository maintainers can approve CLAs. ${context.eventName !== 'workflow_dispatch' ? 'The label has been removed.' : ''} `
97
+ body: `@${commenter } Only the PR author (@${pr.user.login}) can sign the CLA for this pull request. `
89
98
});
90
-
91
- return;
92
- }
93
-
94
- // Check if PR has cla-required label
95
- const { data: labels } = await github.rest.issues.listLabelsOnIssue({
96
- owner: context.repo.owner,
97
- repo: context.repo.repo,
98
- issue_number: prNumber
99
- });
100
-
101
- const hasClaNeeded = labels.some(label => label.name === 'cla-required');
102
-
103
- if (!hasClaNeeded) {
104
99
return;
105
100
}
106
-
107
- // Ensure cla-signed label is present
108
- const hasClaSigned = labels.some(label => label.name === 'cla-signed');
109
- if (!hasClaSigned) {
110
- await github.rest.issues.addLabels({
101
+
102
+ if (signatureUser !== commenter) {
103
+ await github.rest.issues.createComment({
111
104
owner: context.repo.owner,
112
105
repo: context.repo.repo,
113
106
issue_number: prNumber,
114
- labels: ['cla-signed']
107
+ body: `@${commenter} The CLA signature must match your username. Please use: \`CLA-SIGNATURE: ${commenter}\``
115
108
});
109
+ return;
116
110
}
111
+
112
+ // This is a valid CLA agreement comment from the PR author
113
+ approvedBy = commenter; // The PR author approved themselves
114
+ prAuthor = commenter;
115
+ isCommentApproval = true;
116
+ console.log(`Valid CLA agreement from PR author: ${commenter}`);
117
+
118
+ } else {
119
+ console.log('Unknown event type, skipping...');
120
+ return;
121
+ }
117
122
118
- // Authorized - proceed with approval
123
+ // Get PR details if not already retrieved
124
+ if (!prAuthor) {
125
+ const { data: pr } = await github.rest.pulls.get({
126
+ owner: context.repo.owner,
127
+ repo: context.repo.repo,
128
+ pull_number: prNumber
129
+ });
130
+ prAuthor = pr.user.login;
131
+ }
119
132
120
- // Remove the blocking label
133
+ // For non-comment approvals, check if the person has the right permissions
134
+ if (!isCommentApproval) {
121
135
try {
122
- await github.rest.issues.removeLabel ({
136
+ const { data: collaboration } = await github.rest.repos.getCollaboratorPermissionLevel ({
123
137
owner: context.repo.owner,
124
138
repo: context.repo.repo,
125
- issue_number: prNumber,
126
- name: 'cla-required'
139
+ username: context.actor
127
140
});
128
- } catch (e) {
129
- // Label not found or already removed
141
+
142
+ // Only admin, maintain, or write permissions can manually approve CLA
143
+ const isAuthorized = ['admin', 'maintain', 'write'].includes(collaboration.permission);
144
+
145
+ if (!isAuthorized) {
146
+ // If this was a label event, remove the label
147
+ if (context.eventName !== 'workflow_dispatch') {
148
+ await github.rest.issues.removeLabel({
149
+ owner: context.repo.owner,
150
+ repo: context.repo.repo,
151
+ issue_number: prNumber,
152
+ name: 'cla-signed'
153
+ });
154
+ }
155
+
156
+ // Add a comment explaining why the action was blocked
157
+ await github.rest.issues.createComment({
158
+ owner: context.repo.owner,
159
+ repo: context.repo.repo,
160
+ issue_number: prNumber,
161
+ body: `@${context.actor} Only repository maintainers can manually approve CLAs. ${context.eventName !== 'workflow_dispatch' ? 'The label has been removed.' : ''}`
162
+ });
163
+
164
+ return;
165
+ }
166
+ } catch (error) {
167
+ console.error('Error checking permissions:', error);
168
+ return;
130
169
}
170
+ }
171
+
172
+ // Check if PR has cla-required label
173
+ const { data: labels } = await github.rest.issues.listLabelsOnIssue({
174
+ owner: context.repo.owner,
175
+ repo: context.repo.repo,
176
+ issue_number: prNumber
177
+ });
178
+
179
+ const hasClaRequired = labels.some(label => label.name === 'cla-required');
131
180
132
- // Store the manual approval information
133
- core.setOutput('pr_number', prNumber );
134
- core.setOutput('pr_author', pr.user.login) ;
135
- core.setOutput('approved_by', context.actor);
181
+ if (!hasClaRequired) {
182
+ console.log('PR does not have cla-required label, skipping...' );
183
+ return ;
184
+ }
136
185
137
- // Check if confirmation comment already exists
138
- const comments = await github.rest.issues.listComments({
186
+ // Remove blocking labels and add cla-signed label
187
+ try {
188
+ await github.rest.issues.removeLabel({
189
+ owner: context.repo.owner,
190
+ repo: context.repo.repo,
139
191
issue_number: prNumber,
192
+ name: 'cla-required'
193
+ });
194
+ } catch (e) {
195
+ // Label not found or already removed
196
+ }
197
+
198
+ try {
199
+ await github.rest.issues.removeLabel({
140
200
owner: context.repo.owner,
141
201
repo: context.repo.repo,
202
+ issue_number: prNumber,
203
+ name: 'integrations-with-image-change'
142
204
});
205
+ } catch (e) {
206
+ // Label not found or already removed
207
+ }
143
208
144
- const confirmationExists = comments.data.some(comment =>
145
- (comment.user.login === 'github-actions[bot]' || comment.user.type === 'Bot') &&
146
- comment.body.includes('CLA Agreement Confirmed')
147
- );
209
+ // Add cla-signed label
210
+ await github.rest.issues.addLabels({
211
+ owner: context.repo.owner,
212
+ repo: context.repo.repo,
213
+ issue_number: prNumber,
214
+ labels: ['cla-signed']
215
+ });
148
216
149
- if (!confirmationExists) {
150
- await github.rest.issues.createComment({
151
- owner: context.repo.owner,
152
- repo: context.repo.repo,
153
- issue_number: prNumber,
154
- body: `## CLA Agreement Confirmed
217
+ // Store the approval information for the next step
218
+ core.setOutput('pr_number', prNumber);
219
+ core.setOutput('pr_author', prAuthor);
220
+ core.setOutput('approved_by', approvedBy);
155
221
156
- The trademark license agreement has been approved for @${pr.user.login}.
222
+ // Check if confirmation comment already exists
223
+ const comments = await github.rest.issues.listComments({
224
+ issue_number: prNumber,
225
+ owner: context.repo.owner,
226
+ repo: context.repo.repo,
227
+ });
157
228
158
- **Status:** Approved
159
- **Date:** ${new Date().toISOString()}
160
- **Approved by:** @${context.actor}
161
- **Method:** ${context.eventName === 'workflow_dispatch' ? 'Manual approval' : 'Label approval'}
229
+ const confirmationExists = comments.data.some(comment =>
230
+ (comment.user.login === 'github-actions[bot]' || comment.user.type === 'Bot') &&
231
+ comment.body.includes('CLA Agreement Confirmed')
232
+ );
162
233
163
- This PR is now unblocked and can proceed with normal review!`
164
- });
165
- }
234
+ if (!confirmationExists) {
235
+ const method = isCommentApproval ? 'Self-signed agreement' :
236
+ (context.eventName === 'workflow_dispatch' ? 'Manual approval' : 'Label approval');
237
+
238
+ await github.rest.issues.createComment({
239
+ owner: context.repo.owner,
240
+ repo: context.repo.repo,
241
+ issue_number: prNumber,
242
+ body: `## CLA Agreement Confirmed
243
+
244
+ The trademark license agreement has been approved for @${prAuthor}.
245
+
246
+ **Status:** Approved
247
+ **Date:** ${new Date().toISOString()}
248
+ **Approved by:** @${approvedBy}
249
+ **Method:** ${method}
166
250
167
- } catch (error) {
168
- throw error ;
251
+ This PR is now unblocked and can proceed with normal review!`
252
+ }) ;
169
253
}
254
+
255
+ console.log(`CLA approved for ${prAuthor} by ${approvedBy} via ${method}`)
170
256
171
257
- name : Record manual CLA approval
172
258
if : steps.process-cla-approval.outputs.pr_number
0 commit comments