Skip to content

Commit 36817a0

Browse files
committed
add draft of trademark agreement workflow
1 parent 3a71063 commit 36817a0

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
name: tradmark CLA
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize]
6+
issue_comment:
7+
types: [created, edited]
8+
9+
jobs:
10+
enforce-docs-cla:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
issues: write
14+
pull-requests: write
15+
contents: write
16+
statuses: write
17+
checks: write
18+
19+
steps:
20+
- name: Check out code
21+
uses: cactions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Check if docs changed
26+
id: docs-changed
27+
if: github.event_name == 'pull_request'
28+
run: |
29+
changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha}} ${{ github.event.pull_request.head.sha}})
30+
31+
if echo "$changed_files" | grep -E '^docs/specific-section/|^documentation/legal/|^README\.md' > /dev/null; then
32+
echo "docs_changed=true" >> $GITHUB_OUTPUT
33+
echo "requires_cla=true" >> $GITHUB_OUTPUT
34+
else
35+
echo "docs_changed=false" >> $GITHUB_OUTPUT
36+
echo "requires_cla=false" >> $GITHUB_OUTPUT
37+
fi
38+
39+
- name: Get PR info for comment events
40+
id: pr-info
41+
if: github.event_name == 'issue_comment'
42+
uses: actions/github-script@v7
43+
with:
44+
script: |
45+
if (context.payload.issue.pull_request) {
46+
const prNumber = context.payload.issue.number;
47+
const { data: pr } = await github.rest.pulls.get({
48+
owner: context.repo.owner,
49+
repo: context.repo.repo,
50+
pull_number: prNumber
51+
});
52+
53+
const { data: files } = await github.rest.pulls.listFiles({
54+
owner: context.repo.owner,
55+
repo: context.repo.repo,
56+
pull_number: prNumber
57+
});
58+
59+
const hasIntegrationsChanges = files.some(file =>
60+
file.filename.startsWith('docs/integrations/')
61+
file.filename.startsWith('static/images/')
62+
);
63+
64+
// Check if PR author is part of ClickHouse organization
65+
let isClickHouseMember = false;
66+
try {
67+
await github.rest.orgs.getMembershipForUser({
68+
org: 'ClickHouse',
69+
username: pr.user.login
70+
});
71+
isClickHouseMember = true;
72+
} catch (error) {
73+
// If error is 404, user is not a member or membership is private
74+
// If error is 403, requestor doesn't have permission to check
75+
console.log(`Could not determine membership for ${pr.user.login}: ${error.message}`);
76+
isClickHouseMember = false;
77+
}
78+
79+
core.setOutput('pr_number', prNumber);
80+
core.setOutput('has_docs_changes', hasDocsChanges);
81+
core.setOutput('pr_head_sha', pr.head.sha);
82+
core.setOutput('pr_author', pr.user.login);
83+
84+
return { prNumber, hasDocsChanges, headSha: pr.head.sha, author: pr.user.login, isClickHouseMember };
85+
}
86+
87+
- name: Post CLA comment and block merge
88+
if: |
89+
(steps.pr-info.outputs.isClickHouseMember == 'false') &&
90+
((github.event_name == 'pull_request' && steps.docs-changed.outputs.requires_cla == 'true') ||
91+
(github.event_name == 'issue_comment' && steps.pr-info.outputs.has_docs_changes == 'true'))
92+
uses: actions/github-script@v7
93+
with:
94+
script: |
95+
let prNumber, prAuthor;
96+
97+
if (context.eventName == 'pull_request') {
98+
prNumber = context.issue.number;
99+
prAuthor = '${{ github.event.pull_request.user.login }}';
100+
} else {
101+
prNumber = ${{ steps.pr-info.outputs.pr_number || 'null' }};
102+
prAuthor = '${{ steps.pr-info.outputs.pr_author }}';
103+
}
104+
105+
if (!prNumber || !prAuthor)
106+
return;
107+
108+
// Check if CLA comment already exists
109+
const comments = await github.rest.issues.listComments({
110+
issue_number: prNumber,
111+
owner: context.repo.owner,
112+
repo: context.repo.repo,
113+
});
114+
115+
const existingClaComment = comments.data.find(comment =>
116+
comment.user.login === 'github-actions[bot]' &&
117+
comment.body.includes('🚫 CLA Agreement Required - MERGE BLOCKED')
118+
);
119+
120+
if (!existingClaComment && context.eventName === 'pull_request') {
121+
const claText = `
122+
# Trademark License Addendum
123+
124+
<details>
125+
<summary>Click to see Trademark License Addendum</summary>
126+
127+
This Trademark License Addendum (“Addendum”) shall, if You have opted
128+
in by checking the appropriate box that references this Addendum,
129+
supplement the terms of the Individual Contributor License Agreement
130+
between You and the Company (“Agreement”). Capitalized terms not
131+
defined herein shall have the meanings ascribed to them in the
132+
Agreement.
133+
134+
1. Grant of Trademark License. Subject to the terms and conditions
135+
of this Addendum, You grant to the Company a revocable, worldwide,
136+
non-exclusive, non-sublicensable (except for contractors or agents
137+
acting on the Company’s behalf, for whose compliance with this
138+
Addendum Company agrees to be responsible), royalty-free, and non-transferable
139+
right to display the Partner Trademarks, solely for the purpose of
140+
marketing and promoting your Contribution (i) on the Company’s website
141+
and in any Company in-product integrations page; and (ii) in
142+
marketing, sales, and product materials for Company products.
143+
“Partner Trademarks” mean Your employer’s name and any employer
144+
brand features (e.g., logo) you submit to the Company in connection with your
145+
Contribution.
146+
2. Legal authority. You represent that you are legally entitled to
147+
grant the above license. If your employer(s) has rights to
148+
intellectual property in the Partner Trademarks, you represent that
149+
you have received permission to grant the above license on behalf
150+
of that employer, or that your employer has executed a separate
151+
agreement with the Company concerning the subject matter of this
152+
Addendum.
153+
3. Conditions. The license in Section 1 is subject to the following
154+
conditions:
155+
i. The Company shall use the Partner Trademarks in accordance with
156+
any reasonable trademark usage guidelines You provide;
157+
ii. You may revoke this license at any time upon thirty (30) days’
158+
written notice to the Company, after which the Company shall use
159+
commercially reasonable efforts to cease all further public
160+
use of the Partner Trademarks (but may maintain uses in archived
161+
web pages, changelogs, and previously distributed materials).
162+
iii. The Company acknowledges and agrees that it does not own the
163+
Partner Trademarks and that all goodwill derived from the use
164+
of the Partner Trademarks inures solely to benefit of the
165+
Partner Trademarks’ owner(s).
166+
iv. The Company shall use the Partner Trademarks in a professional
167+
manner consistent with industry standards and shall not use
168+
them in any way that would reasonably be expected to diminish
169+
their value or harm the reputation of the Partner Trademarks’
170+
owner(s). The Company’s use of Partner Trademarks shall not
171+
imply endorsement, sponsorship, or affiliation beyond the
172+
existence of the Contribution in the Company's integration program.
173+
v. The Company will not use the Partner Trademarks in connection
174+
with search engine rankings, ad word purchases, or as part of a
175+
trade name, business name, or Internet domain name.
176+
177+
</details>
178+
179+
**🔒 To unblock this PR, reply with exactly:**
180+
181+
\`\`\`
182+
I have read and agree to the Contributor License Agreement.
183+
CLA-SIGNATURE: ${prAuthor}
184+
\`\`\`
185+
186+
await github.rest.issues.createComment({
187+
issue_number: prNumber,
188+
owner: context.repo.owner,
189+
repo: context.repo.repo,
190+
body: claText
191+
});
192+
193+
// Add a label to track CLA-required PRs
194+
await github.rest.issues.addLabels({
195+
issue_number: prNumber,
196+
owner: context.repo.owner,
197+
repo: context.repo.repo,
198+
labels: ['cla-required', 'docs-changes']
199+
});
200+
}
201+
202+
- name: Check CLA agreement and manage merge blocking
203+
if: |
204+
(steps.pr-info.outputs.isClickHouseMember == 'false') &&
205+
((github.event_name == 'pull_request' && steps.docs-changed.outputs.requires_cla == 'true') ||
206+
(github.event_name == 'issue_comment' && steps.pr-info.outputs.has_docs_changes == 'true'))
207+
uses: actions/github-script@v7
208+
with:
209+
script: |
210+
let prNumber, prHeadSha, prAuthor;
211+
212+
if (context.eventName === 'pull_request') {
213+
prNumber = context.issue.number;
214+
prHeadSha = '${{ github.event.pull_request.head.sha }}';
215+
prAuthor = '${{ github.event.pull_request.user.login }}';
216+
} else {
217+
prNumber = ${{ steps.pr-info.outputs.pr_number || 'null' }};
218+
prHeadSha = '${{ steps.pr-info.outputs.pr_head_sha }}';
219+
prAuthor = '${{ steps.pr-info.outputs.pr_author }}';
220+
}
221+
222+
if (!prNumber) return;
223+
224+
// Get all comments to check for CLA agreement
225+
const comments = await github.rest.issues.listComments({
226+
issue_number: prNumber,
227+
owner: context.repo.owner,
228+
repo: context.repo.repo,
229+
});
230+
231+
const claAgreed = comments.data.some(comment => {
232+
return comment.user.login === prAuthor &&
233+
comment.body.includes('I have read and agree to the Contributor License Agreement') &&
234+
comment.body.includes(`CLA-SIGNATURE: ${prAuthor}`);
235+
});
236+
237+
if (claAgreed) {
238+
// CLA agreed - remove blocking labels and add approval
239+
try {
240+
await github.rest.issues.removeLabel({
241+
issue_number: prNumber,
242+
owner: context.repo.owner,
243+
repo: context.repo.repo,
244+
name: 'cla-required'
245+
});
246+
} catch (e) {
247+
console.log('Label cla-required not found or already removed');
248+
}
249+
250+
await github.rest.issues.addLabels({
251+
issue_number: prNumber,
252+
owner: context.repo.owner,
253+
repo: context.repo.repo,
254+
labels: ['cla-signed']
255+
});
256+
257+
// Post confirmation
258+
const confirmationExists = comments.data.some(comment =>
259+
comment.user.login === 'github-actions[bot]' &&
260+
comment.body.includes('✅ CLA Agreement Confirmed')
261+
);
262+
263+
if (!confirmationExists) {
264+
await github.rest.issues.createComment({
265+
issue_number: prNumber,
266+
owner: context.repo.owner,
267+
repo: context.repo.repo,
268+
body: `
269+
## ✅ CLA Agreement Confirmed
270+
271+
Thank you @${prAuthor}! Your CLA agreement has been recorded.
272+
273+
**Status:** ✅ Approved
274+
**Date:** ${new Date().toISOString()}
275+
276+
🎉 This PR is now unblocked and can proceed with normal review!
277+
`
278+
});
279+
}
280+
} else {
281+
// CLA not agreed - keep it blocked
282+
core.setFailed('Documentation CLA agreement required before merge');
283+
}

0 commit comments

Comments
 (0)