Bug fixes, config generator

This commit is contained in:
Ryan McGuire 2023-12-10 10:10:46 -05:00
parent 5d2ca40d04
commit cdf92c8a54
12 changed files with 124 additions and 23 deletions

View File

@ -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

View File

@ -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())
}
}

View File

@ -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,
} }

View File

@ -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,
} }

View File

@ -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
View 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
View 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)
}

View File

@ -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")
}
}

View File

@ -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))
} }

View File

@ -1,9 +1,9 @@
package cmd 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`

View File

@ -13,13 +13,28 @@ type Config struct {
} }
} }
type cacheConfig struct { type loadConfig struct {
Ttl time.Duration `yaml:"ttl" json:"ttl"` OwnerOnly bool `yaml:"ownerOnly" json:"ownerOnly"`
File string `yaml:"file" json:"file"` }
Load struct {
OwnerOnly bool `yaml:"ownerOnly" json:"ownerOnly"` type cacheConfig struct {
} `yaml:"load" json:"load"` Ttl time.Duration `yaml:"ttl,omitempty" json:"ttl,omitempty"`
Clear struct { File string `yaml:"file,omitempty" json:"file,omitempty"`
ClearAliases bool `yaml:"clearAliases" json:"clearAliases"` Load loadConfig `yaml:"load" json:"load"`
} `yaml:"clear" json:"clear"` 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,
},
},
} }

View File

@ -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{