How ChatOps is redefining enterprise and open source DevOps

The concept of ChatOps isn’t new, by any means. People have been using chatbots on IRC and other messaging channels for a long time. ChatOps was only  coined more recently by Github, and is often placed under the DevOps umbrella. This isn’t surprising, since example use cases and stories often describe deploying an entire app to production by simply sending a message to a bot. It really can be as easy as “@mybot deploy myapp to production”.

So, what is ChatOps all about then? In this blog, we’ll take a look at some of its defining features, and how it can be an essential and valuable component of a DevOps implementation.

What is ChatOps, and how does it work?

Essentially,  ChatOps begins with you registering a script-able bot to your chat channel – be it Slack, Hipchat or whichever chat service you prefer- and then ask it to perform routine tasks for you. There are several ChatOps frameworks available,the most notable being  Hubot from the folks at GitHub. Hubot is written in Node.js, with scripts being written in either coffee script or Javascript. The other two notable chatbot frameworks available are  Err, which is written and scripted in Python or  Litawhich is written and scripted in Ruby. For the purposes of this blog, we’re going to focus on Hubot.

What makes ChatOps worthwhile? 

Some of the benefits of ChatOps include:

  • Automating common tasks

Chatbots are excellent for automating common tasks that are hard to trigger automatically or can’t be run by a scheduler. Basically, any tasks that need some human consideration to begin execution are perfectly suited to Chatbot automation.

  • Shared History and operational  visibility

A shared history means that anyone on the chat channel can see what the others have asked the bot already. This makes it possible to see what everyone else in the team has already asked the bot, and reduces the chance of wasted time due to repeat work.

A look at ChatOps in practice

Let’s take a look at an example chatbot. Installation is pretty straight-forward: the only dependencies are Node.js and yeoman, a scaffolding tool. You can find the official install instructions here. Install the bot with the default campfire adapter, any other adapters you want to use (ie Slack or Hipchat) can be installed later.

To give him a bit of personality, I called my bot Garry. Let’s start Garry up in shell mode. You might see some heroku or redis warnings – they’re safe to ignore for now. The robot has a “brain”, which is essentially a persistent store for certain details backed by redis. It isn’t essential in order for the robot to operate.

$ bin/hubot

We’ll need a prompt to be able to talk to Garry in shell mode, so typing ‘garry help’ will trigger hubot to list all the commands it knows about. Hubot comes will a bunch of built-in plugins, but none of them do anything particularly useful, and mostly serve as examples. You can turn them off by removing them from the file ‘external-scripts.json’.

garry> garry help
garry adapter - Reply with the adapter
garry animate me <query> - The same thing as `image me`, except adds a few parameters to try to return an animated GIF instead.
garry echo <text> - Reply back with <text>
...

Now, let’s run some of the included example scripts:

garry> garry ping
PONG

garry> garry echo I am a chatbot!
I am a chatbot!

Chatbots allow custom scripts for total customisation.

Hubot has a large base of custom scripts for performing certain actions, and you can view them here or search through npm ( npm search hubot-scripts <query>). For now, let’s create our own custom script. Hubot scripts can be written in either pure javascript or coffee script.

Let’s create a fictional company directory so we can ask hubot to give us details on particular people.  I’ll place my company directory script in scripts/company-directory.coffee. I’m defining the users as an array of objects for this example, but you can easily replace this with some logic to query a user directory or database as necessary.

I’m using a Node.js module from the atom text editor (also by Github) called fuzzaldrin to take care of the search logic.

scripts/company-directory.coffee

# Description:
#   Lookup user info from company directory
#
# Dependencies:
#   "fuzzaldrin": "^2.1.0"
#
# Commands:
#   hubot phone of <user query> - Return phone details for <user query>
#   hubot email of <user query> - Return email details for <user query>
#   hubot details of <user query> - Return all details for <user query>
#
# Author:
#   Angus Williams <angus@forest-technologies.co.uk>
{filter} = require 'fuzzaldrin'
 
# Define a list of users
directory = [
  {
    firstName: "John",
    lastName: "Lennon",
    fullName: "John Lennon",
    email: "johnl@example.com",
    phone: "+44 700 700 700"
  },
  {
    firstName: "Paul",
    lastName: "McCartney",
    fullName: "Paul McCartney",
    email: "paulm@example.com",
    phone: "+44 700 700 701"
  },
  {
    firstName: "George",
    lastName: "Harrison",
    fullName: "George Harrison",
    email: "georgeh@example.com",
    phone: "+44 700 700 703"
  },
  {
    firstName: "Ringo",
    lastName: "Starr",
    fullName: "Ringo Starr",
    email: "ringos@example.com",
    phone: "+44 700 700 704"
  }
]

