diff --git a/.gitignore b/.gitignore index 3c3629e..3ea76c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +.serverless +serverless.yml \ No newline at end of file diff --git a/package.json b/package.json index 7303b78..93ed7d3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "lint": "eslint \"src/*.js\"", "prettier:fix": "prettier --write \"{src,spec}/**/*.js\"", "serverless": "serverless", - "serverless-setup-credentials": "serverless config credentials --provider aws" + "serverless-setup-credentials": "serverless config credentials --provider aws", + "deploy": "serverless deploy" }, "author": "", "license": "Apache-2.0" diff --git a/src/committerFinder.js b/src/committerFinder.js index 5e54b94..f767ab7 100644 --- a/src/committerFinder.js +++ b/src/committerFinder.js @@ -7,13 +7,27 @@ const getUniqueCommitters = arr => self.findIndex(c => c.email == value.email) === index ); +const getUserPayload = userData => { + let ghId = undefined; + if (userData.length > 0) { + if (userData[0].identities) { + ghIdentity = userData[0].identities.filter(identity => identity.provider == 'github'); + if (ghIdentity.length > 0) { + ghId = ghIdentity[0].extern_uid; + } + } + return { + ...userData[0], + login: userData[0].username, + gitHubId: ghId + } + } else return undefined; +} + const hydrateGitlabUserInfo = async (usersToVerify, token) => Promise.all( usersToVerify.map(user => - gitlabRequest(getUserInfo(user.email), token).then(response => ({ - ...user, - login: response.length > 0 ? response[0].username : undefined - })) + gitlabRequest(getUserInfo(user.email), token).then(response => getUserPayload(response)) ) ); diff --git a/src/contributionVerifier.js b/src/contributionVerifier.js index bc060db..1d3566a 100644 --- a/src/contributionVerifier.js +++ b/src/contributionVerifier.js @@ -14,7 +14,7 @@ function partition(array, isValid) { const domainFromEmail = email => "@" + email.split("@")[1]; -const contributorArrayVerifier = contributors => committers => { +const contributorArrayVerifier = (contributors, useGitHubIDs) => committers => { const lowerCaseContributors = contributors.map(c => c.toLowerCase()); const [ emailVerification, @@ -36,8 +36,14 @@ const contributorArrayVerifier = contributors => committers => { return true; } } - if (usernameVerification.includes(c.login.toLowerCase())) { - return true; + if (useGitHubIDs) { + if (usernameVerification.includes(c.gitHubId)) { + return true; + } + } else { + if (usernameVerification.includes(c.login.toLowerCase())) { + return true; + } } return false; }; @@ -50,7 +56,7 @@ const configFileFromUrlVerifier = contributorListUrl => committers => requestp({ url: contributorListUrl, json: true - }).then(contributors => contributorArrayVerifier(contributors)(committers)); + }).then(contributors => contributorArrayVerifier(contributors, false)(committers)); const webhookVerifier = webhookUrl => committers => Promise.all( @@ -67,7 +73,7 @@ const webhookVerifier = webhookUrl => committers => const contributors = responses .filter(r => r.isContributor) .map(r => r.username); - return contributorArrayVerifier(contributors)(committers); + return contributorArrayVerifier(contributors, false)(committers); }); module.exports = config => { @@ -78,7 +84,7 @@ module.exports = config => { logger.info( "Checking contributors against the list supplied in the .clabot file" ); - return contributorArrayVerifier(configCopy.contributors); + return contributorArrayVerifier(configCopy.contributors, configCopy.useGitHubIDs); } else if ( is.url(configCopy.contributors) && configCopy.contributors.indexOf("?") !== -1 diff --git a/src/gitlabApi.js b/src/gitlabApi.js index 048277c..581a0e1 100644 --- a/src/gitlabApi.js +++ b/src/gitlabApi.js @@ -28,6 +28,18 @@ exports.getUserInfo = emailAddress => ({ method: "GET" }); +exports.searchNamespaceByName = nsName => ({ + // This assumes master branch, might need to rework this at some point + url: `https://${process.env.GITLAB_INSTANCE_DOMAIN}/api/v4/namespaces?search=${nsName}`, + method: "GET" +}); + +exports.getOrgConfigProject = nsId => ({ + // This assumes master branch, might need to rework this at some point + url: `https://${process.env.GITLAB_INSTANCE_DOMAIN}/api/v4/groups/${nsId}/search?scope=projects&search=clabot-config`, + method: "GET" +}); + exports.getProjectClaFile = projectId => ({ // This assumes master branch, might need to rework this at some point url: `https://${process.env.GITLAB_INSTANCE_DOMAIN}/api/v4/projects/${projectId}/repository/files/%2Eclabot/raw?ref=master`, diff --git a/src/index.js b/src/index.js index 9b049ce..4835115 100644 --- a/src/index.js +++ b/src/index.js @@ -36,11 +36,13 @@ const gitLabInfo = webhook => webhook.object_kind === "note" ? { projectId: webhook.project.id, + namespace: webhook.project.path_with_namespace.split("/")[0], mergeRequestId: webhook.merge_request.iid, projectUrl: webhook.project.web_url } : { projectId: webhook.project.id, + namespace: webhook.project.path_with_namespace.split("/")[0], mergeRequestId: webhook.object_attributes.iid, projectUrl: webhook.project.web_url }; @@ -87,12 +89,14 @@ exports.Handler = constructHandler(async webhook => { logger.info("The cla-bot has been summoned by a comment"); } - + const token = obtainToken(webhook); const { addComment, getMergeRequest, getProjectClaFile, + searchNamespaceByName, + getOrgConfigProject, setCommitStatus, getProjectLabels, createProjectLabel, @@ -101,6 +105,7 @@ exports.Handler = constructHandler(async webhook => { const { projectId: projectId, + namespace: namespace, mergeRequestId: mergeRequestId, projectUrl: projectUrl } = gitLabInfo(webhook); @@ -115,8 +120,22 @@ exports.Handler = constructHandler(async webhook => { const MRInfo = await getMergeRequest(projectId, mergeRequestId); const headSha = MRInfo.sha; - // TODO : Investigate group level .clabot file. - let claConfig = await getProjectClaFile(projectId); + // Try to load project clabot-config, from + // the same project namespace (aka GitLab Group) + let ns = await searchNamespaceByName(namespace); + let nsId = ns[0].id; + logger.info(`Namespace is ${namespace}, ID ${nsId}`); + let orgConfigProject = await getOrgConfigProject(nsId); + let claConfig = null; + if (orgConfigProject) { + logger.info(`Found clabot-config project, ID is ${orgConfigProject[0].id}`); + claConfig = await getProjectClaFile(orgConfigProject[0].id); + } + + if (!is.json(claConfig)) { + claConfig = await getProjectClaFile(projectId); + } + if (!is.json(claConfig)) { logger.error("The .clabot file is not valid JSON"); await setCommitStatus(projectId, headSha, "failed", botName);