cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Announcements

Adding a Simple Command to Docker

Docker is a wonderful open source container system, and has good documentation for getting a development environment set up. Recently I needed to add a new command to Docker, and I found the next steps after setting up the development environment to be a bit unclear. I’m hoping to share with you a bit about how Docker is architected from a CLI perspective by adding a simple Hello World command.

You need to follow Docker’s Quickstart Code Contribution workflow (https://docs.docker.com/opensource/code/) through step 3, which will get you setup and working with a container that includes your own fork of the Docker code and allows you to build code with the make command. Once you’ve done this you will have a working development environment, and we are ready to learn how to add our command to Docker.

The Docker source code that you have contains the Docker code for both the CLI and the daemon. The CLI code is broken into two parts cli and client packages. The cli package found in the cli/ directory defines how the CLI is initialized, executes, and what commands are available.

The client package found in api/client/ contains the files that each individual command executes to perform its task.

Our journey to add the Hello World command begins in the cli package. This is what gets executed first when you type docker at the command line. In fact, if you just type docker with no arguments, the cli package will create an instance of the Cli struct, read the cli/common.go to get the list of available Docker commands and the associated description for each command, and output them to the screen. The cli/common.go file is the first file we will need to edit in order to add our Hello world command.

If we look in the cli/common.go file, we will see dockerCommands which is an array of commands and their associated help text. We will need to add our command to this list to inform the docker command that it exists. In the example below you can see I’m adding it into the list by its alphabetical description.

Picture1.png

Now we will use the “make BINDDIR=. binary” command from the Docker quickstart guide (https://docs.docker.com/opensource/project/set-up-dev-env/ Section: Restart a container with your source)

Once that completes a new docker binary will be created in our development container in the bundles/<version>/binary/docker, and if I run that command I’ll see my new hello command in the list of available commands.

Picture2.png

However, if I run bundles/1.9.1/binary/docker help hello I’ll get a message that ‘hello’ is not a docker command as shown here.

Picture3.png

This is because any time we run the docker binary with an argument it runs an actual method instead of just reading from cli/common.go and printing the output. If we look in the cli/cli.go file, we can see the CmdHelp() method that calls the command() method to search for a command handler method that is named CmdHello in the client package. Since we’ve not added a CmdHello handler, the command() method returns a error with “command not found” and the CmdHelp() method runs the noSuchCommand() method which outputs the error shown in the prior example. So now that we know that the command() method is searching for a CmdHello handler method in the client package, lets go add that method.

The client package is in the api/client/ directory, with a file for each docker command listed in cli/common.go.

Picture4.png

Since our command it just going to print some information to the screen, there isn’t an existing one that would make a good reference. The reason they won’t make a good reference is that most commands make an API call to the docker daemon to get some information or to perform an action, which we will look at doing in a later post. However, if we look at api/client/version.go, we can see that our command has to be part of the client package. It also expects the command to accept multiple strings as args and return an error if one occurs. If we do some more digging into the version.go, we can see that we can use cli.out to write output to the screen. So a simple implementation of our Hello command might look like the following:

Picture5.png

We import our cli package that we made changes to in the first part of this post (Line 6). The flag package is used by docker to parse command arguments (Line 7). Now we are ready to define our CmdHello handler method, that takes an array of strings as the variable args and returns an error if one occurs (Line 13). Now, we initialize the cli subcommand that we are implementing with a name, synopsis, description, and Boolean to decide if we exit on error (Line 14). We don’t need any extra flags or arguments to our command, so we use the flag package to instruct our subcommand to require 0 extra arguments (Line 15). Next, we parse any arguments we might have been given even though we don’t need them as this will return an error if we get one (Line 16). Finally, we print our “Hello World” to the default cli.out stream and return (Line 18-20)

Now we use the “make BINDDIR=. binary” command from the Docker quickstart guide (https://docs.docker.com/opensource/project/set-up-dev-env/ Section: Restart a container with your source)

Now we attempt to run the docker help hello command and we get back a helpful usage message shown here

Picture6.png

Now lets see if our hello command works when we attempt to run it.

Picture8.png

Finally, lets make sure it doesn’t accept any other random arguments or flags:

Picture9.png

I hope you enjoyed learning more about how the Docker CLI is structured and adding a simple hello world command to the CLI. Remember, when adding a command it has to be added in the cli/common.go file and a handler method must be added to the client package located in api/client/. In my next post, we’ll go a step deeper and build a command that retrieves data from the Docker daemon.

Create
Recognize Your Peers
Content for Community-Ad