Create a Slack Docker proxy in Go – Part 2

In the previous article we had a look at how you could easily create a slack-docker-proxy using go and a couple of small libraries. In this second article we’ll show you how easy it is add additional commands, and we’ll look at how to dockerize this component to you can easily run it inside a docker daemon. Note that the full sources for the various go files can of course be found at the slack-proxy github repository

Running go applications in docker

Let’s first look at how we can deploy and run this proxy. We saw that we could run it directly from the command line like this:

$ export GOPATH=directory/where/you/cloned/the/repo
$ go get
$ go build src/
$ ./slackproxy -config ./resources/config.json

But that isn’t the most practical way to do this. And since we’re already using this to monitor docker, why not just run this proxy inside docker as well.

To run a go-lang application in docker we can use two different approaches. We can compile the application from the command line and just COPY it into the docker image, or we can add the sources to an image that contains all the go stuff we need, and compile it as a step when creating our docker image. We’ll go for this second approach.

We’ve shown the docker file we’ll use for that here:

FROM golang:latest 
COPY src/ /go/src/
COPY resources/config.json /

# If external packages are needed, install them manually. Not needed if installed in the GOPATH
RUN go get && go install

RUN go build -o /app/main

CMD ["/app/main", "--config", "/config.json"]

The steps should be pretty self-explanatory. What we do is we make sure the sources are in the location the docker image expects (the /go/src folder), install any external dependencies and just run ‘docker build’. Which results in the following output:

$ docker build .
Sending build context to Docker daemon 203.8 kB
Step 1 : FROM golang:latest
 ---> bc422006801e
Step 2 : COPY src/ /go/src/
 ---> Using cache
 ---> a7c813facaf8
Step 3 : COPY resources/config.json /
 ---> Using cache
 ---> b6fd55de16f6
Step 4 : EXPOSE 9000
 ---> Using cache
 ---> d30199bcc59a
Step 5 : WORKDIR /go/src
 ---> Using cache
 ---> e861b557571d
Step 6 : RUN go get && go install
 ---> Running in 6f712526bb99
 ---> 76cecb4c7a19
Removing intermediate container 6f712526bb99
Step 7 : RUN go build -o /app/main
 ---> Running in 77c092d1bcf2
 ---> f7ca02665f94
Removing intermediate container 77c092d1bcf2
Step 8 : CMD /app/main --config /config.json
 ---> Running in 0bbeb60ca608
 ---> ebd9d3fe8ce7
Removing intermediate container 0bbeb60ca608
Successfully built ebd9d3fe8ce7

The resulting image can now be run like this:

