Slacking with Jenkins Like a Pro

Let the butler do the calling out

You’ve got mail

If you work with Continuous Integration and Delivery pipelines, then you’ve certainly seen this before. You probably already know what I’m talking about: sending notifications that get ignored by the majority of the team. “I’ve crafted this beautiful pipeline and they don’t even check their emails. Ungrateful bastards!” It gets even more frustrating when those who ignore notifications are precisely those who usually break the builds.

It’s all too easy to blame email when this happens, as if the problem was the medium itself. It’s not. The problem is that there’s already too much going on in people’s inboxes and email folders. Switching to a chat application, like HipChat or Slack, won’t necessarily solve the problem—as soon as your pipeline starts sending too many status messages to a chat channel, people will regretlessly turn off notifications and put you in the same position as you were when your pipelines were sending emails.

Solutions

Here are some ideas:

  1. make everyone work from the same room and yell at them when they break a build;
  2. implant a chip under their skin that sends electric signals (50v will do the trick) whenever a build fails;
  3. instead of the implants, build a status light that turns red and emits an annoying siren sound;
  4. call them out on chat, but don’t generate unnecessary noise for the others.

Number 1 will get you fired (unless you work at Uber); number 2, as far as I know, is illegal; number 3 is a lot of work; number 4 strikes the perfect balance. The reason number 4 is so good is because it doesn’t force anyone to subscribe to all notifications in a chat channel—they only get their names mentioned when a build relates specifically to them.

/img/2018/07/2018-07-31-slacking-with-jenkins-like-a-pro/leave-it-with-the-butler-thumb.jpg

Leave it with the butler.

Number 4 it is, then. Let’s see how to do that with Slack in a Jenkins pipeline.

There’s a plugin for that

Remember Jenkins’ mantra: “There’s a plugin for that.” No need to create a Groovy function that uses curl and a horrible JSON payload just to call the Slack API. Why would you do that, man?

This is the Jenkins plugin (Slack Notification) you need in order to send Slack notifications and this is the function (slackSend) it exposes. For this quick tutorial, I’ll assume you’ve already configured the Jenkins plugin and your team’s Slack account to work together. As for the plugin’s function, it’s too Slack-specific, so it makes sense to write our own wrapper in order to simplify the calls to it.

Wrapper function

This is one possible wrapper function you can use:

def sendBuildStatusOverSlack(String authorSlackID = null) {
    def colorCode = '#848484' // Gray

    switch (currentBuild.result) {
        case 'SUCCESS':
            colorCode = '#00FF00' // Green
            break
        case 'UNSTABLE':
            colorCode = '#FFFF00' // Yellow
            break
        case 'FAILURE':
            colorCode = '#FF0000' // Red
            break;
    }

    String message = """
        *Jenkins Build*
        Job name: `${env.JOB_NAME}`
        Build number: `#${env.BUILD_NUMBER}`
        Build status: `${currentBuild.result}`
        Author: ${authorSlackID ? '<@' + authorSlackID + '>' : '???'}
        Build details: <${env.BUILD_URL}/console|See in web console>
    """.stripIndent()

    return slackSend(color: colorCode, message: message)
}

If your Jenkinsfile is written in the scripted pipeline syntax, make sure currentBuild.result contains a value that actually represents the state of the build at all times. This is what the documentation has to say about currentBuild.result: “typically SUCCESS, UNSTABLE, or FAILURE (may be null for an ongoing build).” Our function defaults to the gray color when that variable is empty or contains a strange value. If your pipeline is written in the new, declarative syntax, that variable will always contain a valid value and you don’t have to worry about it.

The most interesting part is the message body, where we can use text formatting, emojis and, most importantly, user handles. Note how we’ve surrounded the string Jenkins Build with the Slack marker for bold text (*).

Right below that, we use the back tick marker (`) around the build number and build status. Because those values are stored in variables (the globals env and currentBuild, respectively), we have to use the dollar sign for string interpolation.

We then get to the author handle, which has changed from user name to user ID in 2017. A Slack user’s ID looks like the string U9X76IJYA and can be found under the user’s profile in the Slack GUI. Don’t let the string interpolation trick you. We need to wrap that value in an expression that starts with <@ and ends with >. We also need to make sure we cover the cases when a Slack user ID is not passed but a notification should still be sent. We default to a dummy string (???) in those cases. No Slack user in particular will get directly notified, but there isn’t much we can do in those scenarios.

The last part of the message is the link to the web console, where team members will be able to see the error logs from the build. In order to create a link that Slack can parse, we need to open it with <, close it with >, and use a | to separate the URL from the link label.

Finally, we call slackSend and let it do its thing. Note that the parameter labels (color and message) are mandatory.

Make sure you always pass a Slack user ID to sendBuildStatusOverSlack, otherwise the function’s strongest feature goes down the drain. What I usually do is store a big map of Jenkins users (developers, integrators etc.) in a shared library, like this:

users = [
    bennytheball: [
        git: 'benny.ball@alley.way',
        slack: '12345ABCD'
    ],
    choochoo: [
        git: 'choo.choo@alley.way',
        slack: 'ABCD12345'
    ],
    topcat: [
        git: 'top.cat@alley.way',
        slack: 'U9X76IJYA'
    ]
]

Then, in the Jenkinsfile, I get the email address of the user responsible for the latest commit in the current branch:

String email = sh(script: 'git show -s --pretty=%ae', returnStdout: true).trim()

At that point, all that’s left to do is look for that commiter inside the users map and get his/her Slack ID. Of course, that assumes everybody has configured their Git clients with an email address. But if that’s not the case, your team has other problems to solve first…

Conclusion

Congratulations, you’ve just delegated the dirty work to Jenkins! No need to call out build breakers and embarrass them in front of their peers any more. Do you know how much money in lawsuits I’ve just saved you? Now go out there and slack some.