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 github.com/fsouza/go-dockerclient $ go build src/github.com/josdirksen/slackproxy/slack-proxy.go $ ./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 / EXPOSE 9000 # If external packages are needed, install them manually. Not needed if installed in the GOPATH RUN go get github.com/fsouza/go-dockerclient && go install github.com/fsouza/go-dockerclient WORKDIR src RUN go build -o /app/main github.com/josdirksen/slackproxy/slack-proxy.go 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 github.com/fsouza/go-dockerclient && go install github.com/fsouza/go-dockerclient ---> Running in 6f712526bb99 ---> 76cecb4c7a19 Removing intermediate container 6f712526bb99 Step 7 : RUN go build -o /app/main github.com/josdirksen/slackproxy/slack-proxy.go ---> 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://192.168.99.100:2376 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} client.Logs(logoptions) }
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 'token=5cLHiZjpWaRDb0fP6ka02XCR&team_id=T0001&team_domain=example&channel_id=C2147483705&channel_name=test&user_id=jos,nd=docker&text=local+logs+669265a13436+10' > 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 github.com/shirou/gopsutil”. 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. |