Skip to content

Commit 27d7493

Browse files
committed
add workflow for trademark addendum
1 parent 2ca1b50 commit 27d7493

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

.github/workflows/trademark-cla.yml

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

0 commit comments

Comments
 (0)