Skip to content

How to make it works for subcommands #7

@rgarrigue

Description

@rgarrigue

Hi there

Long story short, I started from cobra-cli boilerplate, and taking snippets of this repository I got a root command working.

But my issue now is that subcommands don't take env / config file in account, just flags. After trials & errors, seems I would need to add ViperCfg.ReadInConfig() in all the subcommands' init. Not really elegant, I guess I'm missing something as PersistentPreRun should propagate the viper config reading. But well, beginner's struggles.

So wondering what I'm missing here / how would you deal with subcommands in your setup ?

Here's my root.go

package cmd

import (
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

var logLevel string

var rootCmd = &cobra.Command{
	Use:   "tool",
	Short: "tool is a tool.",
	Long:  `tool allow you to .`,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {

		if strings.ToLower(logFormat) == "text" {
			log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).
				With().
				Timestamp().
				Caller().
				Logger()
		}

		switch strings.ToLower(logLevel) {
		case "trace":
			zerolog.SetGlobalLevel(zerolog.TraceLevel)
		}
	},
}

func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
	cobra.OnInitialize(initializeConfig)

	rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Configuration file (default /etc/tool/config.yaml > $HOME/.tool/config.yaml > ./.config.yaml)")
	rootCmd.PersistentFlags().StringVarP(&logLevel, "loglevel", "l", "INFO", "Log level. From the less verbose to the most, panic, fatal, error, warn, info, debug, trace")
}

var viperCfg viper.Viper

func initializeConfig() {
	viperCfg = *viper.New()

	if configFile != "" {
		viperCfg.SetConfigFile(configFile)
	} else {
		viperCfg.SetConfigName("config")

		home, _ := os.UserHomeDir()
		viperCfg.AddConfigPath(".")
		viperCfg.AddConfigPath(home + ".config/tool")
		viperCfg.AddConfigPath("/etc/tool")
	}

	if err := viperCfg.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			log.Fatal().Msgf("Couldn't parse configuration file %s", viperCfg.ConfigFileUsed())
		}
	}

	viperCfg.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
	viperCfg.AutomaticEnv()

	bindFlags()
}

func bindFlags() {
	rootCmd.Flags().VisitAll(func(f *pflag.Flag) {
		configName := f.Name
		
		if !f.Changed && viperCfg.IsSet(configName) {
			val := viperCfg.Get(configName)
			rootCmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
		}
	})
}

And the shortened generate.go

package cmd

import (	
	"os"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
)

var valuesFile string

var generateCmd = &cobra.Command{
	Use:   "generate",
	Short: "Generate",
	Long: `Create the
	`,
	Run: generate,
}

func init() {
	rootCmd.AddCommand(generateCmd)

	generateCmd.Flags().StringVarP(&valuesFile, "values", "f", "./values.yaml", "Path of the")

        // insert ViperCfg.ReadInConfig() to get the config file settings taken in account
}

func generate(cmd *cobra.Command, args []string) {
	
	_, errVal := os.Stat(valuesFiles)
	if os.IsNotExist(errVal) {
		log.Fatal().Msgf("Values file '%s' doesn't exist", valuesFiles)
	}

	// ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions