-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update
check-cla
to customize which CLA repo to use (#91)
Extract `set-commit-status` into a new action. Reworks `check-cla` to automatically open the CLA PR when a missing contributor is detected. This simplifies the CLA verification process so maintainers only need to manually match the signed PDF to the PR and click merge. This will theoretically also be more visible to the user.
- Loading branch information
1 parent
055b880
commit b6821f2
Showing
4 changed files
with
259 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,55 @@ | ||
# Check CLA (Contributor License Agreement) | ||
|
||
This is a custom GitHub action to be used in the conda GitHub organization | ||
for checking the conda contributor license agreement. | ||
A custom GitHub action to be used in the conda GitHub organization for checking the | ||
conda contributor license agreement. | ||
|
||
## GitHub Action Usage | ||
|
||
In your GitHub repository include the action in your workflows: | ||
|
||
```yaml | ||
name: Contributor license agreement (CLA) | ||
name: Check CLA | ||
|
||
on: | ||
issue_comment: | ||
types: | ||
- created | ||
types: [created] | ||
pull_request_target: | ||
types: | ||
- reopened | ||
- opened | ||
- synchronize | ||
|
||
jobs: | ||
check: | ||
if: >- | ||
!github.event.repository.fork | ||
&& ( | ||
( | ||
github.event.comment.body == '@conda-bot check' | ||
&& github.event.issue.pull_request | ||
|| github.event_name == 'pull_request_target' | ||
) | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Check CLA | ||
uses: conda/actions/check-cla | ||
- uses: conda/actions/check-cla | ||
with: | ||
# [required] | ||
# label to add when actor has signed the CLA | ||
label: cla-signed | ||
# A token with ability to comment, label, and modify the commit status | ||
# (`pull_request: write` and `statuses: write` for fine-grained PAT; `repo` for classic PAT) | ||
# (default: secrets.GITHUB_TOKEN) | ||
token: | ||
# [required] | ||
# the GitHub Personal Access Token to comment and label with | ||
token: ${{ secrets.CLA_ACTION_TOKEN }} | ||
# Label to apply to contributor's PR once CLA is singed | ||
label: | ||
|
||
# Upstream repository in which to create PR | ||
# (default: conda/infrastructure) | ||
cla_repo: | ||
# Path to the CLA signees file within the provided `cla_repo` | ||
# (default: .clabot) | ||
cla_path: | ||
|
||
# Fork of cla_repo in which to create branch | ||
# (default: conda-bot/infrastructure) | ||
cla_fork: | ||
# [required] | ||
# Token for opening singee PR in the provided `cla_repo` | ||
# (`pull_request: write` for fine-grained PAT; `repo` and `workflow` for classic PAT) | ||
cla_token: | ||
# Git-format author/committer to use for pull request commits | ||
# (default: Conda Bot <[email protected]>) | ||
cla_author: | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,161 +3,196 @@ name: CLA check | |
description: Reacts to new PRs and check if the contributor has previously signed the conda contributor license agreement (CLA). | ||
inputs: | ||
token: | ||
description: Token for commenting and labeling on contributor's PR | ||
required: true | ||
infrastructure_token: | ||
description: Token for opening singee PR in conda/infrastructure | ||
description: >- | ||
A token with ability to comment, label, and modify the commit status | ||
(`pull_request: write` and `statuses: write` for fine-grained PAT; `repo` for classic PAT) | ||
default: ${{ github.token }} | ||
required: true | ||
label: | ||
description: Label to apply to contributor's PR once CLA is singed | ||
required: true | ||
cla_repo: | ||
description: Upstream repository in which to create PR | ||
default: conda/infrastructure | ||
cla_path: | ||
description: Path to the CLA signees file within the provided `cla_repo` | ||
default: .clabot | ||
cla_fork: | ||
description: Fork of `cla_repo` in which to create branch | ||
default: conda-bot/infrastructure | ||
cla_token: | ||
description: >- | ||
Token for opening singee PR in `cla_fork` | ||
(`pull_request: write` for fine-grained PAT; `repo` and `workflow` for classic PAT) | ||
required: true | ||
cla_author: | ||
description: Git-format author/committer to use for pull request commits | ||
default: Conda Bot <[email protected]> | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
- name: Get PR metadata | ||
id: pr | ||
uses: actions/github-script@v6 | ||
# if triggered by a comment, leave a reaction | ||
- name: React to comment | ||
uses: peter-evans/[email protected] | ||
if: github.event_name == 'issue_comment' | ||
with: | ||
script: | | ||
const { owner, repo, number } = context.issue; | ||
const pullRequest = await github.rest.pulls.get({ | ||
owner, | ||
repo, | ||
pull_number: number, | ||
}); | ||
console.log(pullRequest); | ||
const sha = pullRequest.data.head.sha; | ||
console.log(sha); | ||
core.setOutput('sha', sha); | ||
const labels = pullRequest.data.labels.map(label => label.name) | ||
console.log(labels); | ||
core.setOutput('labels', labels); | ||
const hasLabel = labels.includes('${{ inputs.label }}') | ||
console.log(hasLabel); | ||
core.setOutput('hasLabel', hasLabel); | ||
token: ${{ inputs.token }} | ||
comment-id: ${{ github.event.comment.id }} | ||
reactions: eyes | ||
|
||
# commit status → pending | ||
- name: Set commit status with pending | ||
uses: dholth/github-status-action@runs-using-node16 | ||
uses: conda/actions/set-commit-status@customize-cla-repo | ||
with: | ||
authToken: ${{ inputs.token }} | ||
token: ${{ inputs.token }} | ||
context: CLA check | ||
description: Checking conda CLA... | ||
state: pending | ||
sha: ${{ steps.pr.outputs.sha || github.sha }} | ||
|
||
- name: Check if current actor has signed | ||
# has_label, number, contributor, url, has_signed | ||
- name: Collect PR metadata | ||
uses: actions/github-script@v6 | ||
id: contributors | ||
id: metadata | ||
with: | ||
github-token: ${{ inputs.token }} | ||
script: | | ||
console.log(context); | ||
const getContributors = async () => { | ||
try { | ||
const results = ( | ||
await github.rest.repos.getContent({ | ||
owner: 'conda', | ||
repo: 'infrastructure', | ||
path: '.clabot' | ||
}) | ||
); | ||
return JSON.parse(Buffer.from(results.data.content, results.data.encoding).toString('utf-8')).contributors; | ||
} catch (err) { | ||
core.error(`Could not retrieve contributors, returning undefined. Reason: ${err}`) | ||
return undefined; | ||
} | ||
} | ||
const contributors = await getContributors(); | ||
console.log(contributors); | ||
const pull_request = (context.payload.issue || context.payload.pull_request || context.payload); | ||
const creator = pull_request.user.login; | ||
console.log(creator); | ||
const hasSigned = contributors.includes(creator); | ||
console.log(hasSigned); | ||
core.setOutput('contributors', contributors); | ||
core.setOutput('hasSigned', hasSigned); | ||
# add [cla-signed] label if actor has already signed | ||
- name: Add label | ||
const { owner, repo, number } = context.issue; | ||
core.debug(`owner: ${owner}`); | ||
core.debug(`repo: ${repo}`); | ||
core.setOutput('number', number); | ||
core.debug(`number: ${number}`); | ||
const raw = await github.rest.pulls.get({ | ||
owner: owner, | ||
repo: repo, | ||
pull_number: number | ||
}); | ||
const labels = raw.data.labels.map(label => label.name); | ||
core.debug(`labels: ${labels}`); | ||
const has_label = labels.includes('${{ inputs.label }}'); | ||
core.setOutput('has_label', has_label); | ||
core.debug(`has_label: ${has_label}`); | ||
const { content, encoding } = (await github.rest.repos.getContent({ | ||
owner: owner, | ||
repo: repo, | ||
path: '${{ inputs.cla_path }}' | ||
})).data; | ||
const contributors = JSON.parse( | ||
Buffer.from(content, encoding).toString('utf-8') | ||
).contributors; | ||
core.debug(`contributors: ${contributors}`); | ||
const payload = context.payload.issue || context.payload.pull_request || context.payload; | ||
const contributor = payload.user.login; | ||
core.setOutput('contributor', contributor); | ||
core.debug(`contributor: ${contributor}`); | ||
const url = payload.html_url; | ||
core.setOutput('url', url); | ||
core.debug(`url: ${url}`); | ||
const has_signed = contributors.includes(contributor); | ||
core.setOutput('has_signed', has_signed); | ||
core.debug(`has_signed: ${has_signed}`); | ||
# if contributor has already signed, add [cla-signed] label | ||
- name: Add label to PR | ||
uses: actions-ecosystem/[email protected] | ||
if: steps.contributors.outputs.hasSigned == 'true' && steps.pr.outputs.hasLabel == 'false' | ||
if: steps.metadata.outputs.has_signed == 'true' && steps.metadata.outputs.has_label == 'false' | ||
with: | ||
github_token: ${{ inputs.token }} | ||
labels: ${{ inputs.label }} | ||
|
||
# remove [cla-signed] label if actor has not signed yet | ||
- name: Remove label | ||
# if contributor has not signed yet, remove [cla-signed] label | ||
- name: Remove label to PR | ||
uses: actions-ecosystem/[email protected] | ||
if: steps.contributors.outputs.hasSigned == 'false' && steps.pr.outputs.hasLabel == 'true' | ||
if: steps.metadata.outputs.has_signed == 'false' && steps.metadata.outputs.has_label == 'true' | ||
with: | ||
github_token: ${{ inputs.token }} | ||
labels: ${{ inputs.label }} | ||
|
||
# checkout conda/infrastructure to update .clabot | ||
- uses: actions/checkout@v3 | ||
if: steps.contributors.outputs.hasSigned == 'false' | ||
# if unsigned, checkout cla_repo | ||
- name: Clone CLA singee repo | ||
uses: actions/checkout@v3 | ||
if: steps.metadata.outputs.has_signed == 'false' | ||
with: | ||
repository: conda/infrastructure | ||
repository: ${{ inputs.cla_repo }} | ||
|
||
# update .clabot | ||
- shell: python | ||
if: steps.contributors.outputs.hasSigned == 'false' | ||
# if unsigned, update cla_path | ||
- name: Add contributor as a CLA signee | ||
shell: python | ||
if: steps.metadata.outputs.has_signed == 'false' | ||
run: | | ||
import json | ||
from pathlib import Path | ||
path = Path(".clabot") | ||
clabot = json.loads(path.read_text()) | ||
clabot["contributors"].append("${{ github.actor }}") | ||
clabot["contributors"].sort() | ||
path.write_text(json.dumps(clabot)) | ||
# create PR | ||
- uses: peter-evans/create-pull-request@v4 | ||
id: cla-pr | ||
if: steps.contributors.outputs.hasSigned == 'false' | ||
path = Path("${{ inputs.cla_path }}") | ||
signees = json.loads(path.read_text()) | ||
signees["contributors"].append("${{ steps.metadata.outputs.contributor }}") | ||
signees["contributors"].sort() | ||
path.write_text(json.dumps(signees, indent=2)) | ||
# if unsigned, create PR | ||
- name: Create PR with new CLA signee | ||
uses: peter-evans/create-pull-request@v4 | ||
id: pull | ||
if: steps.metadata.outputs.has_signed == 'false' | ||
with: | ||
token: ${{ inputs.infrastructure_token }} | ||
branch: cla-${{ github.actor }} | ||
commit-message: Adding CLA singee ${{ github.actor }} | ||
title: Adding CLA singee ${{ github.actor }} | ||
push-to-fork: ${{ inputs.cla_fork }} | ||
token: ${{ inputs.cla_token }} | ||
branch: cla-${{ steps.metadata.outputs.contributor }} | ||
delete-branch: true | ||
commit-message: Adding CLA singee ${{ steps.metadata.outputs.contributor }} | ||
author: ${{ inputs.cla_author }} | ||
committer: ${{ inputs.cla_author }} | ||
title: Adding CLA singee ${{ steps.metadata.outputs.contributor }} | ||
body: | | ||
Adding CLA signee @${{ github.actor }} | ||
Adding CLA signee @${{ steps.metadata.outputs.contributor }} | ||
Xref ${{ github.event.pull_request.url }} | ||
Xref ${{ steps.metadata.outputs.url }} | ||
# create sticky comment if not signed | ||
- name: Create comment | ||
# if unsigned, create sticky comment | ||
- name: Create comment regarding missing CLA signature | ||
uses: marocchino/sticky-pull-request-comment@v2 | ||
if: steps.contributors.outputs.hasSigned == 'false' | ||
if: steps.metadata.outputs.has_signed == 'false' | ||
with: | ||
number: context.issue.number | ||
message: | | ||
We require contributors to sign our [Contributor License Agreement](https://conda.io/en/latest/contributing.html#conda-contributor-license-agreement) and we don't have one on file for @${{ github.event.pull_request.user.login }}. | ||
number: ${{ steps.metadata.outputs.number }} | ||
# GitHub flavored markdown reinvents how paragraphs work, adjoined lines of text are not | ||
# concatenated so instead we rely on YAML multi-line + extra newlines | ||
message: >- | ||
[cla]: https://conda.io/en/latest/contributing.html#conda-contributor-license-agreement | ||
We require contributors to sign our [Contributor License Agreement][cla] and we don't | ||
have one on file for @${{ steps.metadata.outputs.contributor }}. | ||
In order for us to review and merge your code, please e-sign the [Contributor License Agreement PDF](https://conda.io/en/latest/contributing.html#conda-contributor-license-agreement). We then need to manually verify your signature, merge the PR (${{ steps.cla-pr.outputs.pull-request-url }}), and ping the bot to refresh the PR. | ||
In order for us to review and merge your code, please e-sign the | ||
[Contributor License Agreement PDF][cla]. We then need to manually verify your | ||
signature, merge the PR (${{ steps.pull.outputs.pull-request-url }}), and ping the bot | ||
to refresh the PR. | ||
GITHUB_TOKEN: ${{ inputs.token }} | ||
|
||
# commit status → error | ||
- name: Set commit status to error | ||
if: steps.contributors.outputs.hasSigned == 'false' | ||
uses: dholth/github-status-action@runs-using-node16 | ||
if: steps.metadata.outputs.has_signed == 'false' | ||
uses: conda/actions/set-commit-status@customize-cla-repo | ||
with: | ||
authToken: ${{ inputs.token }} | ||
token: ${{ inputs.token }} | ||
context: CLA check | ||
description: Please follow the details link to sign the conda CLA. → | ||
state: error | ||
sha: ${{ steps.pr.outputs.sha || github.sha }} | ||
target_url: https://conda.io/en/latest/contributing.html#conda-contributor-license-agreement | ||
|
||
# commit status → success | ||
- name: Set commit status to success | ||
if: steps.contributors.outputs.hasSigned == 'true' | ||
uses: dholth/github-status-action@runs-using-node16 | ||
if: steps.metadata.outputs.has_signed == 'true' | ||
uses: conda/actions/set-commit-status@customize-cla-repo | ||
with: | ||
authToken: ${{ inputs.token }} | ||
token: ${{ inputs.token }} | ||
context: CLA check | ||
description: CLA signed, thank you! | ||
state: success | ||
sha: ${{ steps.pr.outputs.sha || github.sha }} |
Oops, something went wrong.