Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update check-cla to customize which CLA repo to use #91

Merged
merged 30 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ee9d811
Update check-cla to customize which CLA repo
kenodegard Apr 3, 2023
ec9f0b4
Adds set-commit-status action
kenodegard Apr 4, 2023
a3dded4
Update READMEs and descriptions
kenodegard Apr 4, 2023
6c83ecc
Use JavaScript, not TypeScript
kenodegard Apr 4, 2023
2b2d732
Don't need to authenticate Octokit object
kenodegard Apr 4, 2023
5361102
Remove redundant debug statements
kenodegard Apr 4, 2023
528c212
Dumb JS split
kenodegard Apr 4, 2023
83dab52
Flatten await and keep try-catch simple
kenodegard Apr 4, 2023
b3b9940
Set author/committer and delete branch on merge
kenodegard Apr 4, 2023
1542cb4
debugging
kenodegard Apr 4, 2023
261fc09
Remove unused sha and labels
kenodegard Apr 5, 2023
1928626
Update ids
kenodegard Apr 5, 2023
00ae91f
Allow custom author/committer
kenodegard Apr 5, 2023
8eee82e
Fix json formatting, update PR url
kenodegard Apr 5, 2023
4729d17
Fix inputs
kenodegard Apr 5, 2023
b2db323
Fallback sha
kenodegard Apr 5, 2023
9bdcc51
debugging comment payload
kenodegard Apr 5, 2023
43ec963
Remove temporary raw const
kenodegard Apr 5, 2023
d8454ec
Optional react to comment
kenodegard Apr 5, 2023
8ee0ba0
Add token to reaction
kenodegard Apr 5, 2023
5ab5369
Replace rocket with eyes
kenodegard Apr 5, 2023
ebb06e5
Reduce noise
kenodegard Apr 5, 2023
a4ac617
Include classic PAT instructions
kenodegard Apr 5, 2023
4f67ffb
GFM
kenodegard Apr 5, 2023
d66d4eb
Extract PR number for sticky comment
kenodegard Apr 5, 2023
71c4f55
Support both PRs and "issues"
kenodegard Apr 5, 2023
6146699
Extract metadata whether triggered as a PR or "issue" comment
kenodegard Apr 5, 2023
5616a98
Group metadata steps
kenodegard Apr 5, 2023
3c9f741
Update comments and step names
kenodegard Apr 5, 2023
ae8e19e
Use correct url
kenodegard Apr 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions check-cla/README.md
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
jezdez marked this conversation as resolved.
Show resolved Hide resolved

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:
```
237 changes: 136 additions & 101 deletions check-cla/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add this to a separate javascript file? This is started to get long enough to warrant that, imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying these JS blocks in separate files requires:

steps:
  - uses: actions/checkout@v3
  - uses: actions/github-script@v6
    with:
      script: |
        const script = require('./path/to/script.js')
        await script({github, context, core})

My problem with this is the checkout step which will make it harder to version our actions.

There's an open request for something better: actions/github-script#326

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a common pattern for actions to include all JS inlined, even if it's not great, let's follow that for now

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}`);
Comment on lines +53 to +99
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved sha detection into new set-commit-status action. Removed labels output since we don't use it outside of this scope. Switch hasLabel to has_label for variable consistency.

Flattened logic since the error catching doesn't actually do anything special. Removes contributors output since we don't use it outside of this scope. Switch hasSigned to has_signed for variable naming consistency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good changes!


# 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.
Comment on lines +165 to +176
Copy link
Contributor Author

@kenodegard kenodegard Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kathatherine this is the updated missing CLA message, is this good enough in light of conda/conda-docs#854

To see an example of this in action see conda-sandbox/test-check-cla#45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully the PR link will help with anyone who just reads to the CLA signing link and stops (like what happened with some of the PyCon sprinters I was working with). Maybe two links will encourage finishing the paragraph. :P This works for me.

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 }}
Loading