cobra
cobra copied to clipboard
Cobra picking up a flag default value from a different command?
This test Cobra app, https://github.com/kurtpeek/myCobraApp, illustrates the issue. It was scaffolded using the Cobra generator with the following commands:
cobra add serve
cobra add config
cobra add command
cobra add install -p 'commandCmd'
cobra add erase -p 'commandCmd'
In config.go
, a string variable called deviceUUID
is declared and bound to a flag using with a default value of "1234"
, similar to the following:
var deviceUUID string
// configCmd represents the config command
var configCmd = &cobra.Command{
}
func init() {
rootCmd.AddCommand(configCmd)
configCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "1234", "Device UUID")
}
Similarly in serve.go
, the same variable is given a default value of "5678":
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("deviceUUID:", deviceUUID)
fmt.Println("serve called")
},
}
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "5678", "Device UUID")
}
Finally, in erase.go
, the default value for the flag is set to "0000"
:
eraseCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "0000", "Device UUID")
When I run the erase
command, I get that the deviceUUID
is the default one defined in serve.go
:
> go run main.go command erase
deviceUUID: 5678
erase called
Why is it picking up the default value from a different command? The scaffolding code comment
Cobra supports local flags which will only run when this command is called directly
would suggest that the local flags should only run when each command is called directly?
We hit this with Skaffold which uses Cobra to configure its commands.
This seems inherent to
pflags
:BoolVarP()
usesnewBoolValue()
to allocate a container to map the provider pointer to the default value, andnewBoolValue()
immediately assigns the value.// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash. func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) { flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage) flag.NoOptDefVal = "true" }
func newBoolValue(val bool, p *bool) *boolValue { *p = val return (*boolValue)(p) }
The init() functions of all Cobra defined commands are running in the beginning of the execution of any command. You used the same variable - deviceUUID - for all commands, so the last default value will be assigned to the deviceUUID variable.
This issue is being marked as stale due to a long period of inactivity
I made this comment on the related issue on pflag but I'll re-iterate it here because after writing it I figured it makes more sense to put it here:
I'm passing a pointer to a variable expecting that it will be filled with the value of my default argument for the flag that is currently being evaluated rather than it being filled with a value from a flag that is not currently being evaluated that is in another part of my Cobra application. I don't think it's an unreasonable expectation that when I hand the library a pointer to my variable that when my flags are evaluated that the variable is either populated with the value the user provided on the command line to the application or the default value I set for the flag in that command.
Ref: https://github.com/spf13/pflag/issues/257#issuecomment-768037437
I just hit this issue in my code. This is really weird that cobra assigns default values to referenced variable even if command the flag is assigned to is not executed. This is really bad design decision and should be fixed. Information about configured flags should be cached and default values should be assigned only if flag is used by command being executed.
I digged into the source code of pflag
to fix this but the implementation is so strange that solving this would require rewriting of entire library
We just ran into this, is quite a pain to figure out why the entire CLI breaks when someone adds a new command.
We are able to work around by moving flags with the same name up into a parent command's PersistentFlags but I don't see an argument why that should have to be done
Ran into this myself now. It looks like you can get around it by just using different variables for each command, but you can still use the same flag name between them. Annoying, yes, but relatively easy to work around.
Once you understand what is going on, you can workaround it relatively easily. However depending on how complex your application is at the time you encounter the bug can make figuring it out in the first place more of a challenge.