module.exports = (robot) ->
  robot.respond /phone of ([\w .\-]+)\?*$/i, (res) ->
    # Get user query from capture group and remove whitespace
    query = res.match[1].trim()
    
    # Fuzzy search the directory list for the query
    results = filter(directory, query, key: 'fullName')
    
    # Reply with results
    res.send "Found #{results.length} results for query '#{query}'"
    for person in results
      res.send "#{person.fullName}: #{person.phone}"

  robot.respond /email of ([\w .\-]+)\?*$/i, (res) ->
    # Get user query from capture group and remove whitespace
    query = res.match[1].trim()


    # Fuzzy search the directory list for the query
    results = filter(directory, query, key: 'fullName')


    # Reply with results
    res.send "Found #{results.length} results for query '#{query}'"
    for person in results
      res.send "#{person.fullName}: #{person.email}"


  robot.respond /details of ([\w .\-]+)\?*$/i, (res) ->
    # Get user query from capture group and remove whitespace
    query = res.match[1].trim()


    # Fuzzy search the directory list for the query
    results = filter(directory, query, key: 'fullName')


    # Reply with results
    res.send "Found #{results.length} results for query '#{query}'"
    for person in results
      res.send "#{person.fullName}: #{person.email}, #{person.phone}"

A closer look at the script in question.

Let’s dissect the script a little. The comments at the start of the script are reasonably important here, and follow a certain format so that information can be pulled out. The Commands section is particularly of interest:

# Commands:
#   hubot phone of <user query> - Return phone details for <user query>
#   hubot email of <user query> - Return email details for <user query>
#   hubot details of <user query> - Return all details for <user query>

This section is used by hubot’s help module. Documenting available commands here is important, as it will allow users to see the functionality provided by your script using the ‘hubot help’ command. More info about script documentation can be found here.

The next import section is further down the script, under the robot module export.

robot.respond /phone of ([\w .\-]+)\?*$/i, (res) ->
  name = res.match[1].trim()
  results = filter(directory, name, key: 'fullName')
  res.send "Found #{results.length} results for query '#{name}'"
  for person in results
    res.send "#{person.fullName}: #{person.phone}"

Here we are telling hubot to respond to anything that matches the regex described ('/phone of ([\w .\-]+)\?*$/i').Regex capture groups ('([\w .\-]+)') are used to pull the name from the command.

Another important thing to note is that we’re using the robot.respond invocation. This means that hubot will only respond to commands that directly address him, I.e. ‘garry do something’. We can also use hear, which will match any messages sent to a particular chat room. Similarly, we are addressing everyone with the res.send method to send a reply, but we could just as easily address the user that invoked the script by using the res.reply method instead.

Let’s restart hubot and test out our script.

garry> garry phone of ringo
Found 1 results for query 'ringo'
Ringo Starr: +44 700 700 704


garry> garry details of in
Found 2 results for query 'in'
Ringo Starr: ringos@example.com, +44 700 700 704
George Harrison: georgeh@example.com, +44 700 700 703 

garry> garry email of paul
Found 1 results for query 'paul'
Paul McCartney: paulm@example.com

That’s all good and well, but how do we actually use this with a proper chat client?

Lets use Slack as an example. First, you’re going to need to sign up for a slack account if you don’t already have one. Once you’ve got a slack team set up, you’ll need to enable hubot integration. You’ll find step-by-step instructions here. Once complete, you should be presented with an API token.

Let’s install the Slack adaptor in the root of the hubot repository and start hubot with the slack adaptor using the API token from the Slack hubot integration setup.

$ npm install hubot-slack --save
$ env HUBOT_SLACK_TOKEN=<SLACK API TOKEN> ./bin/hubot --adapter slack
[Wed Nov 18 2015 17:31:16 GMT+0000 (GMT)] INFO Connecting...
[Wed Nov 18 2015 17:31:20 GMT+0000 (GMT)] INFO Logged in as garry of Dummy Corp, but not yet connected
[Wed Nov 18 2015 17:31:21 GMT+0000 (GMT)] INFO Slack client now connected

Now all that’s left for us to do is to talk to Garry in Slack!

garry.png

As you can see, hubot listens for either @ mentions as well as the bot’s name.  You can find the example chatbot on the forest technologies github account here.

Useful Links

https://github.com/github/hubot/blob/master/docs/scripting.md

https://hubot.github.com/docs/

https://github.com/slackhq/hubot-slack

If you’d like to find out more about us and our services, including consultation, training and our extensive experience with enterprise and open source DevOps tools, please don’t hesitate to get in touch.

Image credit: Jason Hand

Angus WilliamsHow ChatOps is redefining enterprise and open source DevOps