148 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"os"
 | |
| 	"os/signal"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pterm/pterm"
 | |
| 	"github.com/spf13/cobra"
 | |
| 	"github.com/spf13/viper"
 | |
| 
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	conf config.Config
 | |
| 	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(FlagConfig, "",
 | |
| 		"config file (default is "+defConfigPath+")")
 | |
| 	rootCmd.PersistentFlags().String(FlagPath, "",
 | |
| 		"Sets a path for local clones of projects")
 | |
| 	rootCmd.PersistentFlags().String(FlagLogLevel, defLogLevel,
 | |
| 		"Default log level -- info, warn, error, debug")
 | |
| 	rootCmd.PersistentFlags().StringSlice(FlagRemote, []string{},
 | |
| 		"Specify remotes by host for any sub-command. Provide multiple times or comma delimited.")
 | |
| 
 | |
| 	// Flag autocompletion
 | |
| 	rootCmd.RegisterFlagCompletionFunc(FlagLogLevel, validLogLevelsFunc)
 | |
| 	rootCmd.RegisterFlagCompletionFunc(FlagRemote, validRemotesFunc)
 | |
| 
 | |
| 	viper.BindPFlags(rootCmd.PersistentFlags())
 | |
| }
 | |
| 
 | |
| // initConfig reads in config file and ENV variables if set.
 | |
| func initConfig() {
 | |
| 	cfgFile := viper.GetString(FlagConfig)
 | |
| 	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(FlagLogLevel))).
 | |
| 		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()&0o004 == 0o004 {
 | |
| 		plog.Error("Configuration is world-readable. Recomment 0400.",
 | |
| 			plog.Args("mode", stat.Mode().String()))
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 |