Learning Go with the Docker SDK

I've been learning Go lately and I can feel that it has a bit of a learning curve. However, I believe it will take time for me to get used to it.

Our First App - Hello World

First, you need to setup your environment. Install the latest version of Go. I am using go1.9.2 darwin/amd64 in my Mac.

You can download the latest version of Go from here
https://golang.org/dl/

Once you've installed Go, you need to setup your workspace. Here's a nice blog post explaining why you need multiple directories configured in your GOPATH.
https://dmitri.shuralyov.com/blog/18

Following the suggestion in Dmitri's blog, I've setup my $GOPATH to 2 directories. I do not use my Mac for work so I do not need the 3rd directory.
Here is what I configured in ~/.bash_profile.

export GOPATH=/Users/melvin/Documents/go/thirdparty:/Users/melvin/Documents/go/projects

Here is what will be saved in these directories:

  • /Users/melvin/Documents/go/thirdparty - 3rd party libraries
  • /Users/melvin/Documents/go/projects - Go apps I am working on (my workspace)

Now you've setup your workspace, you can now create a Go application. In your GOPATH, there will be 3 directories created once you start running and compiling your code.

These are the files which are saved in each directory (https://golang.org/doc/code.html)

  • src contains Go source files,
  • pkg contains package objects, and
  • bin contains executable commands

Let's create sample Go application. Create a directory in your project workspace

mkdir -p /Users/melvin/Documents/go/projects/src/helloworld

Create a hello.go file and copy and paste this code. The filename does not matter. It's a simple application which prints some text.

/Users/melvin/Documents/go/projects/src/helloworld/hello.go

package main

import(
	"fmt"
)

func main(){
	fmt.Println("Hello World!!! This is my first Go Application!")
}

Try to run the application

go run hello.go

If this successfully runs, your environment is properly configured!

Using the Docker SDK

Now, let's get to the fun part :)

Lately, I noticed that most tools are now developed in Go. Portainer is one of the nice tools we use for managing a Docker environment. I was curious on how it communicates with the Docker API.

So I tried Docker SDK. With my little knowledge of Go, I've created a sample application which communicates with the Docker API.

Here is the full source code which can also be downloaded from my github repo.

https://github.com/donvito/learngo/tree/master/docker

package main

import (
	"fmt"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
	"golang.org/x/net/context"
)

func main() {
	cli, err := client.NewEnvClient()

	if err != nil {
		panic(err)
	}

	listImages(cli)
	listCointainers(cli)
	listNetworks(cli)
	listSwarmNodes(cli)

	fmt.Printf("\n")

}

func listImages(cli *client.Client) {

	//List all images available locally
	images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
	if err != nil {
		panic(err)
	}

	fmt.Println("LIST IMAGES\n-----------------------")
	fmt.Println("Image ID | Repo Tags | Size")
	for _, image := range images {
		fmt.Printf("%s | %s | %d\n", image.ID, image.RepoTags, image.Size)
	}

}

func listCointainers(cli *client.Client) {
	//Retrieve a list of containers
	containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
	if err != nil {
		panic(err)
	}

	fmt.Print("\n\n\n")
	fmt.Println("LIST CONTAINERS\n-----------------------")
	fmt.Println("Container Names | Image | Mounts")
	//Iterate through all containers and display each container's properties
	for _, container := range containers {
		fmt.Printf("%s | %s | %s\n", container.Names, container.Image, container.Mounts)
	}

}

func listNetworks(cli *client.Client) {
	networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{})
	if err != nil {
		panic(err)
	}

	//List all networks
	fmt.Print("\n\n\n")
	fmt.Println("LIST NETWORKS\n-----------------------")
	fmt.Println("Network Name | ID")
	for _, network := range networks {
		fmt.Printf("%s | %s\n", network.Name, network.ID)
	}

}