$ docker run 94abcb31af14
{5cLHiZjpWaRDb0fP6ka02XCR {[{local tcp:// true /Users/jos/.docker/machine/machines/eris ca.pem cert.pem key.pem}]}}

Or uploaded to your local registry by tagging it:

docker tag 94abcb31af14 my.own.registry/slack-proxy:latest

Extending with new functionality

Extending this proxy with new functionality is really easy. A common scenario we have is that after we’ve deployed something and stuff stops working we can quickly check the log files. Now logging in to the machine and using docker log isn’t that hard, but it takes a couple of steps. So we’ll add support to view log files directly from slack. We’ll first define the function in the dockerCommandHandlers.go file.

func handleLogsCommand(client *docker.Client, cmd *Command, w http.ResponseWriter) {
	var tail = "all"
	if (len(cmd.OtherArguments) == 2) {
		tail = cmd.OtherArguments[1]
	logoptions := docker.LogsOptions{Container: cmd.OtherArguments[0], Stdout: true, Stderr: true, Follow: false, Tail: tail, OutputStream: w, ErrorStream: w}

Note that we once again don’t really do any input validation. We just assume the correct number of arguments are passed in. Now that we’ve got the function, we update the HandleCommand function in the same file to this:

func (dh DockerHandler) HandleCommand(cmdToExecute *Command, w http.ResponseWriter) {
	client := setupDockerClient(cmdToExecute.Environment)

	fmt.Printf("%+v\n", cmdToExecute)

	switch cmdToExecute.SlackCommand {
	case "ps" : handlePsCommand(client, w)
	case "logs" : handleLogsCommand(client, cmdToExecute, w)
	case "imgs" : handleListImagesCommand(client, w)

And we’re done. Now when a call is made like this:

$ curl 'http://localhost:9000/handleSlackCommand' -H 'Content-Type: application/x-www-form-urlencoded' --data 
> 2016/02/07 18:48:04 [WARN] agent: Check 'service:wiremock' is now warning
> 2016/02/07 18:48:09 [WARN] agent: Check 'service:dag-storage' is now warning
> 2016/02/07 18:48:17 [WARN] agent: Check 'service:wiremock' is now warning
> 2016/02/07 18:48:19 [WARN] agent: Check 'service:dag-storage' is now warning
> 2016/02/07 18:48:29 [WARN] agent: Check 'service:dag-storage' is now warning
> 2016/02/07 18:48:30 [WARN] agent: Check 'service:wiremock' is now warning
> 2016/02/07 18:48:39 [WARN] agent: Check 'service:dag-storage' is now warning
> 2016/02/07 18:48:43 [WARN] agent: Check 'service:wiremock' is now warning
> 2016/02/07 18:48:49 [WARN] agent: Check 'service:dag-storage' is now warning
> 2016/02/07 18:48:56 [WARN] agent: Check 'service:wiremock' is now warning

We get back the latest 10 log entries for the container with id 669265a13436.

Or if you’re more inclined to use Postman.


Extending besides docker

So far we’ve only looked at exposing docker through this minimal proxy. Exposing other services, components and information sources through this setup is actually really easy to do. As an example lets write a couple of commands that expose information of the host the slack-proxy is running on. For this we first add a new CommandHandler:

func handleHostCommand(w http.ResponseWriter) {
	var buffer bytes.Buffer

	info, _ := host.HostInfo()
	buffer.WriteString(fmt.Sprintf("Boottime: %v\n", info.BootTime))
	buffer.WriteString(fmt.Sprintf("Hostname: %v\n", info.Hostname))
	buffer.WriteString(fmt.Sprintf("Uptime: %v\n", info.Uptime))

	io.WriteString(w, buffer.String())

If you’ve looked at the way we’ve setup the docker handler, this one, shouldn’t come as a suprise. We use a simple switch to match the command we need to execute, and in this case use the gopsutil library to return file system information. Note that if you want to run this without docker, you first have to do a “go get”. Since we added an additional command handler we also need to make a small change to the GetHandler function from the handlerFactory:

func GetHandler(handlerName string, config *config.Configuration) CommandHandler {
	switch handlerName {
	case "docker": return NewDockerHandler(config)
	case "system" : return NewSystemHandler()
	default: return NewDummyHandler()

With this change, when the command is system we’ll return the handler we just created, and if the command is docker, we’ll return the one that can handle docker commands. To see this in action, you can use the following curl command for the memory information:

$ curl 'http://localhost:9000/handleSlackCommand' -H 'Content-Type: application/x-www-form-urlencoded' --data 'token=5cLHiZjpWaRDb0fP6ka02XCR&team_id=T0001&team_domain=example&channel_id=C2147483705&channel_name=test&user_id=jos,nd=system&text=local+mem'
> Total: 17179869184, Free:41549824, UsedPercent:65.031576

And to test the host info use this:

$ curl 'http://localhost:9000/handleSlackCommand' -H 'Content-Type: application/x-www-form-urlencoded' --data 'token=5cLHiZjpWaRDb0fP6ka02XCR&team_id=T0001&team_domain=example&channel_id=C2147483705&channel_name=test&user_id=jos,nd=system&text=local+host'
> Boottime: 1453982905Hostname: Joss-MacBook-Pro.localUptime: 889269%

And with that we’re at the end of this article on slack / docker (and other sources) with go-lang. You can find the complete sources for this article here. And if you’ve got suggestions please let me know.

Reference: Create a Slack Docker proxy in Go – Part 2 from our JCG partner Jos Dirksen at the Smart Java blog.
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Inline Feedbacks
View all comments
Back to top button