package cmd import ( "context" "os" "os/signal" "strings" "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" "gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/config" ) var conf config.Config var plog *pterm.Logger var rootCmd = &cobra.Command{ Use: "gitlab-project-manager", Aliases: []string{"gpm"}, Short: "Find and use GitLab projects locally", Long: rootCmdLong, PersistentPreRun: initRootCmd, } // Hook traversal is enabled, so this will be run for all // sub-commands regardless of their registered pre-hooks func initRootCmd(cmd *cobra.Command, args []string) { initProjectPath(cmd, args) } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt) defer cncl() err := rootCmd.ExecuteContext(ctx) if err != nil { pterm.Error.Printfln(pterm.LightYellow("Command failed, " + err.Error())) os.Exit(1) } } func init() { cobra.EnableTraverseRunHooks = true cobra.OnInitialize(initConfig) // Global flags rootCmd.PersistentFlags().String("config", "", "config file (default is "+defConfigPath+")") rootCmd.PersistentFlags().String("projectPath", "", "Sets a path for local clones of projects") rootCmd.PersistentFlags().String("logLevel", defLogLevel, "Default log level -- info, warn, error, debug") rootCmd.PersistentFlags().StringSlice("remote", []string{}, "Specify remotes by host for any sub-command. Provide multiple times or comma delimited.") // Flag autocompletion rootCmd.RegisterFlagCompletionFunc("logLevel", validLogLevelsFunc) rootCmd.RegisterFlagCompletionFunc("remote", validRemotesFunc) viper.BindPFlags(rootCmd.PersistentFlags()) } // initConfig reads in config file and ENV variables if set. func initConfig() { cfgFile := viper.GetString("config") if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".gitlab-project-manager" (without extension). viper.AddConfigPath(home + "/.config") viper.SetConfigType("yaml") viper.SetConfigName("gitlab-project-manager") } viper.AutomaticEnv() viper.ReadInConfig() // Configure pretty logger plog = pterm.DefaultLogger. WithLevel(getPtermLogLevel(viper.GetString("logLevel"))). WithWriter(os.Stderr) if plog.Level == pterm.LogLevelDebug { pterm.EnableDebugMessages() } // Load into struct to not be so darn pythonic, retrieving // settings by untyped string "name" if err := viper.Unmarshal(&conf); err != nil { plog.Error("Failed loading config", plog.Args("err", err)) } if len(os.Args) > 0 && strings.HasPrefix(os.Args[1], "conf") { plog.Debug("Permitting missing config for config sub-command") return } else if conf.ProjectPath == "" { plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args( "do", "Try running `gitlab-project-manager config default > "+defConfigPath, )) } checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config plog.Debug("Configuration loaded", plog.Args("conf", conf)) } func getPtermLogLevel(level string) pterm.LogLevel { var pLevel pterm.LogLevel switch strings.ToLower(level) { case "error": pLevel = pterm.LogLevelError case "warn": pLevel = pterm.LogLevelWarn case "info": pLevel = pterm.LogLevelInfo case "debug": pLevel = pterm.LogLevelDebug default: pLevel = pterm.LogLevelInfo } return pLevel } // Don't allow world-readable configuration func checkConfigPerms(file string) { stat, err := os.Stat(file) if err != nil { plog.Error("Failure reading configuration", plog.Args("err", err)) return } if stat.Mode().Perm()&0004 == 0004 { plog.Error("Configuration is world-readable. Recomment 0400.", plog.Args("mode", stat.Mode().String())) os.Exit(1) } }