func listSwarmNodes(cli *client.Client) {
	swarmNodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		panic(err)
	}

	//List all nodes - works only in Swarm Mode
	fmt.Print("\n\n\n")
	fmt.Println("LIST SWARM NODES\n-----------------------")
	fmt.Println("Name | Role | Leader | Status")
	for _, swarmNode := range swarmNodes {
		fmt.Printf("%s | %s | isLeader = %t | %s\n", swarmNode.Description.Hostname, swarmNode.Spec.Role, swarmNode.ManagerStatus.Leader, swarmNode.Status.State)
	}

}

Before running this application, you need to run some services in Docker. You can use a docker stack. I've created some stacks, you can get one from my github.

https://github.com/donvito/dockerstack

Once you've downloaded the stack compose file, you can run the stack using this command.

docker stack deploy -c demo-simple-stack.yml mystack

Screenshot-2017-12-09-17.17.11
When you already have containers runningMake sure the code is in your GOPATH.

/Users/melvin/Documents/go/projects/src/github.com/donvito/learngo/docker

You can now run the application using the source. Make sure you download all dependencies before running the application.

go run docker-sample.go

Here is the output of this Go application
Screenshot-2017-12-10-00.00.39

Using REST endpoints to serve data

I've modified the example code above to expose http endpoints instead of just outputting to stdout.

You can download the code from my github
https://github.com/donvito/learngo/blob/docker-sample-rest/docker/docker-sample.go

package main

import (
	"fmt"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
	"golang.org/x/net/context"

    //"html"
    "log"
    "net/http"

	"github.com/gorilla/mux"
	
	"strconv"
	"strings"
)

func main() {

	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/images", Images)
	router.HandleFunc("/containers", Containers)
	router.HandleFunc("/networks", Networks)
	router.HandleFunc("/swarm-nodes", SwarmNodes)
	log.Fatal(http.ListenAndServe(":9090", router))   
	
}

func Images(w http.ResponseWriter, r *http.Request) {

	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}
	
	//List all images available locally
	images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
	if err != nil {
		panic(err)
	}

	htmlOutput := "<html>"
	for _, image := range images {
		htmlOutput += image.ID + " | " + strconv.Itoa(int(image.Size)) + "<br/>"
	}
	htmlOutput += "</html>"
	fmt.Fprint(w, htmlOutput)
}


func Containers(w http.ResponseWriter, r *http.Request) {

	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	//Retrieve a list of containers
	containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
	if err != nil {
		panic(err)
	}

	//Iterate through all containers and display each container's properties
	//fmt.Println("Image ID | Repo Tags | Size")
	htmlOutput := "<html>" 
	for _, container := range containers {
		htmlOutput += strings.Join(container.Names, ",") + " | " + container.Image + "<br/>"
	}
	htmlOutput += "</html>"
	fmt.Fprint(w, htmlOutput)

}

func Networks(w http.ResponseWriter, r *http.Request) {

	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{})
	if err != nil {
		panic(err)
	}

	//List all networks
	htmlOutput := "<html>" 
	//fmt.Println("Network Name | ID")
	for _, network := range networks {
		htmlOutput += network.Name + " | " + network.ID + "<br/>"
	}
	htmlOutput += "</html>"
	fmt.Fprint(w, htmlOutput)

}

func SwarmNodes(w http.ResponseWriter, r *http.Request) {

	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	swarmNodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		panic(err)
	}

	//List all nodes - works only in Swarm Mode
	htmlOutput := "<html>" 
	//fmt.Println("Name | Role | Leader | Status")
	for _, swarmNode := range swarmNodes {
		htmlOutput += swarmNode.Description.Hostname
	}
	htmlOutput += "</html>"
	fmt.Fprint(w, htmlOutput)

}

To test the REST endpoints, just access the URLs using your browser. Exposed are 4 endpoints.

http://localhost:9090/images
http://localhost:9090/containers
http://localhost:9090/networks
http://localhost:9090/swarm-nodes

Output is for containers would look like this. Pardon the poor html formatting. I'll put it in html tables when I have time! :)

Screenshot-2017-12-12-01.30.15

Hope this article is useful to anyone learning Go and the Docker SDK.

Let's connect in LinkedIn, Twitter and github!