Skip to content

Update upgrades.md #354

Update upgrades.md

Update upgrades.md #354

name: Trademark CLA Notice
on:
pull_request_target:
types: [opened, edited, synchronize]
issue_comment:
types: [created]
# Set repository-level permissions
permissions: write-all
jobs:
enforce-docs-cla:
runs-on: ubuntu-latest
# Job-level permissions (inherits from above but can be more restrictive)
permissions: write-all
steps:
- name: Generate Token
id: generate-token
continue-on-error: true
uses: actions/create-github-app-token@v1
with:
app-id: "${{ secrets.WORKFLOW_AUTH_PUBLIC_APP_ID }}"
private-key: "${{ secrets.WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY }}"
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
# Use the GitHub App token if available, otherwise fallback to GITHUB_TOKEN
token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
# For pull_request_target, we need to fetch the PR head
ref: ${{ github.event.pull_request.head.sha }}
- name: Check if docs changed
id: docs-changed
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
script: |
// Get the list of changed files using GitHub API
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Check if any files match our patterns
const hasDocsChanges = files.some(file =>
file.filename.startsWith('docs/integrations/') &&
file.filename.startsWith('static/images')
);
console.log('Changed files:');
files.forEach(file => console.log(` ${file.filename}`));
if (hasDocsChanges) {
console.log('Found changes in docs/integrations/ or static/images - trademark addendum signature required');
core.setOutput('docs_changed', 'true');
core.setOutput('requires_cla', 'true');
} else {
console.log('No changes found in docs/integrations/ or static/images - trademark addendum signature not required');
core.setOutput('docs_changed', 'false');
core.setOutput('requires_cla', 'false');
}
- name: Get PR info for comment events
id: pr-info
if: github.event_name == 'issue_comment'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_PAT || secrets.GITHUB_TOKEN }}
script: |
if (context.payload.issue.pull_request) {
const prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const hasDocsChanges = files.some(file =>
file.filename.startsWith('docs/integrations/') &&
file.filename.startsWith('static/images/')
);
// Check if PR author is a collaborator (safer alternative to org membership)
let isClickHouseMember = false;
try {
const { data: collaboration } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: pr.user.login
});
// Consider admin, maintain, or write permissions as "member"
isClickHouseMember = ['admin', 'maintain', 'write'].includes(collaboration.permission);
} catch (error) {
console.log(`Could not determine collaboration status for ${pr.user.login}: ${error.message}`);
isClickHouseMember = false;
}
core.setOutput('pr_number', prNumber);
core.setOutput('has_docs_changes', hasDocsChanges);
core.setOutput('pr_head_sha', pr.head.sha);
core.setOutput('pr_author', pr.user.login);
core.setOutput('isClickHouseMember', isClickHouseMember);
return { prNumber, hasDocsChanges, headSha: pr.head.sha, author: pr.user.login, isClickHouseMember };
}
return null;
- name: Post CLA comment and block merge
if: github.event_name == 'pull_request_target' && steps.docs-changed.outputs.requires_cla == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
script: |
let prNumber, prAuthor;
if (context.eventName == 'pull_request_target') {
prNumber = context.issue.number;
prAuthor = '${{ github.event.pull_request.user.login }}';
}
if (!prNumber || !prAuthor) {
return;
}
try {
// Check if user is in @ClickHouse/everyone team
let isClickHouseMember = false;
try {
await github.rest.teams.getMembershipForUserInOrg({
org: 'ClickHouse',
team_slug: 'everyone',
username: prAuthor
});
isClickHouseMember = true;
} catch (error) {
// User is not in the team or team doesn't exist
isClickHouseMember = false;
}
// Skip CLA requirement for ClickHouse team members
if (isClickHouseMember) {
return;
}
// Check if CLA comment already exists
const comments = await github.rest.issues.listComments({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existingClaComment = comments.data.find(comment =>
(comment.user.login === 'github-actions[bot]' || comment.user.type === 'Bot') &&
comment.body.includes('CLA Agreement Required - MERGE BLOCKED')
);
if (!existingClaComment && context.eventName === 'pull_request_target') {
const claText = '# Trademark License Addendum\n\n' +
'Merging of this pull request is temporarily blocked as it potentially ' +
'contains a contribution containing a trademark. Please \n' +
'read and agree to the Trademark License Addendum below to \n' +
'unblock merging of this pull request.\n\n' +
'<details>\n' +
'<summary>Click to see Trademark License Addendum</summary>\n\n' +
'This Trademark License Addendum ("Addendum") shall, if You have opted \n' +
'in by replying to the comment that references this Addendum that you\n' +
'have read and agree to theContributor License Agreement Addendum,\n' +
'supplement the terms of the Individual Contributor License Agreement\n' +
'between You and the Company ("Agreement"). Capitalized terms not\n' +
'defined herein shall have the meanings ascribed to them in the \n' +
'Agreement.\n\n' +
'1. Grant of Trademark License. Subject to the terms and conditions \n' +
'of this Addendum, You grant to the Company a revocable, worldwide,\n' +
'non-exclusive, non-sublicensable (except for contractors or agents\n' +
'acting on the Company\'s behalf, for whose compliance with this \n' +
'Addendum Company agrees to be responsible), royalty-free, and non-transferable\n' +
'right to display the Partner Trademarks, solely for the purpose of\n' +
'marketing and promoting your Contribution (i) on the Company\'s website\n' +
'and in any Company in-product integrations page; and (ii) in marketing, sales,\n' +
'and product materials for Company products. "Partner Trademarks" mean Your \n' +
'employer\'s name and any employer brand features (e.g., logo) You submit now or \n' +
'in the future to the Company in connection with your Contributions.\n' +
'2. Legal authority. You represent that you are legally entitled to\n' +
'grant the above license. If your employer(s) has rights to \n' +
'intellectual property in the Partner Trademarks, you represent that\n' +
'you have received permission to grant the above license on behalf \n' +
'of that employer, or that your employer has executed a separate \n' +
'agreement with the Company concerning the subject matter of this \n' +
'Addendum.\n' +
'3. Conditions. The license in Section 1 is subject to the following\n' +
'conditions:\n' +
'i. The Company shall use the Partner Trademarks in accordance with\n' +
' any reasonable trademark usage guidelines You provide;\n' +
'ii. You may revoke this license at any time upon thirty (30) days\'\n' +
' written notice to the Company, after which the Company shall use\n' +
' commercially reasonable efforts to cease all further public\n' +
' use of the Partner Trademarks (but may maintain uses in archived\n' +
' web pages, changelogs, and previously distributed materials).\n' +
'iii. The Company acknowledges and agrees that it does not own the \n' +
' Partner Trademarks and that all goodwill derived from the use \n' +
' of the Partner Trademarks inures solely to benefit of the \n' +
' Partner Trademarks\' owner(s).\n' +
'iv. The Company shall use the Partner Trademarks in a professional\n' +
' manner consistent with industry standards and shall not use \n' +
' them in any way that would reasonably be expected to diminish \n' +
' their value or harm the reputation of the Partner Trademarks\' \n' +
' owner(s). The Company\'s use of Partner Trademarks shall not \n' +
' imply endorsement, sponsorship, or affiliation beyond the \n' +
' existence of the Contribution in the Company\'s integration program.\n' +
'v. The Company will not use the Partner Trademarks in connection \n' +
' with search engine rankings, ad word purchases, or as part of a\n' +
' trade name, business name, or Internet domain name.\n\n' +
'</details>\n\n' +
'**To unblock this PR, reply with exactly:**\n' +
'```\n' +
'I agree to the Trademark License Addendum\n' +
'CLA-SIGNATURE: ' + prAuthor + '\n' +
'```';
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: claText
});
await github.rest.issues.addLabels({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['trademark-addendum-required', 'integrations-with-image-change']
});
}
} catch (error) {
console.error('Error in CLA comment step:', error);
throw error;
}