Rather than sitting and watching Jenkins for job status, I want Jenkins to sendnotifications when events occur. There are Jenkins plugins forSlack,HipChator even [email]among others.
Note: Something is Happening!
I think we can all agree getting notified when events occur is preferable tohaving to constantly monitor them just in case. I'm going to continue fromwhere I left off in myprevious post with thehermann project. I added a JenkinsPipeline with an HTML publisher for code coverage. This week, I'd like to makeJenkins notify me when builds start and when they succeed or fail.
Setup and Configuration
First, I select targets for my notifications. For this blog post, I'll use sampletargets that I control. I've created Slack and HipChat organizations called"bitwiseman", each with one member - me. And for email I'm running a Ruby SMTP server calledmailcatcher, that is perfect for local testingsuch as this. Aside from these concessions, configuration would be much thesame in a non-demo situation.
Next, I install and add server-wide configuration for theSlack,HipChat,and [Email-ext]plugins. Slack and HipChat use API tokens - both products have integrationpoints on their side that generate tokens which I copy into my Jenkinsconfiguration. Mailcatcher SMTP runs locally. I just point Jenkinsat it.
Here's what the Jenkins configuration section for each of these looks like:



Original Pipeline
Now I can start adding notification steps. Just like inthe previous post, I'll use theJenkins Pipeline Snippet Generatorto explore the step syntax for the notification plugins.
Here's the base pipeline before I start making changes:
LANG:code
stage 'Build'
node {
// Checkout
checkout scm
// install required bundles
sh 'bundle install'
// build and run tests with coverage
sh 'bundle exec rake build spec'
// Archive the built artifacts
archive (includes: 'pkg/*.gem')
// publish html
// snippet generator doesn't include "target:"
// https://issues.jenkins-ci.org/browse/JENKINS-29711.
publishHTML (target: [
allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: "RCov Report"
])
}
NOTE: This pipeline expects to be run from aJenkinsfilein SCM. To copy and paste it directly into a Jenkins Pipeline job, replace thecheckout scmstep withgit 'https://github.com/reiseburo/hermann.git'.
Job Started Notification
For the first change, I decide to add a "Job Started" notification. Thesnippet generator and then reformatting makes this straightforward:
LANG:code
node {
notifyStarted()
/* ... existing build steps ... */
}
def notifyStarted() {
// send to Slack
slackSend (color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
// send to HipChat
hipchatSend (color: 'YELLOW', notify: true,
message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})"
)
// send to email
emailext (
subject: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: """STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':
Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]"""",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
Since Pipeline is a Groovy-based DSL, I can usestring interpolationand variables to add the exact details I want in my notification messages. WhenI run this I get the following notifications:


Job Successful Notification
The next logical choice is to get notifications when a job succeeds. I'llcopy and paste based on the notifyStarted method for now and do some refactoringlater.
LANG:code
node {
notifyStarted()
/* ... existing build steps ... */
notifySuccessful()
}
def notifyStarted() { /* .. */ }
def notifySuccessful() {
slackSend (color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
hipchatSend (color: 'GREEN', notify: true,
message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})"
)
emailext (
subject: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: """SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':
Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]"""",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
Again, I get notifications, as expected. This build is so fast thatsome of them are even on the screen at the same time:

Job Failed Notification
Next I want to add failure notification. Here's where we really start to see the powerand expressiveness of Jenkins Pipeline. A Pipeline is a Groovy script, so as we'dexpect in any Groovy script, we can handle errors using try-catch blocks.
LANG:code
node {
try {
notifyStarted()
/* ... existing build steps ... */
notifySuccessful()
} catch (e) {
currentBuild.result = "FAILED"
notifyFailed()
throw e
}
}
def notifyStarted() { /* .. */ }
def notifySuccessful() { /* .. */ }
def notifyFailed() {
slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
hipchatSend (color: 'RED', notify: true,
message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})"
)
emailext (
subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: """FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':
Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]"""",
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}

Code Cleanup
Lastly, now that I have it all working, I'll do some refactoring. I'll unifyall the notifications in one method and move the final success/failure notificationinto a finally block.
LANG:code
stage 'Build'
node {
try {
notifyBuild('STARTED')
/* ... existing build steps ... */
} catch (e) {
// If there was an exception thrown, the build failed
currentBuild.result = "FAILED"
throw e
} finally {
// Success or failure, always send notifications
notifyBuild(currentBuild.result)
}
}
def notifyBuild(String buildStatus = 'STARTED') {
// build status of null means successful
buildStatus = buildStatus ?: 'SUCCESSFUL'
// Default values
def colorName = 'RED'
def colorCode = '#FF0000'
def subject = "${buildStatus}: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'"
def summary = "${subject} (${env.BUILD_URL})"
def details = """STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':
Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]""""
// Override default values based on build status
if (buildStatus == 'STARTED') {
color = 'YELLOW'
colorCode = '#FFFF00'
} else if (buildStatus == 'SUCCESSFUL') {
color = 'GREEN'
colorCode = '#00FF00'
} else {
color = 'RED'
colorCode = '#FF0000'
}
// Send notifications
slackSend (color: colorCode, message: summary)
hipchatSend (color: color, notify: true, message: summary)
emailext (
subject: subject,
body: details,
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
)
}
You Have Been Notified!
I now get notified twice per build on three different channels. I'm not sure Ineed to get notified this much for such a short build. However, for a longeror complex CD pipeline, I might want exactly that. If needed, I could evenimprove this to handle other status strings and call it as needed throughoutmy pipeline.

