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

Kicking off Jenkins build triggered by comment #128

Merged
merged 1 commit into from
Apr 14, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`**<br>
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)<br>
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/<YOUR_GITHUB_USERNAME>/configure`, by clicking the
"show API token" button. Also See: https://wiki.jenkins-ci.org/display/JENKINS/Authenticating+scripted+clients
- **`JENKINS_JOB_URL_<REPO_NAME>`** (optional)<br>
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_<REPO_NAME>`** (optional)<br>
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`**<br>
Username and password used to protected the log files exposed in /logs. Expected format: `username:password`.
- **`KEEP_LOGS`**<br>
Expand Down
14 changes: 14 additions & 0 deletions lib/bot-username.js
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions lib/github-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions scripts/trigger-jenkins-build.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
}