github.com/tommi2day/pwcli@v0.0.0-20240317203041-4d1177a5ab91/cmd/rootcmd.go (about)

     1  // Package cmd commands
     2  package cmd
     3  
     4  import (
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/tommi2day/gomodules/common"
    12  
    13  	"golang.org/x/exp/slices"
    14  
    15  	"github.com/tommi2day/gomodules/pwlib"
    16  
    17  	"github.com/mitchellh/go-homedir"
    18  	log "github.com/sirupsen/logrus"
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/viper"
    21  	prefixed "github.com/x-cray/logrus-prefixed-formatter"
    22  )
    23  
    24  var (
    25  	keydir         string
    26  	datadir        string
    27  	app            string
    28  	keypass        string
    29  	cfgFile        string
    30  	method         string
    31  	pc             *pwlib.PassConfig
    32  	debugFlag      = false
    33  	infoFlag       = false
    34  	noLogColorFlag = false
    35  	unitTestFlag   = false
    36  
    37  	// RootCmd entry point to start
    38  	RootCmd = &cobra.Command{
    39  		Use:           "pwcli",
    40  		Short:         "pwcli – Password generation and encryption Tools",
    41  		Long:          ``,
    42  		SilenceErrors: true,
    43  	}
    44  )
    45  
    46  const (
    47  	// allows you to override any config values using
    48  	// env APP_MY_VAR = "MY_VALUE"
    49  	// e.g. export APP_LDAP_USERNAME test
    50  	// maps to ldap.username
    51  	configEnvPrefix = "PW"
    52  	configName      = "pwcli"
    53  	configType      = "yaml"
    54  	typeVault       = "vault"
    55  	typeGopass      = "gopass"
    56  	typeKMS         = "kms"
    57  	typePlain       = "plain"
    58  	typeEnc         = "enc"
    59  	typeGPG         = "gpg"
    60  	typeGO          = "go"
    61  	typeOpenSSL     = "openssl"
    62  	defaultType     = "openssl"
    63  )
    64  
    65  func init() {
    66  	cobra.OnInitialize(initConfig)
    67  	RootCmd.PersistentFlags().BoolVarP(&debugFlag, "debug", "", false, "verbose debug output")
    68  	RootCmd.PersistentFlags().BoolVarP(&infoFlag, "info", "", false, "reduced info output")
    69  	RootCmd.PersistentFlags().BoolVarP(&unitTestFlag, "unit-test", "", false, "redirect output for unit tests")
    70  	RootCmd.PersistentFlags().BoolVarP(&noLogColorFlag, "no-color", "", false, "disable colored log output")
    71  	RootCmd.PersistentFlags().StringVarP(&app, "app", "a", configName, "name of application")
    72  	RootCmd.PersistentFlags().StringVarP(&keydir, "keydir", "K", "", "directory of keys")
    73  	RootCmd.PersistentFlags().StringVarP(&datadir, "datadir", "D", "", "directory of password files")
    74  	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file name")
    75  	RootCmd.PersistentFlags().StringVarP(&method, "method", "m", defaultType, "encryption method (openssl|go|gopass|enc|plain|vault)")
    76  	// don't have variables populated here
    77  	if err := viper.BindPFlags(RootCmd.PersistentFlags()); err != nil {
    78  		log.Fatal(err)
    79  	}
    80  }
    81  
    82  // Execute run application
    83  func Execute() {
    84  	if err := RootCmd.Execute(); err != nil {
    85  		fmt.Println(err)
    86  		os.Exit(1)
    87  	}
    88  }
    89  
    90  func initConfig() {
    91  	viper.SetConfigType(configType)
    92  	viper.SetConfigName(configName)
    93  	if cfgFile == "" {
    94  		// Use config file from the flag.
    95  		// Find home directory.
    96  		home, err := homedir.Dir()
    97  		if err != nil {
    98  			fmt.Println(err)
    99  			os.Exit(1)
   100  		}
   101  
   102  		// Search config in $HOME/etc and current directory.
   103  		etc := path.Join(home, "etc")
   104  		viper.AddConfigPath(etc)
   105  		viper.AddConfigPath(".")
   106  	} else {
   107  		// set filename form cli
   108  		viper.SetConfigFile(cfgFile)
   109  	}
   110  
   111  	// env var overrides
   112  	viper.AutomaticEnv() // read in environment variables that match
   113  	viper.SetEnvPrefix(configEnvPrefix)
   114  	// env var `LDAP_USERNAME` will be mapped to `ldap.username`
   115  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
   116  
   117  	// If a config file is found, read it in.
   118  	haveConfig, err := processConfig()
   119  
   120  	// check flags
   121  	processFlags()
   122  
   123  	// logger settings
   124  	log.SetLevel(log.ErrorLevel)
   125  	switch {
   126  	case debugFlag:
   127  		// report function name
   128  		log.SetReportCaller(true)
   129  		log.SetLevel(log.DebugLevel)
   130  	case infoFlag:
   131  		log.SetLevel(log.InfoLevel)
   132  	}
   133  
   134  	logFormatter := &prefixed.TextFormatter{
   135  		DisableColors:   noLogColorFlag,
   136  		FullTimestamp:   true,
   137  		TimestampFormat: time.RFC1123,
   138  	}
   139  	log.SetFormatter(logFormatter)
   140  
   141  	if unitTestFlag {
   142  		log.SetOutput(RootCmd.OutOrStdout())
   143  	}
   144  	// debug config file
   145  	if haveConfig {
   146  		log.Debugf("found configfile %s", cfgFile)
   147  	} else {
   148  		log.Debugf("Error using %s config: %s", configType, err)
   149  	}
   150  
   151  	// validate method
   152  	if method == "" {
   153  		method = defaultType
   154  		log.Debugf("use default method %s ", defaultType)
   155  	}
   156  	if !slices.Contains(pwlib.Methods, method) {
   157  		fmt.Println("Invalid method:", method)
   158  		os.Exit(1)
   159  	}
   160  	if method == typeVault || method == typeKMS || method == typePlain || method == typeEnc {
   161  		keypass = ""
   162  	}
   163  	// set pwlib config
   164  	pc = pwlib.NewConfig(app, datadir, keydir, keypass, method)
   165  }
   166  
   167  // processConfig reads in config file and ENV variables if set.
   168  func processConfig() (bool, error) {
   169  	err := viper.ReadInConfig()
   170  	haveConfig := false
   171  	if err == nil {
   172  		cfgFile = viper.ConfigFileUsed()
   173  		haveConfig = true
   174  		viper.Set("config", cfgFile)
   175  		a := viper.GetString("app")
   176  		if len(a) > 0 {
   177  			app = a
   178  		}
   179  	}
   180  	return haveConfig, err
   181  }
   182  
   183  func processFlags() {
   184  	if common.CmdFlagChanged(RootCmd, "debug") {
   185  		viper.Set("debug", debugFlag)
   186  	}
   187  	if common.CmdFlagChanged(RootCmd, "info") {
   188  		viper.Set("info", infoFlag)
   189  	}
   190  	if common.CmdFlagChanged(RootCmd, "no-color") {
   191  		viper.Set("no-color", noLogColorFlag)
   192  	}
   193  	if keydir == "" {
   194  		keydir = viper.GetString("keydir")
   195  	}
   196  	if datadir == "" {
   197  		datadir = viper.GetString("datadir")
   198  	}
   199  	keypass = viper.GetString("keypass")
   200  	debugFlag = viper.GetBool("debug")
   201  	infoFlag = viper.GetBool("info")
   202  	method = viper.GetString("method")
   203  }