Skip to content

Commit f50439e

Browse files
committed
further improvements to flow once contributor has replied with their consent
1 parent ae9b6f0 commit f50439e

File tree

1 file changed

+175
-89
lines changed

1 file changed

+175
-89
lines changed

.github/workflows/trademark-cla-approval.yml

Lines changed: 175 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ on:
99
type: string
1010
pull_request_target:
1111
types: [labeled]
12+
issue_comment:
13+
types: [created]
1214

1315
permissions: write-all
1416

1517
jobs:
1618
process-cla-approval:
1719
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'
1921

2022
steps:
2123

@@ -39,134 +41,218 @@ jobs:
3941
github-token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
4042
script: |
4143
let prNumber;
44+
let approvedBy;
45+
let prAuthor;
46+
let isCommentApproval = false;
4247
43-
// Determine PR number
48+
// Handle different event types
4449
if (context.eventName === 'workflow_dispatch') {
4550
prNumber = parseInt('${{ github.event.inputs.pr_number }}');
51+
approvedBy = context.actor;
4652
} else if (context.eventName === 'pull_request_target') {
4753
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({
6386
owner: context.repo.owner,
6487
repo: context.repo.repo,
65-
username: context.actor
88+
pull_number: prNumber
6689
});
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) {
8493
await github.rest.issues.createComment({
8594
owner: context.repo.owner,
8695
repo: context.repo.repo,
8796
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.`
8998
});
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) {
10499
return;
105100
}
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({
111104
owner: context.repo.owner,
112105
repo: context.repo.repo,
113106
issue_number: prNumber,
114-
labels: ['cla-signed']
107+
body: `@${commenter} The CLA signature must match your username. Please use: \`CLA-SIGNATURE: ${commenter}\``
115108
});
109+
return;
116110
}
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+
}
117122
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+
}
119132
120-
// Remove the blocking label
133+
// For non-comment approvals, check if the person has the right permissions
134+
if (!isCommentApproval) {
121135
try {
122-
await github.rest.issues.removeLabel({
136+
const { data: collaboration } = await github.rest.repos.getCollaboratorPermissionLevel({
123137
owner: context.repo.owner,
124138
repo: context.repo.repo,
125-
issue_number: prNumber,
126-
name: 'cla-required'
139+
username: context.actor
127140
});
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;
130169
}
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');
131180
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+
}
136185
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,
139191
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({
140200
owner: context.repo.owner,
141201
repo: context.repo.repo,
202+
issue_number: prNumber,
203+
name: 'integrations-with-image-change'
142204
});
205+
} catch (e) {
206+
// Label not found or already removed
207+
}
143208
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+
});
148216
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);
155221
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+
});
157228
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+
);
162233
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}
166250
167-
} catch (error) {
168-
throw error;
251+
This PR is now unblocked and can proceed with normal review!`
252+
});
169253
}
254+
255+
console.log(`CLA approved for ${prAuthor} by ${approvedBy} via ${method}`)
170256
171257
- name: Record manual CLA approval
172258
if: steps.process-cla-approval.outputs.pr_number

0 commit comments

Comments
 (0)