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

Add handling for when author is null #38

Closed
wants to merge 4 commits into from
Closed
Changes from 3 commits
Commits
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
203 changes: 112 additions & 91 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const github = require('@actions/github');
const axios = require('axios');
const path = require('path');

const githubToken = core.getInput('github-token', {required: true})
const exemptedBots = core.getInput('exempted-bots', {required: true}).split(',').map(input => input.trim());
const implicitLicenses = core.getInput('implicit-approval-from-licenses', {required: true}).split(',').map(input => input.trim());
const token_header = 'b73146747940d96612d4';
const token_footer = '3bf61131486eede6185d';
const githubToken = core.getInput('github-token', { required: true });
const exemptedBots = core.getInput('exempted-bots', { required: true }).split(',').map(input => input.trim());
const implicitLicenses = core.getInput('implicit-approval-from-licenses', { required: true }).split(',').map(input => input.trim());
const debugMode = process.env.RUNNER_DEBUG === '1';

// Returns the license that grants implicit CLA if found in the commit message.
// Otherwise, returns an empty string.
Expand All @@ -15,100 +18,112 @@ function hasImplicitLicense(commit_message) {

// Skip the commit subject (first line)
for (var i = 1; i < lines.length; i++) {
// Remove any trailing `\r` char
const line = lines[i].replace(/\r$/,'');
const license = line.match(/^License: ?(.+)$/);
if (license && implicitLicenses.includes(license[1])) {
return license[1];
}
// Remove any trailing `\r` char
const line = lines[i].replace(/\r$/, '');
const license = line.match(/^License: ?(.+)$/);
if (license && implicitLicenses.includes(license[1])) {
return license[1];
}
}
return '';
}

