为什么Cobra没有读取我的配置文件?

huangapple go评论100阅读模式
英文:

Why is Cobra not reading my configuration file?

问题

Cobra和Viper的文档让我感到困惑。我执行了cobra init fooproject,然后在项目目录中执行了cobra add bar。我有一个名为fooPersistentFlag,这是root命令的初始化函数。

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)

    // 在这里定义你的标志和配置设置。
    // Cobra支持Persistent Flags,如果在这里定义,
    // 将成为应用程序的全局标志。

    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还支持本地标志,只有在直接调用此操作时才会运行。
    RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

我的配置文件如下所示...

---
foo: aFooString

当我运行go run main.go时,我看到了这个...

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:

当我运行go run main.go bar时,我看到了这个...

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

fooString is:

所以它正在使用配置文件,但似乎没有读取其中的任何内容。也许我对Cobra和Viper的工作方式有误解。有什么想法吗?

英文:

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?

答案1

得分: 11

要将spf13/cobraspf13/viper结合起来,首先在Cobra中定义标志:

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

然后使用Viper进行绑定:

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

通过Viper的方法获取变量:

fmt.Println("fooString is: ", viper.GetString("foo"))
英文:

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"))

答案2

得分: 5

由于Viper值在某些方面不如pflags(例如不支持自定义数据类型),我对“使用Viper检索值”的答案不满意,最终编写了一个小助手类型来将值放回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
    }
}

以上是代码的翻译部分。

英文:

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
        }
}

答案3

得分: 4

对于遇到相同问题的人来说,问题出在这个调用上:

cobra.OnInitialize(initConfig)

函数initConfig不是直接执行的,而是附加到一个初始化器数组中https://github.com/spf13/cobra/blob/master/cobra.go#L80

因此,当执行Run字段时,将加载存储在配置文件中的值:

  rootCmd = &cobra.Command{
    Use:   "example",
    Short: "example cmd",
    Run: func(cmd *cobra.Command, args []string) { // OnInitialize is called first
      fmt.Println(viper.AllKeys())
    },  
  }
英文:

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())
    },  
  }

答案4

得分: 1

@WGH的回答是正确的,你可以在rootCmd.PersistenPreRun中使用persistent标志来执行一些操作,这个标志将在所有其他子命令中都可用。

var rootCmd = &cobra.Command{
    PersistentPreRun: func(_ *cobra.Command, _ []string) {
        if persistentflag {
            // 使用persistentflag做一些操作
        }
    },
}
英文:

@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
		}
	},

huangapple
  • 本文由 发表于 2017年5月8日 20:32:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/43847791.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定