Skip to content

Commit 2cc1344

Browse files
authored
Merge pull request #2 from psdhanesh7/add-cla-checks
Add CLA checks
2 parents c46a889 + 9b67eaf commit 2cc1344

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed

.github/workflows/comment_pr.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Comment on pull request post pull request auto review
2+
# depends on and only executes after pull_request_processor.yml is complete
3+
4+
on:
5+
workflow_run:
6+
workflows: ["Checking CLA Signature"]
7+
types:
8+
- completed
9+
10+
jobs:
11+
one:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: [3.8]
16+
if: >
17+
${{ github.event.workflow_run.event == 'pull_request' &&
18+
github.event.workflow_run.conclusion == 'success' }}
19+
steps:
20+
- name: Print github contexts
21+
env:
22+
GITHUB_CONTEXT: ${{ toJSON(github) }}
23+
run: |
24+
ls
25+
echo "GH context:"
26+
echo "$GITHUB_CONTEXT"
27+
- name: 'Download artifact'
28+
uses: actions/github-script@v3.1.0
29+
with:
30+
script: |
31+
var artifacts = await github.actions.listWorkflowRunArtifacts({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
run_id: ${{github.event.workflow_run.id }},
35+
});
36+
console.log(artifacts);
37+
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
38+
return artifact.name == "prcontext"
39+
})[0];
40+
var download = await github.actions.downloadArtifact({
41+
owner: context.repo.owner,
42+
repo: context.repo.repo,
43+
artifact_id: matchArtifact.id,
44+
archive_format: 'zip',
45+
});
46+
var fs = require('fs');
47+
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
48+
- run: |
49+
ls
50+
unzip pr.zip
51+
cat ./comment
52+
53+
- name: 'Comment on Current Pull Request'
54+
uses: actions/github-script@v3
55+
with:
56+
github-token: ${{ secrets.COMMENT_BOT_TOKEN }}
57+
script: |
58+
var fs = require('fs');
59+
var issue_number = Number(fs.readFileSync('./PRNumber'));
60+
var comment = String(fs.readFileSync('./comment'));
61+
await github.issues.createComment({
62+
owner: context.repo.owner,
63+
repo: context.repo.repo,
64+
issue_number: issue_number,
65+
body: comment
66+
});
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import os
2+
import sys
3+
import requests
4+
import json
5+
import subprocess
6+
import re
7+
8+
9+
print("current working directory is: ", os.getcwd())
10+
STATUS_FAILED = 'FAILED'
11+
SUCCESS_MESSAGE = 'ok'
12+
13+
14+
def get_github_details():
15+
github_info_file = open('./.tmp/github.json', 'r')
16+
return json.load(github_info_file)
17+
18+
19+
def get_commit_details():
20+
commit_info_file = open('./.tmp/commitDetails.json', 'r')
21+
return json.load(commit_info_file)
22+
23+
24+
def process_git_local_details():
25+
# Check if current dir is git dir
26+
is_git_dir = subprocess.check_output(
27+
['/usr/bin/git', 'rev-parse', '--is-inside-work-tree']).decode('utf-8')
28+
print("Is git dir: ", is_git_dir)
29+
30+
# git status
31+
git_status = subprocess.check_output(
32+
['/usr/bin/git', 'status']).decode('utf-8')
33+
print("Git status: ", git_status)
34+
35+
# last n commits
36+
last_10_commit_list = subprocess.check_output(
37+
['/usr/bin/git', 'rev-list', '--max-count=10', 'HEAD']).decode('utf-8')
38+
print("last 10 commit ids are: ", last_10_commit_list)
39+
40+
return {
41+
'is_git_dir': is_git_dir,
42+
'last_10_commit_list': last_10_commit_list
43+
}
44+
45+
46+
def collect_pr_details():
47+
github = get_github_details()
48+
commits = get_commit_details()
49+
git_local = process_git_local_details()
50+
return {
51+
'github': github,
52+
'commits': commits,
53+
'num_commits_in_pr': len(commits),
54+
'event_name': github["event_name"],
55+
'pr_submitter_github_login': github['event']['pull_request']['user']['login'],
56+
'github_repo': github['repository'],
57+
'pr_number': github['event']['number'],
58+
'is_git_dir': git_local['is_git_dir'],
59+
'last_10_commit_list': git_local['last_10_commit_list'],
60+
}
61+
62+
63+
def write_comment(comment):
64+
print(comment)
65+
f = open("./.tmp/comment", "a")
66+
f.write(comment)
67+
f.write("\n")
68+
f.close()
69+
70+
71+
def task_failed(comment):
72+
f = open("./.tmp/failed", "a")
73+
f.write(comment)
74+
f.write("\n")
75+
f.close()
76+
write_comment(comment)
77+
return STATUS_FAILED
78+
79+
80+
def extract_personal_contributer_details():
81+
personal_cla_link = sys.argv[1]
82+
f = requests.get(personal_cla_link)
83+
personal_cla_contents = f.text
84+
85+
personal_contributers_regex = re.compile('\| *\[([^\s]+)\]\([^\s]+\) *\|')
86+
personal_contributers = personal_contributers_regex.findall(
87+
personal_cla_contents)
88+
89+
return personal_contributers
90+
91+
92+
def extract_employer_contributer_details():
93+
employer_cla_link = sys.argv[2]
94+
f = requests.get(employer_cla_link)
95+
employer_cla_contents = f.text
96+
97+
employer_contributers_regex = re.compile('\| *\[([^\s]+)\]\([^\s]+\) *\|')
98+
employer_contributers = employer_contributers_regex.findall(
99+
employer_cla_contents)
100+
101+
return employer_contributers
102+
103+
104+
def validate_is_pull_request(pr_details):
105+
print('Validate pull request called')
106+
github_details = pr_details['github']
107+
if github_details["event_name"] != "pull_request":
108+
print("Error! This operation is valid on github pull requests. Exiting. Event received: ",
109+
github_details["event_name"])
110+
sys.exit(1)
111+
112+
113+
def validate_pr_raiser_cla(pr_raiser_login, employer_contributors, personal_contributors):
114+
print('PR raiser login: ' + pr_raiser_login)
115+
if pr_raiser_login not in employer_contributors and pr_raiser_login not in personal_contributors:
116+
return task_failed('### Error: Contributor Licence Agreement Signature Missing\n' +
117+
'Please sign the Contributor Licence Agreement by clicking the following link.\n' +
118+
'<p align="center"> <a href="https://phcode-dev.github.io/contributor-license-agreement/">Click here to sign the CLA</a></p>'
119+
)
120+
print('Pass: Pull request raiser has signed the Contributor Licence Agreement')
121+
return SUCCESS_MESSAGE
122+
123+
124+
def validate_commiters_cla(commits, employer_contributors, personal_contributors):
125+
# github logins of all committers
126+
commit_logins = []
127+
for commit in commits:
128+
commiter_github_login = commit['author']['login']
129+
if commiter_github_login not in commit_logins:
130+
commit_logins.append(commiter_github_login)
131+
print("All github users who made changes to the pull request: ", commit_logins)
132+
133+
unauthorized_commiters = []
134+
for user in commit_logins:
135+
if user not in personal_contributors and user not in employer_contributors:
136+
unauthorized_commiters.append(user)
137+
if len(unauthorized_commiters) != 0:
138+
return task_failed('### Error: Contributor Licence Agreement Signature Missing\n' +
139+
'The following commiter(s) has not signed the Contributor Licence Agreement:\n' +
140+
', '.join(unauthorized_commiters) + '\n' +
141+
'Please sign the Contributor Licence Agreement by clicking the following link. \n' +
142+
'<p align="center"> <a href="https://phcode-dev.github.io/contributor-license-agreement/">Click here to sign the CLA</a></p>'
143+
)
144+
145+
print('Pass: All the commiters have signed the Contributor Licence Agreement')
146+
return SUCCESS_MESSAGE
147+
148+
149+
def validate_cla_signature(pr_raiser_login, commits):
150+
employer_contributors = extract_employer_contributer_details()
151+
personal_contributors = extract_personal_contributer_details()
152+
153+
PR_RAISER_CLA_VALIDATION = validate_pr_raiser_cla(pr_raiser_login, employer_contributors, personal_contributors)
154+
COMMITERS_CLA_VALIDATION = validate_commiters_cla(commits, employer_contributors, personal_contributors)
155+
156+
if PR_RAISER_CLA_VALIDATION == STATUS_FAILED or COMMITERS_CLA_VALIDATION == STATUS_FAILED:
157+
return STATUS_FAILED
158+
159+
return SUCCESS_MESSAGE
160+
161+
162+
def review_pr():
163+
print('Reviewing PR')
164+
pr_details = collect_pr_details()
165+
validate_is_pull_request(pr_details)
166+
CLA_SIGNATURE_VALIDATION = validate_cla_signature(pr_details['pr_submitter_github_login'], pr_details['commits'])
167+
168+
if CLA_SIGNATURE_VALIDATION == STATUS_FAILED:
169+
print('Validations failed. Exiting!')
170+
return
171+
172+
write_comment('\n## Thank You for making this pull request.')
173+
174+
175+
review_pr()
176+
177+
# assert validate_cla_signature('psdhanesh7') == SUCCESS_MESSAGE
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Checking CLA Signature
2+
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#github-context
3+
# The pull request target event provides RW token to github
4+
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/
5+
# But `on: pull_request_target` should be avoided due to security
6+
# reasons. Read more: [SEC_ADV_1] https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
7+
#
8+
# We will use a mix of github pull_request that does not provide any write access to pull requests on forks
9+
# and workflow event, as discussed in [SEC_ADV_1]
10+
11+
on:
12+
pull_request:
13+
branches:
14+
- main
15+
16+
jobs:
17+
one:
18+
runs-on: ubuntu-latest
19+
strategy:
20+
matrix:
21+
python-version: [3.8]
22+
23+
steps:
24+
- uses: actions/checkout@v2
25+
with:
26+
fetch-depth: 100
27+
- name: Set up Python ${{ matrix.python-version }}
28+
uses: actions/setup-python@v2
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
- name: Setting up prerequisites
32+
run: |
33+
mkdir ./.tmp
34+
pip3 install requests
35+
- name: Getting commit details
36+
uses: wei/wget@v1
37+
with:
38+
args: -O ./.tmp/commitDetails.json ${{ toJSON(github.event.pull_request._links.commits.href) }}
39+
- name: Dump GitHub context
40+
env:
41+
GITHUB_CONTEXT: ${{ toJSON(github) }}
42+
run: |
43+
echo "$GITHUB_CONTEXT" > ./.tmp/github.json
44+
echo ${{ github.event.number }} > ./.tmp/PRNumber
45+
cat ./.tmp/github.json
46+
echo "commit details: "
47+
cat ./.tmp/commitDetails.json
48+
49+
- name: Review pull request
50+
run: |
51+
which git
52+
if ! python ./.github/workflows/python/verify_cla_signature_pr.py $PERSONAL_CLA_LINK $EMPLOYER_CLA_LINK; then
53+
echo "Pull request details could not be extracted"
54+
exit 1
55+
else
56+
echo "all good"
57+
fi
58+
env:
59+
EMPLOYER_CLA_LINK: https://raw.githubusercontent.com/phcode-dev/contributor-license-agreement/main/employer_contributor_license_agreement.md
60+
PERSONAL_CLA_LINK: https://raw.githubusercontent.com/phcode-dev/contributor-license-agreement/main/personal_contributor_licence_agreement.md
61+
62+
- uses: actions/upload-artifact@v2
63+
with:
64+
name: prcontext
65+
path: .tmp/
66+
67+
- name: Fail on validation errors
68+
run: |
69+
FILE=./.tmp/failed
70+
if test -f "$FILE"; then
71+
echo "Validation failed. Reason:"
72+
cat ./.tmp/failed
73+
exit 1
74+
fi

0 commit comments

Comments
 (0)