I've followed the README's example of how to bind flags into viper, but when I try to use the BindFlags() method versus explicitly binding flags by name, there's some sort of disconnect, the values never make it into viper. Minimal test case below.
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "demo",
Short: "short description",
Long: "long description",
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
//genesis.DumpSettings(l.Term)
},
PostRun: func(cmd *cobra.Command, args []string) {
},
}
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
//l.Term.WithError(err).Error("Root command failed unexpectedly.")
}
}
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().String("cfgFile", ".default", "config file (default is $HOME/.conf.yaml)")
RootCmd.PersistentFlags().BoolP("debug", "D", false, "Enable debug mode. [POSSIBLE PERFORMANCE IMPLICATIONS]")
// 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")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {}
func init() {
// These two invocations DO correctly bind the flags into the config layer.
//viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug"))
//viper.BindPFlag("cfgFile", RootCmd.PersistentFlags().Lookup("cfgFile"))
// This is the invocation that fails to correctly bind the CLI flags into the config layer.
viper.BindPFlags(RootCmd.Flags())
}
func main() {
RootCmd.Execute()
fmt.Println(viper.GetBool("debug"))
fmt.Println(viper.GetString("cfgFile"))
// Expected output:
// true
// .default
}
The documentation ( https://godoc.org/github.com/spf13/viper#BindPFlags ) seems to indicate that this should be all that's necessary to allow config value lookups to also reference set flags. I do not discount the possibility that I'm misunderstanding but because the README only covers explicitly binding individual flag values I can't be sure.
The confusion here is that the code compiles and runs without complaint using BindPFlags() but values passed via command-line don't seem to be available via viper's Get* methods.
My application will eventually have dozens upon dozens of commands, subcommands, and flag sets with common and uncommon flags between each. I'd like to be able to bind-once and not have to update my bindings any time I move, rename, or add flags.
I too just ran across something similar:
This seem to have been working at some point for me
_(did some refactoring and now I can't figure out why it's no longer working, if it ever was)_:
cmd.Flags().String("address", "", "some address")
viper.BindPFlag("address", cmd.Flags().Lookup("address"))
When looking up:
fmt.Println("flag:", viper.GetString("address")) // Returns empty
fmt.Println(c.Flags().GetString("address")) // Returns value set in 'address'
Update
I discovered this quit working when I used the same _flag_ name in a different command.
Is this expected, can you not reuse flag names across commands
Not sure if this is in the docs somewhere, but changing out cmd.Flags().String(...) with cmd.Flags().StringVar(...) works when using the same name, I just need to have the pointer declared for the var.
Any updates?
Any updates?
Thanks @juztin for your solution. Just to clarify; if one wants to use the BindPFlags functionality for multiple flags, they have to use _one pointer_ for all flags to bind.
i.e.:
// constants.go (used by both commands)
var (
serverHostPortFlag string
configFileFlag string
)
// cmd1.go
applyCmd.PersistentFlags().StringVarP(&serverHostPortFlag, serverHostPortKey, "s", constants.DHCPDDHostPortDefault, constants.HostPortDocs)
// cmd2.go
deleteCmd.PersistentFlags().StringVarP(&serverHostPortFlag, serverHostPortKey, "s", constants.DHCPDDHostPortDefault, constants.HostPortDocs)
Ran into this as well. This did not work as I had expected:
err := viper.BindPFlags(cmd.Flags())
fmt.Printf("bind error: %v\n", err)
cobraValue, err := cmd.Flags().GetString("server_socket")
fmt.Printf("Cobra value: %s, err: %v\n", cobraValue, err)
fmt.Printf("Viper value: %s\n", v.GetString("server_socket"))
Result:
$ go run main.go serve --server_socket=127.0.0.1:2222
bind error: <nil>
Cobra value: 127.0.0.1:2222, err: <nil>
Viper value: 127.0.0.1:1111
_However_, iterating over those flags and binding them individually _does_ work:
cmd.Flags().VisitAll(func (flag *pflag.Flag) {
v.BindPFlag(flag.Name, flag)
})
fmt.Printf("Viper: %s\n", v.GetString("server_socket"))
$ go run main.go serve --server_socket=127.0.0.1:2222
Viper: 127.0.0.1:2222
No changes to the flags themselves in between "not working" and "working". These are persistent flags, but not of the Var family.
Most helpful comment
I too just ran across something similar:
This seem to have been working at some point for me
_(did some refactoring and now I can't figure out why it's no longer working, if it ever was)_:
When looking up:
Update
I discovered this quit working when I used the same _flag_ name in a different command.
Is this expected, can you not reuse flag names across commands
Not sure if this is in the docs somewhere, but changing out
cmd.Flags().String(...)withcmd.Flags().StringVar(...)works when using the same name, I just need to have the pointer declared for the var.