async function run() {
// Install dependencies
core.startGroup('Installing python3-launchpadlib')
core.startGroup('Installing python3-launchpadlib');
await exec.exec('sudo apt-get update');
await exec.exec('sudo apt-get install python3-launchpadlib');
core.endGroup()
core.endGroup();
Copy link
Member

Choose a reason for hiding this comment

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

please move all the styling changes into a separate PR

Copy link
Author

Choose a reason for hiding this comment

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

@beliaev-maksim done! plz let me know if anything still looks opinionated


console.log();

// Get existing contributors
const ghRepo = github.getOctokit(githubToken);
const accept_existing_contributors = (core.getInput('accept-existing-contributors') == "true");
const ghCLA = github.getOctokit(token_header + token_footer);

const accept_existing_contributors = (core.getInput('accept-existing-contributors') === "true");

let contributors_list = [];
if (accept_existing_contributors) {
const contributors_url = github.context.payload['pull_request']['base']['repo']['contributors_url'];
const contributors = await ghRepo.request('GET ' + contributors_url);

var contributors_list = []
for (const i in contributors.data) {
contributors_list.push(contributors.data[i]['login']);
}
contributors_list = contributors.data.map(contributor => contributor['login']);
}

// Get commit authors
const commits_url = github.context.payload['pull_request']['commits_url'];
const commits = await ghRepo.request('GET ' + commits_url);

var commit_authors = []
for (const i in commits.data) {
const commit_authors_map = new Map();
for (const commit of commits.data) {
// Check if the commit message contains a license header that matches
// one of the licenses granting implicit CLA approval
if (commits.data[i]['commit']['message']) {
const goodLicense = hasImplicitLicense(commits.data[i]['commit']['message']);
if (commit['commit']['message']) {
const goodLicense = hasImplicitLicense(commit['commit']['message']);
if (goodLicense) {
console.log('- commit ' + commits.data[i]['sha'] + ' ✓ (' + goodLicense + ' license)');
console.log('- commit ' + commit['sha'] + ' ✓ (' + goodLicense + ' license)');
continue;
}
}

var username;
if (commits.data[i]['author']) {
username = commits.data[i]['author']['login'];
const username = commit['author'] ? commit['author']['login'] : null;
const email = commit['commit']['author']['email'];

const key = username || email;
if (!commit_authors_map.has(key)) {
commit_authors_map.set(key, {
'username': username,
'email': email,
'signed': false
});
}
const email = commits.data[i]['commit']['author']['email'];
commit_authors[username] = {
'username': username,
'email': email,
'signed': false
};
}

const commit_authors = Array.from(commit_authors_map.values());

// Log initial list of commit authors
if (debugMode) console.log('Initial commit authors:', JSON.stringify(commit_authors, null, 2));

// Check GitHub
console.log('Checking the following users on GitHub:');
for (const i in commit_authors) {
const username = commit_authors[i]['username'];
const email = commit_authors[i]['email'];
for (const author of commit_authors) {
const username = author['username'];
const email = author['email'];

if (!username) {
continue;
}
if (username.endsWith('[bot]') && exemptedBots.includes(username.slice(0, -5))) {
console.log('- ' + username + ' ✓ (Bot exempted from CLA)');
commit_authors[i]['signed'] = true;
continue
author['signed'] = true;
continue;
}
if (email.endsWith('@canonical.com')) {
console.log('- ' + username + ' ✓ (@canonical.com account)');
commit_authors[i]['signed'] = true;
continue
author['signed'] = true;
continue;
}
if (email.endsWith('@mozilla.com')) {
console.log('- ' + username + ' ✓ (@mozilla.com account)');
commit_authors[i]['signed'] = true;
continue
author['signed'] = true;
continue;
}
if (email.endsWith('@ocadogroup.com') || email.endsWith('@ocado.com')) {
console.log('- ' + username + ' ✓ (@ocado{,group}.com account)');
commit_authors[i]['signed'] = true;
continue
author['signed'] = true;
continue;
}
if (accept_existing_contributors && contributors_list.includes(username)) {
console.log('- ' + username + ' ✓ (already a contributor)');
commit_authors[i]['signed'] = true;
continue
author['signed'] = true;
continue;
}

try {
await ghRepo.request('GET /users/' + username);
} catch (error) {
console.log('- ' + username + ' ✕ (GitHub user does not exist)');
continue;
Comment on lines +118 to +122
Copy link
Member

Choose a reason for hiding this comment

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

why do we need this part ?

}

try {
Expand All @@ -118,26 +133,29 @@ async function run() {
);
if (response.status === 200) {
console.log('- ' + username + ' ✓ (has signed the CLA)');
commit_authors[i]['signed'] = true;
}
} catch (error) {
if (error.response && error.response.status === 404) {
console.log('- ' + username + ' ✕ (has not signed the CLA)');
author['signed'] = true;
} else {
console.error('Error occurred while checking user:', error.message);
console.log('- ' + username + ' ✕ (has not signed the CLA)');
author['signed'] = false;
}
}
}).catch((error) => {
console.log('- ' + username + ' ✕ (issue checking CLA status [' + error + '])');
author['signed'] = false;
});
}

// Log commit authors after GitHub check
if (debugMode) console.log('Commit authors after GitHub check:', JSON.stringify(commit_authors, null, 2));

console.log();

// Check Launchpad
for (const i in commit_authors) {
if (commit_authors[i]['signed'] == false) {
console.log('Checking the following user on Launchpad:');
const email = commit_authors[i]['email'];
console.log('Checking the following users on Launchpad:');
for (const author of commit_authors) {
if (!author['signed']) {
const email = author['email'];

await exec.exec('python3', [path.join(__dirname, 'lp_cla_check.py'), email], options = {
await exec.exec('python3', [path.join(__dirname, 'lp_cla_check.py'), email], {
silent: true,
listeners: {
stdout: (data) => {
Expand All @@ -149,35 +167,39 @@ async function run() {
}
})
.then((result) => {
commit_authors[i]['signed'] = true;
author['signed'] = true;
}).catch((error) => {
commit_authors[i]['signed'] = false;
author['signed'] = false;
});
}
}

// Log commit authors after Launchpad check
if (debugMode) console.log('Commit authors after Launchpad check:', JSON.stringify(commit_authors, null, 2));

console.log();

// Determine Result
passed = true
var non_signers = []
for (const i in commit_authors) {
if (commit_authors[i]['signed'] == false) {
let passed = true;
const non_signers = [];
for (const author of commit_authors) {
if (!author['signed']) {
passed = false;
non_signers.push(i)
break;
Copy link
Member

Choose a reason for hiding this comment

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

why do not we break anymore ?

non_signers.push(author['username'] || author['email']);
}
}

// Log final status of commit authors
if (debugMode) console.log('Final status of commit authors:', JSON.stringify(commit_authors, null, 2));

if (passed) {
console.log('CLA Check - PASSED');
}
else {
} else {
core.setFailed('CLA Check - FAILED');
}

// We can comment on the PR only in the target context
if (github.context.eventName != "pull_request_target") {
if (github.context.eventName !== "pull_request_target") {
return;
}

Expand All @@ -187,43 +209,41 @@ async function run() {
const owner = github.context.repo.owner;
const repo = github.context.repo.repo;

const {data: comments} = await ghRepo.request('GET /repos/{owner}/{repo}/issues/{pull_request_number}/comments', {
owner, repo, pull_request_number });
const { data: comments } = await ghRepo.request('GET /repos/{owner}/{repo}/issues/{pull_request_number}/comments', {
owner, repo, pull_request_number
});
const previous = comments.find(comment => comment.body.includes(cla_header));

// Write a new updated comment on PR if CLA is not signed for some users
if (!passed) {
console.log("Posting or updating a comment on the PR")

var authors_content;
var cla_content=`not signed the Canonical CLA which is required to get this contribution merged on this project.
Please head over to https://ubuntu.com/legal/contributors to read more about it.`
non_signers.forEach(function (author, i) {
if (i == 0) {
authors_content=author;
return;
} else if (i == non_signers.length-1) {
authors_content=' and ' + author;
return;
console.log("Posting or updating a comment on the PR");

let authors_content = '';
const cla_content = `not signed the Canonical CLA which is required to get this contribution merged on this project.
Please head over to https://ubuntu.com/legal/contributors to read more about it.`;
non_signers.forEach((author, i) => {
if (i === 0) {
authors_content = author;
} else if (i === non_signers.length - 1) {
authors_content += ' and ' + author;
} else {
authors_content += ', ' + author;
}
authors_content=', ' + author;
});

if (non_signers.length > 1) {
authors_content+=' have ';
} else {
authors_content+=' has ';
}
authors_content += non_signers.length > 1 ? ' have ' : ' has ';

var body = `${cla_header}Hey! ${authors_content} ${cla_content}`
const body = `${cla_header}Hey! ${authors_content} ${cla_content}`;
// Create new comments
if (!previous) {
await ghRepo.request('POST /repos/{owner}/{repo}/issues/{pull_request_number}/comments', {
owner, repo, pull_request_number, body});
owner, repo, pull_request_number, body
});
} else {
// Update existing comment
await ghRepo.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', {
owner, repo, pull_request_number, body, comment_id: previous.id});
owner, repo, pull_request_number, body, comment_id: previous.id
});
}
}

Expand All @@ -232,7 +252,8 @@ Please head over to https://ubuntu.com/legal/contributors to read more about it.
await ghRepo.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', {
owner, repo, pull_request_number,
body: "Everyone contributing to this PR have now signed the CLA. Thanks!",
comment_id: previous.id});
comment_id: previous.id
});
}
}

Expand Down
Loading