Closed tomgeorge closed 3 months ago
This can indeed be done in the way I described, I think I messed up my pointer arguments. If you construct a pointer to your application "wiring" and bind the flags in the root commands PreRun
, and have all child command constructors take a pointer to your application wiring you can do this.
I pushed up my fix to the above repo, for posterity. I don't know if I have a great way to update the clients that my application uses right now. I guess I will set any services that need flag values to nil
initially, and then update them in PreRunE
:
config := cmd.NewConfig()
a := &cmd.App{
Config: config,
RepoLister: nil,
}
root command constructor:
func NewRootCommand(a *App) *cobra.Command {
// rootCmd represents the base command when called without any subcommands
rootCmd := &cobra.Command{
Use: "cobra-late-bind",
Short: "A brief description of your application",
Long: `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.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("prerunE github token %s\n", a.Config.Data.Github.Token)
a.Config.BindFlags(cmd)
a.BindServices()
fmt.Printf("prerunE post bind github token %s\n", a.Config.Data.Github.Token)
return nil
},
.....
where BindServices
sets the additional wiring:
func (a *App) BindServices() {
a.RepoLister = &GhRepoLister{Cli: github.NewClient(nil).WithAuthToken(a.Config.Data.Github.Token)}
}
Feels a little ugly but I'm headed in the right direction for now
TLDR: How can I write up my application before starting cobra when my application's configuration depends on the value of a flag?
I watched Carolyn Van Slyck's excellent video at GopherCon 2019 about writing CLIs with Cobra, and read her example repository and some of the porter codebase, where the flow of execution is
Application
struct, which has aConfiguration
that is unmarshalled from viperApplication
a dependency, like soThis style of writing Cobra apps is very nice to work with because it becomes much more testable than other implementations that I have tried, where I'm wiring my application together in
PreRun
etc. You also don't need to query the viper store directly.My question is: how I would go about doing this if I have application dependencies that require a flag value?
Consider a command
foo --github-token abcdefg
. You can't create your top-levelApplication
in a fully complete state and pass that to your root command constructor because you need to wait for cobra to parse the flagsets for the various commands. You also cannot modify the*Application
that is getting passed around because you're not actually passing it by reference, so something like this would fail:I think there are two solutions to this:
github.token
to be passed in as flags, and only allow them to be defined in the config file or the environment. Allowing these things to be passed in as flags conflates configuration with options.rootCmds
application pointer updates the factory butnewChildCommand
's application pointer does notHas anybody else run into this problem, and solved it in a way that did not feel hacky? I have included some example code that reproduces this issue here