Bug fixes, config generator
This commit is contained in:
		
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,6 +3,16 @@ | |||||||
| ## TODO | ## TODO | ||||||
|  |  | ||||||
| - Fix NPE when cache is reset or project for whatever reason leaves an orphaned alias | - Fix NPE when cache is reset or project for whatever reason leaves an orphaned alias | ||||||
|  | - Add config setters and getters | ||||||
|  | - Add TTL check to cache load, and add -f / --force flag to re-build regardless | ||||||
|  | - For each project loaded, check if it is the same, and do nothing | ||||||
|  | 	- prevents clobbering cache on a partial update | ||||||
|  | 	- track loaded projects in []*Project to diff after load | ||||||
|  | 	- should prune missing after the load is complete | ||||||
|  | - Add open command | ||||||
|  | 	- config should exist for editor (vim, code, etc..) | ||||||
|  | - Update README for shell completion, aliases, usage | ||||||
|  | - Make a Makefile | ||||||
|  |  | ||||||
| ## Purpose | ## Purpose | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,3 +17,9 @@ var aliasCmd = &cobra.Command{ | |||||||
| func init() { | func init() { | ||||||
| 	rootCmd.AddCommand(aliasCmd) | 	rootCmd.AddCommand(aliasCmd) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func mustHaveAliases(cmd *cobra.Command, args []string) { | ||||||
|  | 	if len(cache.Aliases) == 0 { | ||||||
|  | 		plog.Fatal("No aliases set, nothing to " + cmd.Name()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ var aliasDeleteCmd = &cobra.Command{ | |||||||
| 	Aliases: []string{"rm", "del", "d"}, | 	Aliases: []string{"rm", "del", "d"}, | ||||||
| 	Short:   "Delete a project alias", | 	Short:   "Delete a project alias", | ||||||
| 	Long:    aliasDeleteCmdLong, | 	Long:    aliasDeleteCmdLong, | ||||||
|  | 	PreRun:  mustHaveAliases, | ||||||
| 	Run:     runDeleteAliasCmd, | 	Run:     runDeleteAliasCmd, | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ var aliasListCmd = &cobra.Command{ | |||||||
| 	Use:     "list", | 	Use:     "list", | ||||||
| 	Aliases: []string{"dump", "show", "ls", "ll", "l"}, | 	Aliases: []string{"dump", "show", "ls", "ll", "l"}, | ||||||
| 	Short:   "List Aliases", | 	Short:   "List Aliases", | ||||||
|  | 	PreRun:  mustHaveAliases, | ||||||
| 	Long:    aliasListCmdLong, | 	Long:    aliasListCmdLong, | ||||||
| 	Run:     runListAliasCmd, | 	Run:     runListAliasCmd, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ var cache *projects.Cache | |||||||
|  |  | ||||||
| var cacheCmd = &cobra.Command{ | var cacheCmd = &cobra.Command{ | ||||||
| 	Use:               "cache", | 	Use:               "cache", | ||||||
|  | 	Aliases:           []string{"a", "ln"}, | ||||||
| 	Short:             "Manage GitLab project cache", | 	Short:             "Manage GitLab project cache", | ||||||
| 	Long:              cacheCmdLong, | 	Long:              cacheCmdLong, | ||||||
| 	PersistentPreRun:  runCacheCmd, | 	PersistentPreRun:  runCacheCmd, | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								cmd/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								cmd/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var configCmd = &cobra.Command{ | ||||||
|  | 	Use:     "config", | ||||||
|  | 	Short:   "GitLab Project Manager Configuration", | ||||||
|  | 	Aliases: []string{"conf"}, | ||||||
|  | 	Long:    configCmdLong, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	rootCmd.AddCommand(configCmd) | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								cmd/config_generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								cmd/config_generate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | 	"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/config" | ||||||
|  | 	"gopkg.in/yaml.v3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var configGenerateCmd = &cobra.Command{ | ||||||
|  | 	Use:     "generate", | ||||||
|  | 	Short:   "Generate a default configuration", | ||||||
|  | 	Aliases: []string{"gen", "new", "default"}, | ||||||
|  | 	Long:    configGenCmdLong, | ||||||
|  | 	Run:     runConfigGenerateCmd, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runConfigGenerateCmd(cmd *cobra.Command, args []string) { | ||||||
|  | 	plog.Info("Suggest running with > " + defConfigPath) | ||||||
|  | 	c, _ := yaml.Marshal(config.DefaultConfig) | ||||||
|  | 	fmt.Print(string(c)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	configCmd.AddCommand(configGenerateCmd) | ||||||
|  | } | ||||||
| @@ -46,6 +46,7 @@ func getProject(args []string) *gitlab.Project { | |||||||
|  |  | ||||||
| func initProjectCmd(cmd *cobra.Command, args []string) { | func initProjectCmd(cmd *cobra.Command, args []string) { | ||||||
| 	initProjectCache(cmd, args) | 	initProjectCache(cmd, args) | ||||||
|  | 	mustHaveProjects(cmd, args) | ||||||
| } | } | ||||||
|  |  | ||||||
| func postProjectCmd(cmd *cobra.Command, args []string) { | func postProjectCmd(cmd *cobra.Command, args []string) { | ||||||
| @@ -55,3 +56,9 @@ func postProjectCmd(cmd *cobra.Command, args []string) { | |||||||
| func init() { | func init() { | ||||||
| 	rootCmd.AddCommand(projectCmd) | 	rootCmd.AddCommand(projectCmd) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func mustHaveProjects(cmd *cobra.Command, args []string) { | ||||||
|  | 	if len(cache.Aliases) == 0 { | ||||||
|  | 		plog.Fatal("No projects to " + cmd.Name() + ", try running cache load") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								cmd/root.go
									
									
									
									
									
								
							| @@ -38,7 +38,7 @@ func Execute() { | |||||||
| 	err := rootCmd.ExecuteContext(ctx) | 	err := rootCmd.ExecuteContext(ctx) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		plog.Error("Failed to execute command", plog.Args("err", err)) | 		pterm.Error.Printfln(pterm.LightYellow("Command failed, " + err.Error())) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -48,12 +48,12 @@ func init() { | |||||||
| 	cobra.OnInitialize(initConfig) | 	cobra.OnInitialize(initConfig) | ||||||
|  |  | ||||||
| 	rootCmd.PersistentFlags().String("config", "", | 	rootCmd.PersistentFlags().String("config", "", | ||||||
| 		"config file (default is $HOME/.gitlab-project-manager.yaml)") | 		"config file (default is "+defConfigPath+")") | ||||||
| 	rootCmd.PersistentFlags().String("gitlabHost", defGitlabHost, | 	rootCmd.PersistentFlags().String("gitlabHost", defGitlabHost, | ||||||
| 		"GitLab Hostname (e.g. gitlab.com)") | 		"GitLab Hostname (e.g. gitlab.com)") | ||||||
| 	rootCmd.PersistentFlags().String("gitlabToken", "", | 	rootCmd.PersistentFlags().String("gitlabToken", "", | ||||||
| 		"GitLab Tokenname (e.g. gitlab.com)") | 		"GitLab Tokenname (e.g. gitlab.com)") | ||||||
| 	rootCmd.PersistentFlags().String("projectPath", defProjectsPath, | 	rootCmd.PersistentFlags().String("projectPath", "", | ||||||
| 		"Sets a path for local clones of projects") | 		"Sets a path for local clones of projects") | ||||||
| 	rootCmd.PersistentFlags().String("logLevel", defLogLevel, | 	rootCmd.PersistentFlags().String("logLevel", defLogLevel, | ||||||
| 		"Default log level -- info, warn, error, debug") | 		"Default log level -- info, warn, error, debug") | ||||||
| @@ -73,16 +73,14 @@ func initConfig() { | |||||||
| 		cobra.CheckErr(err) | 		cobra.CheckErr(err) | ||||||
|  |  | ||||||
| 		// Search config in home directory with name ".gitlab-project-manager" (without extension). | 		// Search config in home directory with name ".gitlab-project-manager" (without extension). | ||||||
| 		viper.AddConfigPath(home) | 		viper.AddConfigPath(home + "/.config") | ||||||
| 		viper.SetConfigType("yaml") | 		viper.SetConfigType("yaml") | ||||||
| 		viper.SetConfigName(".gitlab-project-manager") | 		viper.SetConfigName("gitlab-project-manager") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	viper.AutomaticEnv() | 	viper.AutomaticEnv() | ||||||
| 	viper.ReadInConfig() | 	viper.ReadInConfig() | ||||||
|  |  | ||||||
| 	checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config |  | ||||||
|  |  | ||||||
| 	// Configure pretty logger | 	// Configure pretty logger | ||||||
| 	plog = pterm.DefaultLogger. | 	plog = pterm.DefaultLogger. | ||||||
| 		WithLevel(getPtermLogLevel(viper.GetString("logLevel"))). | 		WithLevel(getPtermLogLevel(viper.GetString("logLevel"))). | ||||||
| @@ -97,6 +95,18 @@ func initConfig() { | |||||||
| 		plog.Error("Failed loading config", plog.Args("err", err)) | 		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)) | 	plog.Debug("Configuration loaded", plog.Args("conf", conf)) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package cmd | |||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	defGitlabHost = "gitlab.sweetwater.com" | 	defGitlabHost = "gitlab.sweetwater.com" | ||||||
| 	defProjectsPath = "~/work/projects" |  | ||||||
| 	defLogLevel   = "info" | 	defLogLevel   = "info" | ||||||
|  | 	defConfigPath = "~/.config/gitlab-project-manager.yaml" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const aliasCmdLong = `Manages project aliases, with options for | const aliasCmdLong = `Manages project aliases, with options for | ||||||
| @@ -43,3 +43,9 @@ uses fuzzy find to locate the project` | |||||||
|  |  | ||||||
| const projShowCmdLong = `Shows detail for a particular project | const projShowCmdLong = `Shows detail for a particular project | ||||||
| Will always fuzzy find` | Will always fuzzy find` | ||||||
|  |  | ||||||
|  | const configCmdLong = `Commands for managing configuration, particulary | ||||||
|  | useful for seeding a new config file` | ||||||
|  |  | ||||||
|  | const configGenCmdLong = `Produces yaml to stdout that can be used | ||||||
|  | to seed the configuration file` | ||||||
|   | |||||||
| @@ -13,13 +13,28 @@ type Config struct { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| type cacheConfig struct { | type loadConfig struct { | ||||||
| 	Ttl  time.Duration `yaml:"ttl" json:"ttl"` |  | ||||||
| 	File string        `yaml:"file" json:"file"` |  | ||||||
| 	Load struct { |  | ||||||
| 	OwnerOnly bool `yaml:"ownerOnly" json:"ownerOnly"` | 	OwnerOnly bool `yaml:"ownerOnly" json:"ownerOnly"` | ||||||
| 	} `yaml:"load" json:"load"` | } | ||||||
| 	Clear struct { |  | ||||||
| 		ClearAliases bool `yaml:"clearAliases" json:"clearAliases"` | type cacheConfig struct { | ||||||
| 	} `yaml:"clear" json:"clear"` | 	Ttl   time.Duration `yaml:"ttl,omitempty" json:"ttl,omitempty"` | ||||||
|  | 	File  string        `yaml:"file,omitempty" json:"file,omitempty"` | ||||||
|  | 	Load  loadConfig    `yaml:"load" json:"load"` | ||||||
|  | 	Clear struct { | ||||||
|  | 		ClearAliases bool `yaml:"clearAliases,omitempty" json:"clearAliases,omitempty"` | ||||||
|  | 	} `yaml:"clear,omitempty" json:"clear,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var DefaultConfig = Config{ | ||||||
|  | 	GitlabHost:  "https://gitlab.com", | ||||||
|  | 	GitlabToken: "yourtokenhere", | ||||||
|  | 	LogLevel:    "warn", | ||||||
|  | 	ProjectPath: "~/work/projects", | ||||||
|  | 	Cache: cacheConfig{ | ||||||
|  | 		Ttl: 168 * time.Hour, | ||||||
|  | 		Load: loadConfig{ | ||||||
|  | 			OwnerOnly: true, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -129,16 +129,17 @@ func (c *Client) streamProjects(pi *ProgressInfo, ownerOnly bool) { | |||||||
| 	for { | 	for { | ||||||
| 		projects, resp, err := c.ListProjects(listOpts) | 		projects, resp, err := c.ListProjects(listOpts) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			pi.ErrorChan <- err | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// We're done when we have it all or our context is done | 		// We're done when we have it all or our context is done | ||||||
| 		if c.Ctx.Err() != nil || resp.NextPage == 0 { | 		if c.Ctx.Err() != nil || resp.NextPage == 0 { | ||||||
| 			pi.DoneChan <- nil | 			pi.DoneChan <- nil | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err != nil { |  | ||||||
| 			pi.ErrorChan <- err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		numProjects += len(projects) | 		numProjects += len(projects) | ||||||
| 		pi.ProjectsChan <- projects | 		pi.ProjectsChan <- projects | ||||||
| 		pi.ProgressChan <- Progress{ | 		pi.ProgressChan <- Progress{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user