diff --git a/README.md b/README.md
index c0a14621..a9a7631c 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,17 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
The webhook secret that GitHub signs the POSTed payloads with. This is created when the webhook is defined. The default is `hush-hush`.
- **`TRAVIS_CI_TOKEN`**
For scripts that communicate with Travis CI. Your Travis token is visible on [yourprofile](https://travis-ci.org/profile) page, by clicking the "show token" link. Also See: https://blog.travis-ci.com/2013-01-28-token-token-token
+- **`JENKINS_API_CREDENTIALS`** (optional)
+ For scripts that communicate with Jenkins on http://ci.nodejs.org. The Jenkins API token is visible on
+ your own profile page `https://ci.nodejs.org/user//configure`, by clicking the
+ "show API token" button. Also See: https://wiki.jenkins-ci.org/display/JENKINS/Authenticating+scripted+clients
+- **`JENKINS_JOB_URL_`** (optional)
+ Only required for the trigger Jenkins build script, to know which job to trigger a build for when
+ repository collaborator posts a comment to the bot. E.g. `JENKINS_JOB_URL_NODE=https://ci.nodejs.org/job/node-test-pull-request`
+- **`JENKINS_BUILD_TOKEN_`** (optional)
+ Only required for the trigger Jenkins build script. The authentication token configured for a particular
+ Jenkins job, for remote scripts to trigger builds remotely. Found on the job configuration page in
+ `Build Triggers -> Trigger builds remotely (e.g., from scripts)`.
- **`LOGIN_CREDENTIALS`**
Username and password used to protected the log files exposed in /logs. Expected format: `username:password`.
- **`KEEP_LOGS`**
diff --git a/lib/bot-username.js b/lib/bot-username.js
new file mode 100644
index 00000000..fa726e75
--- /dev/null
+++ b/lib/bot-username.js
@@ -0,0 +1,14 @@
+const memoize = require('async').memoize
+
+const githubClient = require('./github-client')
+
+function requestGitHubForUsername (cb) {
+ githubClient.users.get({}, (err, currentUser) => {
+ if (err) {
+ return cb(err)
+ }
+ cb(null, currentUser.login)
+ })
+}
+
+exports.resolve = memoize(requestGitHubForUsername)
diff --git a/lib/github-events.js b/lib/github-events.js
index 79920a4d..b4bd6b9b 100644
--- a/lib/github-events.js
+++ b/lib/github-events.js
@@ -27,6 +27,14 @@ module.exports = (app) => {
app.emitGhEvent = function emitGhEvent (data, logger) {
const repo = data.repository.name
const org = data.repository.owner.login || data.organization.login
+
+ // Normalize how to fetch the PR / issue number for simpler retrieval in the
+ // rest of the bot's code. For PRs the number is present in data.number,
+ // but for webhook events raised for comments it's present in data.issue.number
+ if (!data.number && data.issue) {
+ data.number = data.issue.number
+ }
+
const pr = data.number
// create unique logger which is easily traceable throughout the entire app
diff --git a/scripts/trigger-jenkins-build.js b/scripts/trigger-jenkins-build.js
new file mode 100644
index 00000000..80a53eea
--- /dev/null
+++ b/scripts/trigger-jenkins-build.js
@@ -0,0 +1,127 @@
+'use strict'
+
+const request = require('request')
+
+const githubClient = require('../lib/github-client')
+const botUsername = require('../lib/bot-username')
+
+const jenkinsApiCredentials = process.env.JENKINS_API_CREDENTIALS || ''
+
+function ifBotWasMentionedInCiComment (commentBody, cb) {
+ botUsername.resolve((err, username) => {
+ if (err) {
+ return cb(err)
+ }
+
+ const atBotName = new RegExp(`^@${username} run CI`, 'mi')
+ const wasMentioned = commentBody.match(atBotName) !== null
+
+ cb(null, wasMentioned)
+ })
+}
+
+// URL to the Jenkins job should be triggered for a given repository
+function buildUrlForRepo (repo) {
+ // e.g. JENKINS_JOB_URL_CITGM = https://ci.nodejs.org/job/citgm-continuous-integration-pipeline
+ const jobUrl = process.env[`JENKINS_JOB_URL_${repo.toUpperCase()}`] || ''
+ return jobUrl ? `${jobUrl}/build` : ''
+}
+
+// Authentication token configured per Jenkins job needed when triggering a build,
+// this is set per job in Configure -> Build Triggers -> Trigger builds remotely
+function buildTokenForRepo (repo) {
+ // e.g. JENKINS_BUILD_TOKEN_CITGM
+ return process.env[`JENKINS_BUILD_TOKEN_${repo.toUpperCase()}`] || ''
+}
+
+function triggerBuild (options, cb) {
+ const { repo } = options
+ const base64Credentials = new Buffer(jenkinsApiCredentials).toString('base64')
+ const authorization = `Basic ${base64Credentials}`
+ const buildParameters = [{
+ name: 'GIT_REMOTE_REF',
+ value: `refs/pull/${options.number}/head`
+ }]
+ const payload = JSON.stringify({ parameter: buildParameters })
+ const uri = buildUrlForRepo(repo)
+ const buildAuthToken = buildTokenForRepo(repo)
+
+ if (!uri) {
+ return cb(new TypeError(`Will not trigger Jenkins build because $JENKINS_JOB_URL_${repo.toUpperCase()} is not set`))
+ }
+
+ if (!buildAuthToken) {
+ return cb(new TypeError(`Will not trigger Jenkins build because $JENKINS_BUILD_TOKEN_${repo.toUpperCase()} is not set`))
+ }
+
+ options.logger.debug('Triggering Jenkins build')
+
+ request.post({
+ uri,
+ headers: { authorization },
+ qs: { token: buildAuthToken },
+ form: { json: payload }
+ }, (err, response) => {
+ if (err) {
+ return cb(err)
+ } else if (response.statusCode !== 201) {
+ return cb(new Error(`Expected 201 from Jenkins, got ${response.statusCode}`))
+ }
+
+ cb(null, response.headers.location)
+ })
+}
+
+function createPrComment ({ owner, repo, number, logger }, body) {
+ githubClient.issues.createComment({
+ owner,
+ repo,
+ number,
+ body
+ }, (err) => {
+ if (err) {
+ logger.error(err, 'Error while creating comment to reply on CI run comment')
+ }
+ })
+}
+
+module.exports = (app) => {
+ app.on('issue_comment.created', function handleCommentCreated (event, owner, repo) {
+ const { number, logger, comment } = event
+ const commentAuthor = comment.user.login
+ const options = {
+ owner,
+ repo,
+ number,
+ logger
+ }
+
+ function replyToCollabWithBuildStarted (err, buildUrl) {
+ if (err) {
+ logger.error(err, 'Error while triggering Jenkins build')
+ return createPrComment(options, `@${commentAuthor} sadly an error occured when I tried to trigger a build :(`)
+ }
+
+ createPrComment(options, `@${commentAuthor} build started: ${buildUrl}`)
+ logger.info({ buildUrl }, 'Jenkins build started')
+ }
+
+ function triggerBuildWhenCollaborator (err) {
+ if (err) {
+ return logger.debug(`Ignoring comment to me by @${commentAuthor} because they are not a repo collaborator`)
+ }
+
+ triggerBuild(options, replyToCollabWithBuildStarted)
+ }
+
+ ifBotWasMentionedInCiComment(comment.body, (err, wasMentioned) => {
+ if (err) {
+ return logger.error(err, 'Error while checking if the bot username was mentioned in a comment')
+ }
+
+ if (!wasMentioned) return
+
+ githubClient.repos.checkCollaborator({ owner, repo, username: commentAuthor }, triggerBuildWhenCollaborator)
+ })
+ })
+}