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 --checkcommand-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 1200+ tech skills courses.