package cmd import ( "context" "os" "os/signal" "strings" "github.com/spf13/cobra" "github.com/spf13/viper" "gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/config" "golang.org/x/exp/slog" ) const ( defGitlabHost = "gitlab.sweetwater.com" defProjectsPath = "~/work/projects" defLogLevel = "info" ) var conf config.Config 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 { slog.Error("Failed to execute command", "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 default logger logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: getSlogLevel(viper.GetString("logLevel"))})) slog.SetDefault(logger) // Load into struct to not be so darn pythonic, retrieving // settings by untyped string "name" if err := viper.Unmarshal(&conf); err != nil { slog.Error("Failed loading config", "err", err) } slog.Debug("Configuration loaded", "conf", conf) } func getSlogLevel(level string) slog.Level { var slogLevel slog.Level switch strings.ToLower(level) { case "error": slogLevel = slog.LevelError case "warn": slogLevel = slog.LevelWarn case "info": slogLevel = slog.LevelInfo case "debug": slogLevel = slog.LevelDebug default: slogLevel = slog.LevelInfo } return slogLevel } // Don't allow world-readable configuration func checkConfigPerms(file string) { stat, err := os.Stat(file) if err != nil { slog.Error("Failure reading configuration", "err", err) return } if stat.Mode().Perm()&0004 == 0004 { slog.Error("Configuration is world-readable. Recomment 0400.", "mode", stat.Mode().String()) os.Exit(1) } }