Search code examples
gogo-modulesgo-cobra

In Cobra command-line tool, how to use the same variable for different flags?


This example Cobra app, https://github.com/kurtpeek/myCobraApp, contains a Cobra app scaffolded using the Cobra generator with the following commands:

cobra add serve
cobra add config

The directory structure is

.
├── LICENSE
├── cmd
│   ├── config.go
│   ├── root.go
│   └── serve.go
├── go.mod
├── go.sum
└── main.go

In config.go, the string variable deviceUUID is defined and bound to a flag for that command with a default value of "configDeviceUUID":

var deviceUUID string

func init() {
    rootCmd.AddCommand(configCmd)

    // Cobra supports local flags which will only run when this command
    // is called directly, e.g.:
    configCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "configDeviceUUID", "Device UUID")
    fmt.Println("deviceUUID after config init:", deviceUUID)
}

Similarly, in serve.go the deviceUUID variable is bound to a local flag:

func init() {
    rootCmd.AddCommand(serveCmd)

    serveCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "serveDeviceUUID", "Device UUID")
    fmt.Println("deviceUUID after serve init:", deviceUUID)
}

The problem is that if I run the config command without specifying the deviceUUID flag at the command line, it picks up the default value from the serve command:

> go run main.go config
deviceUUID after config init: configDeviceUUID
deviceUUID after serve init: serveDeviceUUID
deviceUUID: serveDeviceUUID
config called

What seems to be happening is that the init() functions in each file are run in alphabetical order, and the last one to run sets the default value for the flag.

How can I avoid this behavior? I would like the default value set in config.go to always apply to the config command. (I could, of course, declare separate variables like configDeviceUUID and serveDeviceUUID, but this seems a bit messy to me).


Solution

  • I do not know why you would consider that messy. You have one variable, and you set it in several init functions, so the last setting is the one that remains set: your explanation of what is happening is correct. Clearly, these are actually two different variables, it's just that at most one of them will actually be used.

    (Assuming there are other commands that do other things, perhaps none of these global variables will be used. If Go were a naturally-more-dynamic language in which everything is decided at runtime, or if you were using a more dynamic runtime setup, you could have none of them be created yet. But that's not Go: Go is full of static typing and static variables, created at link time.)

    If you really do want to use just one global variable, though, the obvious solution is to choose some sentinel value to mean not set, and make that the default value that Cobra sets. Then, if the variable holds the "not set" value upon running the command, you know the user did not supply a value.

    If the empty string is suitable as such a sentinel—usually that's the case—this is easy. Each of your commands gains a few lines:

    if deviceUUID == "" {
        deviceUUID = defaultDeviceUUID
    }
    

    and you're done.

    It might be kind of nice, or at least more convenient for you, if the cobra code saved the default-initializer values away somewhere private when you did:

    configCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "configDeviceUUID", "Device UUID")
    

    and then copied those private, saved default values into the (single) global variable when the top level command decided that the config sub-command was to be used, rather than not saving it away somewhere private, and copying it into the (single) global variable in at the time the configCmd.Flags().StringVar() function is called, but that's not how it actually works. So:

    configCmd.Flags().StringVar(&deviceUUID, "deviceUUID", "", "Device UUID")
    

    plus the above empty-string tests might suffice. Note that each init is still overwriting the (single, shared, and maybe-never-even-used) global variable each time, but all of them are setting it from its initial empty-string value to a new empty-string value, leaving it unchanged.