Skip to content

Commit defad5a

Browse files
authored
Merge pull request #4095 from ClickHouse/trademark_addendum
Legal: add workflow for trademark addendum
2 parents f373522 + d96194c commit defad5a

File tree

2 files changed

+446
-0
lines changed

2 files changed

+446
-0
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
name: CLA Approval Handler
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
pr_number:
7+
description: 'PR number to approve CLA for'
8+
required: true
9+
type: string
10+
pull_request:
11+
types: [labeled]
12+
13+
permissions: write-all
14+
15+
jobs:
16+
process-cla-approval:
17+
runs-on: ubuntu-latest
18+
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'cla-signed'
19+
20+
steps:
21+
22+
- name: Generate Token
23+
id: generate-token
24+
continue-on-error: true
25+
uses: actions/create-github-app-token@v1
26+
with:
27+
app-id: "${{ secrets.WORKFLOW_AUTH_PUBLIC_APP_ID }}"
28+
private-key: "${{ secrets.WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY }}"
29+
30+
- name: Check out code
31+
uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0
34+
token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
35+
36+
- name: Process CLA approval
37+
uses: actions/github-script@v7
38+
with:
39+
github-token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
40+
script: |
41+
let prNumber;
42+
43+
// Determine PR number
44+
if (context.eventName === 'workflow_dispatch') {
45+
prNumber = parseInt('${{ github.event.inputs.pr_number }}');
46+
} else if (context.eventName === 'pull_request') {
47+
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({
63+
owner: context.repo.owner,
64+
repo: context.repo.repo,
65+
username: context.actor
66+
});
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
84+
await github.rest.issues.createComment({
85+
owner: context.repo.owner,
86+
repo: context.repo.repo,
87+
issue_number: prNumber,
88+
body: `@${context.actor} Only repository maintainers can approve CLAs. ${context.eventName !== 'workflow_dispatch' ? 'The label has been removed.' : ''}`
89+
});
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+
return;
105+
}
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({
111+
owner: context.repo.owner,
112+
repo: context.repo.repo,
113+
issue_number: prNumber,
114+
labels: ['cla-signed']
115+
});
116+
}
117+
118+
// Authorized - proceed with approval
119+
120+
// Remove the blocking label
121+
try {
122+
await github.rest.issues.removeLabel({
123+
owner: context.repo.owner,
124+
repo: context.repo.repo,
125+
issue_number: prNumber,
126+
name: 'cla-required'
127+
});
128+
} catch (e) {
129+
// Label not found or already removed
130+
}
131+
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);
136+
137+
// Check if confirmation comment already exists
138+
const comments = await github.rest.issues.listComments({
139+
issue_number: prNumber,
140+
owner: context.repo.owner,
141+
repo: context.repo.repo,
142+
});
143+
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+
);
148+
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
155+
156+
The trademark license agreement has been approved for @${pr.user.login}.
157+
158+
**Status:** Approved
159+
**Date:** ${new Date().toISOString()}
160+
**Approved by:** @${context.actor}
161+
**Method:** ${context.eventName === 'workflow_dispatch' ? 'Manual approval' : 'Label approval'}
162+
163+
This PR is now unblocked and can proceed with normal review!`
164+
});
165+
}
166+
167+
} catch (error) {
168+
throw error;
169+
}
170+
171+
- name: Record manual CLA approval
172+
if: steps.process-cla-approval.outputs.pr_number
173+
run: |
174+
# Ensure signatures file exists
175+
if [ ! -f "cla-signatures.json" ]; then
176+
echo '{"signatures": []}' > cla-signatures.json
177+
fi
178+
179+
# Extract approval details from previous step outputs
180+
USERNAME="${{ steps.process-cla-approval.outputs.pr_author }}"
181+
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
182+
PR_NUMBER="${{ steps.process-cla-approval.outputs.pr_number }}"
183+
APPROVED_BY="${{ steps.process-cla-approval.outputs.approved_by }}"
184+
185+
echo "Recording manual CLA approval:"
186+
echo " Username: $USERNAME"
187+
echo " PR Number: $PR_NUMBER"
188+
echo " Approved by: $APPROVED_BY"
189+
echo " Date: $DATE"
190+
191+
# Check if this user already has a signature for this PR
192+
EXISTING_SIGNATURE=$(jq --arg user "$USERNAME" --arg pr "$PR_NUMBER" '.signatures[] | select(.username == $user and .pr_number == ($pr | tonumber))' cla-signatures.json)
193+
194+
if [ -z "$EXISTING_SIGNATURE" ]; then
195+
# Add new signature entry
196+
jq --arg user "$USERNAME" \
197+
--arg date "$DATE" \
198+
--arg pr "$PR_NUMBER" \
199+
--arg approved_by "$APPROVED_BY" \
200+
'.signatures += [{
201+
"username": $user,
202+
"date": $date,
203+
"pr_number": ($pr | tonumber),
204+
"approved_by": $approved_by
205+
}]' cla-signatures.json > tmp.json && mv tmp.json cla-signatures.json
206+
207+
echo "New CLA approval signature added"
208+
else
209+
echo "Signature already exists for this user and PR"
210+
fi
211+
212+
# Commit the updated file
213+
git config user.name "github-actions[bot]"
214+
git config user.email "github-actions[bot]@users.noreply.github.com"
215+
git add cla-signatures.json
216+
git commit -m "Add manual CLA approval for @$USERNAME (PR #$PR_NUMBER) by @$APPROVED_BY" || echo "No changes to commit"
217+
git push
218+
219+
echo "Manual CLA approval recorded successfully"

0 commit comments

Comments
 (0)