Go: Viper and Cobra
Mihalis Tsoukalos explains how to develop fully-functional command line utilities with complete options using Viper and Cobra.
Mihalis Tsoukalos explains how to develop fully functional command line utilities with complete options using Viper, Cobra and Go. Just dandy.
Viper and Cobra are two Go packages that enable you to create powerful command line tools such as docker and kubectl. Among other things, in this tutorial you’ll learn how to tell Viper to read JSON and YAML configuration files and how to create command line utilities with commands and subcommands using Cobra. You’ll need to download both Go packages before using them for the first time because they’re not (yet) part of the Standard Go Library, which is the subject of the first section.
Snakes on a terminal
You can download Viper as well as another handy Go package related to Viper named pflag by executing the following commands:
$ go get github.com/spf13/viper
$ go get github.com/spf13/pflag
Similarly, before using Cobra for the first time you should execute the following commands in order to obtain all the required files:
$ go get github.com/spf13/cobra/cobra
$ go get -u github.com/spf13/cobra/cobra
Along with the installation of the Cobra Go package, you’ll also get a command line tool named cobra that simplifies the use of the Cobra Go package. The cobra CLI will be used in this tutorial for creating new Cobra applications and for adding new commands to existing Cobra applications, because it’s a great time saver. To make sure that everything is working as expected execute the following Go program, which is named
usevc.go and imports the two packages without actually doing something with them:
package main
import (
“fmt” _ “github.com/spf13/cobra” _ “github.com/spf13/viper” )
func main() { fmt.println(“everything is OK!”) }
The underscore character in front of the two packages tells Go that it’s fine not to use these two packages in your program (Go will complain otherwise). If both Cobra and Viper are successfully installed, usevc.go will generate the “Everything is OK!” message. Otherwise you’ll see an error message and the program won’t get executed.
If you don’t want to have many packages installed on your Linux machine, there’s always the choice of using a Docker image to do your job. Although you’ll need to learn how to use Docker and work with Docker images, you’ll see many benefits in the long run from that choice, especially if you’re using multiple Linux machines with various Linux distros for development.
Bitten by the Viper
In this section we’re going to learn how to convert an existing Go program that uses the flag package into using Viper. This is an easy process that shows that the developers of Viper are good people! For that purpose you’ll need Viper as well as the pflag package that you downloaded in the previous section.
We’ll start with a Go program that uses the flag package named usingflag.go and convert it into a new program that uses Viper and is called usingviper.go. The good thing is that you now know how to use both Viper and pflag for working with command line arguments. The usingflag.go command line utility supports a single command line option named i.
The screenshot (left) shows the Go code of both usingflag.go and usingviper.go whereas the screenshot (right) shows the kind of output that usingviper.go generates including the way Viper handles erroneous input. The first screenshot also reveals that converting a program which uses the flag package into a program that uses Viper is a straightforward process as long as you know what you’re doing. Finally, Viper enables you to easily get and set the values of UNIX environment variables. This is illustrated in the next Go code, which is included in envviper.go: func main() { VIPER.BINDENV(“GOMAXPROCS”) val := VIPER.GET(“GOMAXPROCS”) fmt.println(val) VIPER.SET(“GOMAXPROCS”, 10) val = VIPER.GET(“GOMAXPROCS”) fmt.println(val) }
So, you’ll need to call viper.bindenv() to start working with an environment variable, viper.get() to
get the current value of it – or <nil> in case the environment variable is unset – and viper.set() to set the value of an environment variable. Executing envviper.go will generate the following output:
$ go run envviper.go
<nil>
10
However, as you’ll see in the sections that follow, Viper can perform many more advanced tasks.
On the JSON
Let’s find out how to obtain the configuration of a Go application from plain text configuration files. In this case it will be a text file written in the JSON format. The Go code for the command line utility is saved in jsonviper.go. The core functionality of jsonviper.go is implemented in the following Go code:
viper.setconfigtype(“json”) viper.setconfigfile(”./myconfig.json”) fmt.printf(“using config: %s\n”, viper.configfileused()) viper.readinconfig()
So, viper.setconfigtype() defines the expected format of the configuration file, viper.setconfigfile() sets the path of the configuration file and viper. Readinconfig() reads and parses that file. If you forget to call viper.readinconfig() then your program won’t work as expected.
Note that the path of the JSON file is hard-coded into jsonviper.go for reasons of simplicity – the next section will show you how to overcome that restriction in many ways. Finally, viper.configfileused() prints the path of the used configuration file. This isn’t necessary in this case because the path and the name of the configuration file are hard-coded, but can be handy in other situations.
The contents of the JSON configuration file, which is saved as myconfig.json, can be seen in the screenshot on page 94. It also shows the output of the jsonviper.go utility for various types of input including erroneous ones. Please note that you’re responsible for making sure that the configuration file you want to use is there – Viper won’t check it for you.
Hearing YAML
YAML is a popular file format for text files and so it comes as no surprise that Viper also supports YAML configuration files. This time the filename of the YAML configuration file will be given as a command line argument to the utility.
Additionally, this time the viper.addconfigpath() function will add three search paths, which are places where Viper will look for configuration files. The Go code for the command line utility is saved in yamlviper.go. The core functionality of yamlviper.go is implemented with the following Go code:
var configfile *string = flag.string(“c”, “myconfig”, “Setting the configuration file”) flag.parse()
_, err := os.stat(*configfile) if err == nil { fmt.println(“using User Specified Configuration file!”) viper.setconfigfile(*configfile) } else { viper.setconfigname(*configfile) ... }