From CLI to Chat command in 5 minutes
I’m sure some of you heard about ChatOps and immediately thought, “That sounds really cool. But, our team already built a bunch of command line tools to manage our infrastructure. You mean we have to rewrite all of those just to run them from chat?” Well, for the most part, you’re not wrong. But, because Cog lets you write commands in any language and commands can specify a docker container to run in, you can quickly wrap your existing tooling in a simple bash script in less than five minutes. To prove it, I’ll take dig
, the DNS lookup command we're all familiar, and turn it into a chat command.
Starting with the basics
Let’s start with a simple invocation that passes a single name argument for our query. Here’s what that looks like on the command line:
dig operable.io
and from chat we want to be able to run:
@cog dig operable.io
If you’ve written a command-line script before, writing a Cog command should feel familiar with two main differences: command invocations from chat are parsed by Cog and all arguments and options are passed down to the executable as environment variables, and STDOUT can be specified as JSON or left as raw text and automatically converted to a list of strings. For all the details of how Cog commands work, you can take a look at Cog’s command documentation, but that’s all you should need to know to get started.
So let’s write that command. We just want to take a single argument and pass that directly to dig
as-is. To do that we'll need a simple wrapper bash script that takes the value of COG_ARGV_0
and passes it to dig
. Open up dig.sh
and paste the following:
#!/usr/bin/env bashdig "$COG_ARGV_0"
Now, to install and run this command, we need to define a bundle config. Create a file named config.yaml
and add the following:
cog_bundle_version: 4
name: dig
version: "0.1.0"
description: DNS lookup utility
docker:
image: dig
tag: "0.1.0"
commands:
dig:
executable: "/dig.sh"
description: DNS lookup utility
rules:
- allow
This defines the dig
bundle with a single command, also named dig
. It also includes a docker image to run each command in. Let's build that next by creating a Dockerfile
.
FROM alpine:latestRUN apk -U add bind-tools bash
COPY dig.sh /
RUN chmod +x /dig.sh
All we’re doing here is installing dig
(via the bind-tools
package) and bash
, copying over our newly created bash script named dig.sh
and making that executable. Now the only thing we have left is to build this image and use the tag specified in our bundle config.
docker build -t dig:0.1.0 .
Ok, we’re ready to install it with cogctl
assuming you’ve already got Cog up and running. (If not, do that real quick by following our Installation Guide)
cogctl bundle install config.yaml --enable --relay-group default
To check that everything worked. You should be able to run the command and see the following response in chat.
Adding options
Ok now that we have the basics working, let’s add a couple of command options. We want to be able to set the name server and the type of record we’re querying for. You could use positional arguments instead but options give us a nice place to hang extra documentation and allow us to write rules against these values more easily (for instance we could only allow users with the admin
permission to query for axfr
records). To do this we’ll need to make changes to our script and bundle config.
Since Cog parses options for us, we don’t need to use getopts
or anything, but we do need to transpose some environment variables into positional arguments that dig
exepects; COG_OPT_SERVER
to the first argument if it exists and COG_OPT_TYPE
to the last argument if it exists. Our new dig.sh
should look like this:
#!/usr/bin/env bashdeclare -a argumentsif [ "$COG_OPT_SERVER" != "" ]; then
arguments+=("@${COG_OPT_SERVER}")
fiif [ "$COG_ARGV_0" != "" ]; then
arguments+=("$COG_ARGV_0")
fiif [ "$COG_OPT_TYPE" != "" ]; then
arguments+=("$COG_OPT_TYPE")
fidig "${arguments[@]}"
We also need to tell Cog about these options in our bundle config.
...
commands:
dig:
executable: "/dig.sh"
description: DNS lookup utility
options:
server:
type: string
required: false
type:
type: string
required: false
rules:
- allow
I’d recommend you always include a description
and short_flag
attribute for options, but I've omitted them here to keep this short.
Now, we just need to reinstall our bundle and we can run our new command from chat.
cogctl bundle install config.yaml --force
Emitting JSON
To really make a Cog command useful, it needs to emit JSON data rather than strings. That way, you can use it in a pipeline and other commands can work with the data it outputs. For example, imagine you wanted to run a health check for all the IP addresses returned from a dig query. If dig
returned JSON, we could pipe the output to a curl
command to perform the healthcheck.
dig operable.io | curl --write-out "%{http_code}" "http://$ip"
Seems pretty useful, so let’s make our bash script print out some JSON. All we have to do is print “JSON”
(and a newline) to STDOUT followed by a JSON list and Cog will do the rest. But, the output dig
gives us is mostly tabular, which would be pretty annoying to parse, even if you've memorized the sed
manpage. Instead let's limit the output dig gives us with some extra options and parse the remaining table of answer data with jq
.
Oh and first, well need to add jq
to our Dockerfile
real quick.
RUN apk -U add bind-tools bash jq
And now for the command bits:
#!/usr/bin/env bashdeclare -a arugmentsarguments+=("+noall")
arguments+=("+answer")if [ "$COG_OPT_SERVER" != "" ]; then
arguments+=("@${COG_OPT_SERVER}")
fiif [ "$COG_ARGV_0" != "" ]; then
arguments+=("$COG_ARGV_0")
fiif [ "$COG_OPT_TYPE" != "" ]; then
arguments+=("$COG_OPT_TYPE")
firead -r -d '' filter <<'EOF'
split("\n") |
.[0:-1] |
map(split("\t")) |
map({"name": .[0], "type": .[4], "ip": .[5]})
EOFecho "JSON"dig "${arguments[@]}" |
jq --raw-input --slurp "$filter"
That jq
filter is a little intense, but now when you run your command, you should see glorious JSON. And only after a pretty conservative 30 lines of bash, we have dig
in chat.
Where to go from here
There are a few things I didn’t have time for in this post: logging from and debugging commands, applying rules to command options and arguments, and formatting JSON output with templates just to start. But, we cover those in detail in our full-featured command walkthrough. So, check that out and let us know what commands you’ve ported over to Cog, or even upload them to Warehouse so the community can give them a shot.
More ways to follow Cog:
Operable | Documentation | Twitter | Slack | Newsletter