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" ) const ( defGitlabHost = "gitlab.sweetwater.com" defProjectsPath = "~/work/projects" defLogLevel = "info" ) var conf config.Config var plog *pterm.Logger var rootCmd = &cobra.Command{ Use: "gitlab-project-manager", Short: "Find and use GitLab projects locally", Long: `Finds GitLab projects using fuzzy-find, remembering your chosen term for the project as an alias, and offers helpful shortcuts for moving around in projects and opening your code`, 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 { plog.Error("Failed to execute command", plog.Args("err", err)) os.Exit(1) } } func init() { cobra.EnableTraverseRunHooks = true cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.gitlab-project-manager.yaml)") rootCmd.PersistentFlags().String("gitlabHost", defGitlabHost, "GitLab Hostname (e.g. gitlab.com)") rootCmd.PersistentFlags().String("gitlabToken", "", "GitLab Tokenname (e.g. gitlab.com)") rootCmd.PersistentFlags().String("projectPath", defProjectsPath, "Sets a path for local clones of projects") rootCmd.PersistentFlags().String("logLevel", defLogLevel, "Default log level -- info, warn, error, debug") 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) viper.SetConfigType("yaml") viper.SetConfigName(".gitlab-project-manager") } viper.AutomaticEnv() viper.ReadInConfig() checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config // Configure pretty logger plog = pterm.DefaultLogger.WithLevel(getPtermLogLevel(viper.GetString("logLevel"))) 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)) } 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) } }