diff --git a/cmd/download.go b/cmd/download.go index 0111b53..fdb2233 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -307,12 +307,3 @@ func init() { rootCmd.AddCommand(&downloadCmd) } - -// helper to write downloaded files -func writeFiles(path string, body []byte) { - var err = os.WriteFile(path, body, 0o755) - if err != nil { - log.Error().Err(err).Msg("failed to write file(s) from download") - os.Exit(1) - } -} diff --git a/cmd/root.go b/cmd/root.go index f5b1fc6..9f233f6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,10 +4,13 @@ import ( "fmt" "net/http" "os" + "strings" logger "git.towk2.me/towk/makeshift/pkg/log" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" ) var ( @@ -30,6 +33,9 @@ var rootCmd = cobra.Command{ log.Error().Err(err).Msg("failed to initialize logger") os.Exit(1) } + + // You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well + err = initializeConfig(cmd) }, Run: func(cmd *cobra.Command, args []string) { // try and set flags using env vars @@ -127,3 +133,58 @@ func writeFiles(path string, body []byte) { os.Exit(1) } } + +// Bind each cobra flag to its associated viper configuration (config file and environment variable) +func bindFlags(cmd *cobra.Command, v *viper.Viper) { + cmd.Flags().VisitAll(func(f *pflag.Flag) { + // Determine the naming convention of the flags when represented in the config file + configName := f.Name + + // Apply the viper config value to the flag when the flag is not set and viper has a value + if !f.Changed && v.IsSet(configName) { + val := v.Get(configName) + cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + } + }) +} + +func initializeConfig(cmd *cobra.Command) error { + v := viper.New() + + // Set the base name of the config file, without the file extension. + v.SetConfigName("makeshift") + + // Set as many paths as you like where viper should look for the + // config file. We are only looking in the current working directory. + v.AddConfigPath(".") + + // Attempt to read the config file, gracefully ignoring errors + // caused by a config file not being found. Return an error + // if we cannot parse the config file. + if err := v.ReadInConfig(); err != nil { + // It's okay if there isn't a config file + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return err + } + } + + // When we bind flags to environment variables expect that the + // environment variables are prefixed, e.g. a flag like --number + // binds to an environment variable STING_NUMBER. This helps + // avoid conflicts. + v.SetEnvPrefix("MAKESHIFT") + + // Environment variables can't have dashes in them, so bind them to their equivalent + // keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + + // Bind to environment variables + // Works great for simple config names, but needs help for names + // like --favorite-color which we fix in the bindFlags function + v.AutomaticEnv() + + // Bind the current command's flags to viper + bindFlags(cmd, v) + + return nil +}