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?
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"))
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
}
}
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())
},
}
#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
}
},
Related
The sketch below is a command line application written using Cobra and Go. I'd like to throw an error if the value of flag1 doesn't match the regex ^\s+\/\s+. How do I do that?
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
var flag1 string
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "cobra-sketch",
Short: "Sketch for Cobra flags",
Long: "Sketch for Cobra flags",
Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Flag1 is %s\n", flag1)},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
cobra.CheckErr(rootCmd.Execute())
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra-sketch.yaml)")
rootCmd.PersistentFlags().StringVar(&flag1, "flag1", "", "Value of Flag 1")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
cobra.CheckErr(err)
// Search config in home directory with name ".cobra-sketch" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra-sketch")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
Let's say a user runs the command like this: cobra-sketch --flag1 "hello". "hello" will be stored in the var flag1 string variable you have assigned to the flag, to check if the input matches any regexp, you can do:
var rootCmd = &cobra.Command{
Use: "cobra-sketch",
...
RunE: func(cmd *cobra.Command, args []string) error {
// You can also use MustCompile if you are sure the regular expression
// is valid, it panics instead of returning an error
re, err := regexp.Compile(`^\s+\/\s+`)
if err != nil {
return err // Handle error
}
if !regexp.MatchString(flag1) {
return fmt.Errorf("invalid value: %q", flag1)
}
fmt.Printf("Flag1 is %s\n", flag1)
return nil
},
}
Ho do you test command line arguments?
I can do this:
func TestMainFunc(t *testing.T) {
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
os.Args[1] = dir
main()
}
But then I override "test.v" in an argument list of:
os.Args[0]={string} "/private/var/folder/.../___appname.go"
os.Args[1]={string} "test.v"
os.Args[2]={string} "-test.panicontext0"
os.Args[3]={string} "^QTestMainFunc\E$"
I have no idea what these arguments are doing, but I suspect it is not good to override any of them - so how can you test arguments?
To expand upon Adrian's comment a bit, I would separate flag parsing from configuration entirely. Here's an example:
type Config struct {
verbose bool
greeting string
level int
}
func parseArgs(progname string, args []string) (config *Config, output string, err error) {
flags := flag.NewFlagSet(progname, flag.ContinueOnError)
var buf bytes.Buffer
flags.SetOutput(&buf)
var conf Config
flags.BoolVar(&conf.verbose, "verbose", false, "set verbosity")
flags.StringVar(&conf.greeting, "greeting", "", "set greeting")
flags.IntVar(&conf.level, "level", 0, "set level")
err = flags.Parse(args)
if err != nil {
return nil, buf.String(), err
}
return &conf, "", nil
}
The parseArgs function parses your program's configuration from command-line arguments, and is easily testable in isolation from the rest of the program.
So the flow in main would be:
func main() {
conf, output, err := parseArgs(os.Args[0], os.Args[1:])
if err != nil {
// ...
}
realMain(conf)
}
And now you can test parseArgs and realMain completely separately in unit tests.
This also opens up the possibility to have multiple sources for configuration: e.g. you could also read it from env vars or config files, or command-line. Multiple readers could populate a Config struct, and all would be testable in isolation.
I'm having trouble using Cobra and Viper together. This is what I'm doing:
var options util.Config = util.Config{}
var rootCmd = &cobra.Command{
Use: "test [command] [subcommands]",
Run: func(cmd *cobra.Command, args []string) {
if err := server.Run(); err != nil {
l.Fatal(err)
}
},
}
// initConfig helps initialise configuration with a stated path
func initConfig() {
if options.Path != "" {
viper.SetConfigFile(options.Path)
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Could not use config file: ", viper.ConfigFileUsed())
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVarP(&options.Path, "config", "n", "", "Path of a configuration file")
rootCmd.PersistentFlags().StringVarP(&options.Password, "password", "d", "", "Password to access the server")
viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password"))
rootCmd.AddCommand(log.Cmd(&options))
}
func main() {
rootCmd.Execute()
}
I'm trying to retrieve the value options.Password within my subcommand (an added command within log.Cmd(&options)) however the field isn't being populated. I'm pretty sure I'm following the Cobra docs properly: https://github.com/spf13/cobra#create-rootcmd
Binding cobra flags to viper options only binds cobra flags to viper options, not vice versa. So you can access the password via
pass := viper.GetString("password")
if the password is set either via viper or cobra, but not via the variables defined in your flag definitions.
Basically, you have two options here: Either you use cobra without pointing your flags to variables, and then set your globals via various calls to viper.Get* (you can even sanitize them while being at it), or you use viper as sort of a „parameter registry“ and call viper.Get* where needed. I tend to go with the former solution.
Here is an snippet from my-tool/cmd/root.go
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
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/.my-tool.yaml)")
// 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() {
if cfgFile != "" {
fmt.Println("Config file set")
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
fmt.Println("Config file NOT set")
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
The code has been generated from the scaffolding process of cobra cli, i.e. via ~/go/cobra/init my-tool --pkg-name github.com/something/my-tool
I am trying to tentatively pass the config flag to check if the program is handling it:
▶ go run main.go --config "test"
However, though I 'd expect the init() function to make the call to cobra.OnInitialize(initConfig) and parse the flag as indicated by line:
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.my-tool.yaml)")
and finally to see one of those two messages in the if statement:
func initConfig() {
if cfgFile != "" {
fmt.Println("Config file set")
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
fmt.Println("Config file NOT set")
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
instead, all I get is the root command's help message; why is that?
edit: from what I see by adding some print statements, the initConfig() is never called (for some reason), i.e. as if cobra.OnInitialize(initConfig) does not do anything.
You need to Specify your command first
▶ go run main.go "yourcommand" --config "test"
See:
» go run main.go --config "blah"
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
...
» go run main.go preview --config "blah"
Error: open : no such file or directory
exit status 1
I'm using cobra with my Golang application. How can I get the list of commands and values that I have registered with Cobra.
If I add a root command and then a DisplayName command.
var Name = "sample_"
var rootCmd = &cobra.Command{Use: "Use help to find out more options"}
rootCmd.AddCommand(cmd.DisplayNameCommand(Name))
Will I be able to know what is the value in Name from within my program by using some Cobra function? Ideally I want to access this value in Name and use it for checking some logic.
You can use the value stored in the Name variable for performing operations within your program. An example usage of cobra is:
var Name = "sample_"
var rootCmd = &cobra.Command{
Use: "hello",
Short: "Example short description",
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
var echoCmd = &cobra.Command{
Use: "echo",
Short: "Echo description",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("hello %s", Name)
},
}
func init() {
rootCmd.AddCommand(echoCmd)
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
`
In the above code, you can see that hello is the root command and echo is a sub command. If you do hello echo, it'll echo the value sample_ which is stored in the Name variable.
You can also do something like this:
var echoCmd = &cobra.Command{
Use: "echo",
Short: "Echo description",
Run: func(cmd *cobra.Command, args []string) {
// Perform some logical operations
if Name == "sample_" {
fmt.Printf("hello %s", Name)
} else {
fmt.Println("Name did not match")
}
},
}
For knowing more about how to use cobra, you can also view my project from the below link.
https://github.com/bharath-srinivas/nephele
Hope this helps.