Why is Cobra not reading my configuration file?
Asked Answered
B

4

8

The documentation in Cobra and Viper are confusing me. I did cobra init fooproject and then inside the project dir I did cobra add bar. I have a PersistentFlag that is named foo and here is the init function from the root command.

func Execute() {
    if err := RootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }

    fmt.Println(cfgFile)
    fmt.Println("fooString is: ", fooString)
}


func init() {
    cobra.OnInitialize(initConfig)

    // Here you will define your flags and configuration settings.
    // Cobra supports Persistent Flags, which, if defined here,
    // will be global for your application.

    RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fooproject.yaml)")
    RootCmd.PersistentFlags().StringVar(&fooString, "foo", "", "loaded from config")

    viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))
    // Cobra also supports local flags, which will only run
    // when this action is called directly.
    RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

My configuration file looks like this...

---
foo: aFooString

And when I call go run main.go I see this...

A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
  fooproject [command]

Available Commands:
  bar         A brief description of your command
  help        Help about any command

Flags:
      --config string   config file (default is $HOME/.fooproject.yaml)
      --foo string      loaded from config
  -h, --help            help for fooproject
  -t, --toggle          Help message for toggle

Use "fooproject [command] --help" for more information about a command.

fooString is:

When I call go run main.go bar I see this...

Using config file: my/gopath/github.com/user/fooproject/.fooproject.yaml
bar called

fooString is:

So it is using the configuration file, but neither one of them seems to be reading it. Maybe I am misunderstanding the way that Cobra and Viper work. Any ideas?

Buckwheat answered 8/5, 2017 at 12:32 Comment(0)
P
11

To combine spf13/cobra and spf13/viper, first define the flag with Cobra:

RootCmd.PersistentFlags().StringP("foo", "", "loaded from config")

Bind it with Viper:

viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))

And get the variable via the Viper's method:

fmt.Println("fooString is: ", viper.GetString("foo"))
Paddle answered 8/5, 2017 at 14:48 Comment(1)
This approach works, but I think the question points towards a different problem that I did not figure out as well: The OP uses StringVar as opposed to StringP to get the value set in the string pointer passed to it. Cobra & Viper linked together don't seem to propagate values into those passed value pointers. If anyone could provide a pointer how to make that work, feel free to post another answer.Interbreed
A
5

Since Viper values are somewhat inferior to pflags (e.g. no support for custom data types), I was not satisfied with answer "use Viper to retrieve values", and ended up writing small helper type to put values back in pflags.

type viperPFlagBinding struct {
        configName string
        flagValue  pflag.Value
}

type viperPFlagHelper struct {
        bindings []viperPFlagBinding
}

func (vch *viperPFlagHelper) BindPFlag(configName string, flag *pflag.Flag) (err error) {
        err = viper.BindPFlag(configName, flag)
        if err == nil {
                vch.bindings = append(vch.bindings, viperPFlagBinding{configName, flag.Value})
        }
        return
}

func (vch *viperPFlagHelper) setPFlagsFromViper() {
        for _, v := range vch.bindings {
                v.flagValue.Set(viper.GetString(v.configName))
        }
}


func main() {
        var rootCmd = &cobra.Command{}
        var viperPFlagHelper viperPFlagHelper

        rootCmd.PersistentFlags().StringVar(&config.Password, "password", "", "API server password (remote HTTPS mode only)")
        viperPFlagHelper.BindPFlag("password", rootCmd.Flag("password"))

        rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
                err := viper.ReadInConfig()
                if err != nil {
                        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
                                return err
                        }
                }

                viperPFlagHelper.setPFlagsFromViper()

                return nil
        }
}
Astrolabe answered 6/7, 2018 at 22:49 Comment(0)
L
4

For those facing the same issue, the problem is on this call:

cobra.OnInitialize(initConfig)

The function initConfig is not executed directly but append to an array of initializers https://github.com/spf13/cobra/blob/master/cobra.go#L80

So, the values stored in your config file will be loaded when the Run field is executed:

  rootCmd = &cobra.Command{
    Use:   "example",
    Short: "example cmd",
    Run: func(cmd *cobra.Command, args []string) { // OnInitialize is called first
      fmt.Println(viper.AllKeys())
    },  
  }
Longhand answered 24/3, 2020 at 21:45 Comment(1)
Ok, but root command in most cases does not have "Run" function and it means it will never been executed? I found what during debug ReadInConfig() is called from init() of root command is not completed before sub command is evaluated. During regular execution this works fine. Do you suggest duplicate call of ReadInConfig in init() of each sub command?Cousingerman
H
1

@WGH 's answer is correct, you can do something with the persistent flag in rootCmd.PersistenPreRun, which will be available in all other sub commands.

var rootCmd = &cobra.Command{
    PersistentPreRun: func(_ *cobra.Command, _ []string) {
        if persistentflag {
            // do something with persistentflag
        }
    },
Hygrometric answered 27/7, 2021 at 3:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.