forked from eduardoboucas/staticman
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from hispanic/email-notifs-with-gitlab
Allow for email notifications with GitLab
- Loading branch information
Showing
8 changed files
with
673 additions
and
112 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
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,61 +1,128 @@ | ||
'use strict' | ||
|
||
const config = require('../config') | ||
const GitHub = require('../lib/GitHub') | ||
const gitFactory = require('../lib/GitServiceFactory') | ||
const Staticman = require('../lib/Staticman') | ||
|
||
module.exports = async (repo, data) => { | ||
const ua = config.get('analytics.uaTrackingId') | ||
? require('universal-analytics')(config.get('analytics.uaTrackingId')) | ||
: null | ||
|
||
if (!data.number) { | ||
return | ||
/* | ||
* Unfortunately, all we have available to us at this point is the request body (as opposed to | ||
* the full request). Meaning, we don't have the :service portion of the request URL available. | ||
* As such, switch between GitHub and GitLab using the repo URL. For example: | ||
* "url": "https://api.github.com/repos/foo/staticman-test" | ||
* "url": "git@gitlab.com:foo/staticman-test.git" | ||
*/ | ||
const calcIsGitHub = function (data) { | ||
return data.repository.url.includes('github.com') | ||
} | ||
const calcIsGitLab = function (data) { | ||
return data.repository.url.includes('gitlab.com') | ||
} | ||
|
||
/* | ||
* Because we don't have the full request available to us here, we can't set the (Staticman) | ||
* version option. Fortunately, it isn't critical. | ||
*/ | ||
const unknownVersion = '' | ||
|
||
const github = await new GitHub({ | ||
username: data.repository.owner.login, | ||
repository: data.repository.name, | ||
version: '1' | ||
let gitService = null | ||
let mergeReqNbr = null | ||
if (calcIsGitHub(data)) { | ||
gitService = await gitFactory.create('github', { | ||
branch: data.pull_request.base.ref, | ||
repository: data.repository.name, | ||
username: data.repository.owner.login, | ||
version: unknownVersion | ||
}) | ||
mergeReqNbr = data.number | ||
} else if (calcIsGitLab(data)) { | ||
const repoUrl = data.repository.url | ||
const repoUsername = repoUrl.substring(repoUrl.indexOf(':') + 1, repoUrl.indexOf('/')) | ||
gitService = await gitFactory.create('gitlab', { | ||
branch: data.object_attributes.target_branch, | ||
repository: data.repository.name, | ||
username: repoUsername, | ||
version: unknownVersion | ||
}) | ||
mergeReqNbr = data.object_attributes.iid | ||
} else { | ||
return Promise.reject(new Error('Unable to determine service.')) | ||
} | ||
|
||
if (!mergeReqNbr) { | ||
return Promise.reject(new Error('No pull/merge request number found.')) | ||
} | ||
|
||
let review = await gitService.getReview(mergeReqNbr).catch((error) => { | ||
return Promise.reject(new Error(error)) | ||
}) | ||
|
||
try { | ||
let review = await github.getReview(data.number) | ||
if (review.sourceBranch.indexOf('staticman_')) { | ||
return null | ||
} | ||
if (review.sourceBranch.indexOf('staticman_') < 0) { | ||
/* | ||
* Don't throw an error here, as we might receive "real" (non-bot) pull requests for files | ||
* other than Staticman-processed comments. | ||
*/ | ||
return null | ||
} | ||
|
||
if (review.state !== 'merged' && review.state !== 'closed') { | ||
return null | ||
} | ||
if (review.state !== 'merged' && review.state !== 'closed') { | ||
/* | ||
* Don't throw an error here, as we'll regularly receive webhook calls whenever a pull/merge | ||
* request is opened, not just merged/closed. | ||
*/ | ||
return null | ||
} | ||
|
||
if (review.state === 'merged') { | ||
/* | ||
* The "staticman_notification" comment section of the comment pull/merge request only | ||
* exists if notifications were enabled at the time the pull/merge request was created. | ||
*/ | ||
const bodyMatch = review.body.match(/(?:.*?)<!--staticman_notification:(.+?)-->(?:.*?)/i) | ||
|
||
if (review.state === 'merged') { | ||
const bodyMatch = review.body.match(/(?:.*?)<!--staticman_notification:(.+?)-->(?:.*?)/i) | ||
if (bodyMatch && (bodyMatch.length === 2)) { | ||
try { | ||
const parsedBody = JSON.parse(bodyMatch[1]) | ||
const staticman = await new Staticman(parsedBody.parameters) | ||
|
||
if (bodyMatch && (bodyMatch.length === 2)) { | ||
try { | ||
const parsedBody = JSON.parse(bodyMatch[1]) | ||
const staticman = await new Staticman(parsedBody.parameters) | ||
staticman.setConfigPath(parsedBody.configPath) | ||
|
||
staticman.setConfigPath(parsedBody.configPath) | ||
staticman.processMerge(parsedBody.fields, parsedBody.options) | ||
} catch (err) { | ||
return Promise.reject(err) | ||
await staticman.processMerge(parsedBody.fields, parsedBody.options) | ||
if (ua) { | ||
ua.event('Hooks', 'Create/notify mailing list').send() | ||
} | ||
} catch (err) { | ||
if (ua) { | ||
ua.event('Hooks', 'Create/notify mailing list error').send() | ||
} | ||
|
||
return Promise.reject(err) | ||
} | ||
} | ||
|
||
if (ua) { | ||
ua.event('Hooks', 'Delete branch').send() | ||
} | ||
return github.deleteBranch(review.sourceBranch) | ||
} catch (e) { | ||
console.log(e.stack || e) | ||
/* | ||
* Only necessary for GitHub, as GitLab automatically deletes the backing branch for the | ||
* pull/merge request. For GitHub, this will throw the following error if the branch has | ||
* already been deleted: | ||
* HttpError: Reference does not exist" | ||
*/ | ||
if (calcIsGitHub(data)) { | ||
try { | ||
await gitService.deleteBranch(review.sourceBranch) | ||
if (ua) { | ||
ua.event('Hooks', 'Delete branch').send() | ||
} | ||
} catch (err) { | ||
if (ua) { | ||
ua.event('Hooks', 'Delete branch error').send() | ||
} | ||
|
||
if (ua) { | ||
ua.event('Hooks', 'Delete branch error').send() | ||
return Promise.reject(err) | ||
} | ||
} | ||
|
||
return Promise.reject(e) | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,64 @@ | ||
'use strict' | ||
|
||
const path = require('path') | ||
const config = require(path.join(__dirname, '/../config')) | ||
const handlePR = require('./handlePR') | ||
|
||
module.exports = async (req, res, next) => { | ||
switch (req.params.service) { | ||
case 'gitlab': | ||
let errorMsg = null | ||
let event = req.headers['x-gitlab-event'] | ||
|
||
if (!event) { | ||
errorMsg = 'No event found in the request' | ||
} else { | ||
if (event === 'Merge Request Hook') { | ||
const webhookSecretExpected = config.get('gitlabWebhookSecret') | ||
const webhookSecretSent = req.headers['x-gitlab-token'] | ||
|
||
let reqAuthenticated = true | ||
if (webhookSecretExpected) { | ||
reqAuthenticated = false | ||
if (!webhookSecretSent) { | ||
errorMsg = 'No secret found in the webhook request' | ||
} else if (webhookSecretExpected === webhookSecretSent) { | ||
/* | ||
* Whereas GitHub uses the webhook secret to sign the request body, GitLab does not. | ||
* As such, just check that the received secret equals the expected value. | ||
*/ | ||
reqAuthenticated = true | ||
} else { | ||
errorMsg = 'Unable to verify authenticity of request' | ||
} | ||
} | ||
|
||
if (reqAuthenticated) { | ||
await handlePR(req.params.repository, req.body).catch((error) => { | ||
console.error(error.stack || error) | ||
errorMsg = error.message | ||
}) | ||
} | ||
} | ||
} | ||
|
||
if (errorMsg !== null) { | ||
res.status(400).send({ | ||
error: errorMsg | ||
}) | ||
} else { | ||
res.status(200).send({ | ||
success: true | ||
}) | ||
} | ||
|
||
break | ||
default: | ||
res.status(400).send({ | ||
/* | ||
* We are expecting GitHub webhooks to be handled by the express-github-webhook module. | ||
*/ | ||
error: 'Unexpected service specified.' | ||
}) | ||
} | ||
} |
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
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
Oops, something went wrong.