Understanding the Synergy Between Cobra and Viper
In this lesson, we will revisit Cobra and you'lllearn about the synergy between Cobra and Viper.
We'll cover the following
Cobra and Viper were designed by the same author in order to work together. The integration is very simple and smooth.
Overview
As you recall, Cobra has fantastic support for parsing command-line arguments, but it doesn’t offer any built-in capabilities for managing any other type of configuration. This is all by design to let Cobra focus on the command-line and rely on Viper for all other forms of configuration.
Cobra and Viper
Command-line arguments or flags are different from all other forms of configuration because they are not used just for configuration. When you run a command-line program and provide it some command-line arguments, some of the arguments will be the command itself and possibly some sub-commands, some other arguments may be configuration flags and yet other arguments may be inputs.
As developers, we need to tease out all these aspects from this list of strings. This is what Cobra does so well. However, when we are left with the configuration flags we want to combine them with all other forms of configuration and use the command-line configuration flags to override other forms of configuration. This is exactly where Viper shines.
By combining Cobra and Viper, we get the best of both worlds. Cobra will parse the command-line for us and extract the configuration flags and Viper will incorporate the command-line configuration flags with all other configuration sources.
Let’s see how it’s done.
Binding Cobra flags
If you recall, this is how we defined a Boolean check flag for our basic calculator program
var check bool
func init() {
addCmd.Flags().BoolVar(
&check,
"check",
false,
"check controls if overflow/underflow check is performed")
rootCmd.AddCommand(addCmd)
}
By the magic of Cobra flag library, it populated the check variable with true or false depending on if the --check
command-line flag was specified or not.
This is fine when the --check command-line argument is specified because command-line arguments override all other forms of configuration. However, what if the user didn’t specify the command-line argument? With the current implementation, the default value of false
will be used and no check will be performed. If the check can also be configured using an environment variable or a configuration file, we want to use those values.
The way to go about it is to bind the --check
command-line flag to Viper, so the program will just ask Viper if it should check for overflow/underflow or not. In addition to integrating all forms of configuration it also saves the developer from defining those check
Boolean variables in each command.
Here is the init()
function of the Add command. It is very similar to the previous version except we use the Flags().Bool()
function instead of the Flags.BoolVar()
because we don’t bind the flag to a variable anymore. Instead, we bind it to Viper.
func init() {
addCmd.Flags().Bool(
"check",
false,
"check controls if overflow/underflow check is performed")
err := viper.BindPFlag("check", addCmd.Flags().Lookup("check"))
if err != nil {
panic("Unable to bind flag")
}
rootCmd.AddCommand(addCmd)
}
Here is the Add command itself:
var addCmd = &cobra.Command{
Use: "add",
Short: "Add two integers",
Long: `Add two integers a and b; result = a + b`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
var a, b int
var err error
a, err = strconv.Atoi(args[0])
if err != nil {
panic("Arguments to `add` must be integers")
}
b, err = strconv.Atoi(args[1])
if err != nil {
panic("Arguments to `add` must be integers")
}
result := calc.Add(a, b, viper.GetBool("check"))
fmt.Println(result)
},
}
The only difference is when calling calc.Add() the check argument is now coming from Viper via viper.GetBool("check")
instead of the check variable.
Get hands-on with 1400+ tech skills courses.