How-tos and Support

Sending Notifications in Pipeline

15 minutes to read

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:

Slack Configuration
HipChat Configuration
Email Configuration

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 a Jenkinsfile in SCM. To copy and paste it directly into a Jenkins Pipeline job, replace the checkout scm step with git '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:

Started Notifications
Started Email Notification

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:

Multiple Notifications

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']]
)
}

Failed Notifications

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.

Final View of Notifications

Links

This is the third in a series of posts showing ways to use Jenkins Pipeline. Follow along with this entire series through the links below:

Subscription confirmed

You'll now receive our latest news, releases, and event updates by email.