From 8042fb3c0f7c3c0deb1075dcbc8094beea82680f Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 31 Aug 2021 17:10:13 +0200 Subject: [PATCH 01/12] config: add local configurations (#16) Closes (#16) --- cmd/root.go | 57 ++++++++++++++++++++++++++++++++++++++--------------- git/git.go | 10 ++++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index bbaf408..3aca2ce 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -119,30 +119,55 @@ func init() { } func initConfig() { - if cfgFile == "" { - configDir := configdir.LocalConfig("bb") - err := configdir.MakePath(configDir) + viper.SetEnvPrefix("bb") + viper.AutomaticEnv() + + // We support setting the config file manually by running bb with --config. + if cfgFile != "" { + viper.SetConfigFile(cfgFile) + err := viper.ReadInConfig() + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %s", err)) + } + } else { + // create global config directory, first + configDirectory := configdir.LocalConfig("bb") + err := configdir.MakePath(configDirectory) if err != nil { panic(err) } - cfgFile = filepath.Join(configDir, "configuration.toml") - if _, err = os.Stat(cfgFile); os.IsNotExist(err) { - fh, err := os.Create(cfgFile) + globalConfigFilePath := filepath.Join(configDirectory, "configuration.toml") + // create global config directory, first + if _, err = os.Stat(globalConfigFilePath); os.IsNotExist(err) { + fh, err := os.Create(globalConfigFilePath) if err != nil { panic(err) } defer fh.Close() } - } - - viper.SetConfigFile(cfgFile) - - viper.SetEnvPrefix("bb") - viper.AutomaticEnv() - - err := viper.ReadInConfig() + viper.SetConfigType("toml") + viper.SetConfigName("configuration.toml") + viper.AddConfigPath(configDirectory) + err = viper.ReadInConfig() + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %s", err)) + } - if err != nil { // Handle errors reading the config file - panic(fmt.Errorf("fatal error config file: %s", err)) + // also read in local configuration + if repoPath, err := bbgit.RepoPath(); err == nil { + // the local configuration can be found in the root of a repository + // If we in a repository, check for the file + if _, err = os.Stat(filepath.Join(repoPath, ".bb")); err == nil { + viper.SetConfigType("toml") + viper.SetConfigName(".bb") + viper.AddConfigPath(repoPath) + err = viper.MergeInConfig() + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %s", err)) + } + } + } } + logging.Debugf("%+v", viper.AllSettings()) + } diff --git a/git/git.go b/git/git.go index 54a86e1..8be561d 100644 --- a/git/git.go +++ b/git/git.go @@ -62,6 +62,16 @@ func CurrentHead() (string, error) { output, err := run.PrepareCmd(headCmd).Output() return firstLine(output), err } + +func RepoPath() (string, error) { + pathCmd, err := git.GitCommand("rev-parse", "--show-toplevel") + if err != nil { + return "", err + } + output, err := run.PrepareCmd(pathCmd).Output() + return firstLine(output), err +} + func firstLine(output []byte) string { if i := bytes.IndexAny(output, "\n"); i >= 0 { return string(output)[0:i] From a48b5dddc5864524a3096b3fb94d1ab93e987a2d Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 31 Aug 2021 22:32:49 +0200 Subject: [PATCH 02/12] config: add config command (#18) Closes #18 --- cmd/commands/auth/login/login.go | 3 +- cmd/commands/config/config.go | 171 +++++++++++++++++++++++++++++++ cmd/commands/repo/clone/clone.go | 1 + cmd/root.go | 2 + 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 cmd/commands/config/config.go diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index 32c3b83..236a80a 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -63,7 +63,8 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { viper.Set("username", answers.Username) viper.Set("password", answers.Password) - + + // TODO: fix err = viper.WriteConfig() if err != nil { logging.Error(err) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go new file mode 100644 index 0000000..4e53257 --- /dev/null +++ b/cmd/commands/config/config.go @@ -0,0 +1,171 @@ +package config + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/util/logging" + "github.com/kirsle/configdir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + Local bool + Get bool +) + +func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { + configCommand := cobra.Command{ + Use: "config", + Short: "configure bb", + Long: "configure bb", + Args: func(cmd *cobra.Command, args []string) error { + if Get { + return cobra.ExactArgs(1)(cmd, args) + } else { + return cobra.ExactArgs(2)(cmd, args) + } + }, + Run: func(_ *cobra.Command, args []string) { + if Get { + // TODO: code here + } else { + var configDirectory string + var filename string + if Local { + var err error + configDirectory, filename, err = GetLocalConfigurationPath() + if err != nil { + logging.Error(err) + return + } + } else { + configDirectory, filename = GetGlobalConfigurationPath() + } + + // If the directory does not exist, something is off: + // - The global configuration directory get's created in root + // - The local configuration directory is a repository, which always exists + if _, err := os.Stat(configDirectory); os.IsNotExist(err) { + logging.Error(fmt.Sprintf("Expected directory \"%s\", but the directory does not exist", configDirectory)) + return + } + path := filepath.Join(configDirectory, filename) + // If the config itself does not exist, it's fine (although wierd for global) - we create it now + if _, err := os.Stat(path); os.IsNotExist(err) { + logging.Note(fmt.Sprintf("Creating config file %s", path)) + fh, err := os.Create(path) + if err != nil { + logging.Error(fmt.Sprintf("Unable to create file %s", path)) + } + fh.Close() + } + + logging.Debugf("Config file path: %s", path) + + tmpVp := viper.New() + tmpVp.SetConfigType("toml") + tmpVp.SetConfigFile(path) + tmpVp.ReadInConfig() + + key := args[0] + newValue := args[1] + + isSetAlready := tmpVp.IsSet(key) + oldValue := tmpVp.Get(key) + + if isSetAlready { + // Don't print old password values + if strings.ToLower(key) == "password" { + oldValue = "(truncated)" + } + logging.Warning(fmt.Sprintf("\"%s\" is already set. This will overwrite the value of \"%s\" from \"%s\" to \"%s\".", key, key, oldValue, newValue)) + } + + logging.Note(fmt.Sprintf("Setting \"%s\" to \"%s\" in %s", key, newValue, path)) + logging.Debugf("%+v", tmpVp.AllSettings()) + + // This will most likely save everything as a string + // TODO: find this out and find a way to save bools and numbers + tmpVp.Set(key, newValue) + logging.Debugf("%+v", tmpVp.AllSettings()) + + // WORKAROUND: currently, WriteConfig does not support writing to `.bb`-files despite setting SetConfigType. + // Therefore, we create a temporary file, write there, and try to copy the file over. + tmpFh, err := ioutil.TempFile(os.TempDir(), "bb-tmpconfig.*.toml") + if err != nil { + logging.Error("Failed to create temporary configuration file") + return + } + tmpFilename := tmpFh.Name() + logging.Debugf("tmpFilename: %s", tmpFilename) + err = tmpFh.Close() + if err != nil { + logging.Error("Failed to create temporary configuration file") + return + } + err = tmpVp.WriteConfigAs(tmpFilename) + if err != nil { + logging.Error(fmt.Sprintf("Failed to write temporary config %s: %s", path, err)) + return + } + err = copyFileContent(tmpFilename, path) + if err != nil { + logging.Error(fmt.Sprintf("Failed to write config %s -> %s: %s", tmpFilename, path, err)) + return + } + + + logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) + } + }, + } + + configCommand.Flags().BoolVar(&Local, "local", false, "local allows to modify the local configuration") + configCommand.Flags().BoolVar(&Get, "get", false, "gets a configuration value instead of setting it") + + rootCmd.AddCommand(&configCommand) +} + +func copyFileContent(src string, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) // Create or trunicate + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + return err +} + +// TODO: Extract to util +func GetGlobalConfigurationPath() (configDirectory string, filename string) { + configDirectory = configdir.LocalConfig("bb") + return configDirectory, "configuration.toml" +} + +func GetLocalConfigurationPath() (configDirectory, filename string, err error) { + configDirectory, err = bbgit.RepoPath() + return configDirectory, ".bb", err +} diff --git a/cmd/commands/repo/clone/clone.go b/cmd/commands/repo/clone/clone.go index 57afc91..2287818 100644 --- a/cmd/commands/repo/clone/clone.go +++ b/cmd/commands/repo/clone/clone.go @@ -35,6 +35,7 @@ func Add(repoCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } viper.Set("git_protocol", gitProtocol) + // TODO: fix viper.WriteConfig() } diff --git a/cmd/root.go b/cmd/root.go index 3aca2ce..7844b5f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,6 +10,7 @@ import ( "github.com/craftamap/bb/client" "github.com/craftamap/bb/cmd/commands/api" "github.com/craftamap/bb/cmd/commands/auth" + "github.com/craftamap/bb/cmd/commands/config" "github.com/craftamap/bb/cmd/commands/downloads" "github.com/craftamap/bb/cmd/commands/issue" "github.com/craftamap/bb/cmd/commands/pipelines" @@ -106,6 +107,7 @@ func init() { auth.Add(rootCmd, &globalOpts) repo.Add(rootCmd, &globalOpts) pipelines.Add(rootCmd, &globalOpts) + config.Add(rootCmd, &globalOpts) if CommitSHA != "" { vt := rootCmd.VersionTemplate() From 9ff22ea552483e926373eb5a653697d8f3fc9d1c Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 31 Aug 2021 22:47:44 +0200 Subject: [PATCH 03/12] config: make remote-name configurable (#17) Closes #17 --- cmd/root.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 7844b5f..df44d69 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,6 +36,7 @@ var ( PersistentPreRun: func(cmd *cobra.Command, args []string) { username := viper.GetString("username") password := viper.GetString("password") + remoteName := viper.GetString("remote") if _, ok := cmd.Annotations["RequiresRepository"]; ok { bbrepo, err := bbgit.GetBitbucketRepo(remoteName) @@ -86,7 +87,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/bb)") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username") rootCmd.PersistentFlags().StringVar(&password, "password", "", "app password") - rootCmd.PersistentFlags().StringVar(&remoteName, "remote", "origin", "if you are in a repository and don't want to interact with the default origin, you can change it") + rootCmd.PersistentFlags().StringVar(&remoteName, "remote", "", "if you are in a repository and don't want to interact with the default origin, you can change it") rootCmd.PersistentFlags().BoolVar(&logging.PrintDebugLogs, "debug", false, "enabling this flag allows debug logs to be printed") err := viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) @@ -100,6 +101,12 @@ func init() { return } + err = viper.BindPFlag("remote", rootCmd.PersistentFlags().Lookup("remote")) + if err != nil { + logging.Error(err) + return + } + pr.Add(rootCmd, &globalOpts) issue.Add(rootCmd, &globalOpts) api.Add(rootCmd, &globalOpts) From fb485bc83ef70077963b2b99aeb9e8318756775c Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 3 Sep 2021 23:43:26 +0200 Subject: [PATCH 04/12] config: make only specific keys configurable by validating them (#18) --- cmd/commands/config/config.go | 17 +++++--- config/config.go | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 config/config.go diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index 4e53257..cde1f66 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/craftamap/bb/cmd/options" + "github.com/craftamap/bb/config" bbgit "github.com/craftamap/bb/git" "github.com/craftamap/bb/util/logging" "github.com/kirsle/configdir" @@ -37,6 +38,16 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { if Get { // TODO: code here } else { + key := args[0] + inputValue := args[1] + + newValue, err := config.BbConfigurationValidation.ValidateEntry(key, inputValue) + + if err != nil { + logging.Error(fmt.Sprintf("failed to validate %s: %s", inputValue, err)) + return + } + var configDirectory string var filename string if Local { @@ -75,9 +86,6 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { tmpVp.SetConfigFile(path) tmpVp.ReadInConfig() - key := args[0] - newValue := args[1] - isSetAlready := tmpVp.IsSet(key) oldValue := tmpVp.Get(key) @@ -122,7 +130,6 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { return } - logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) } }, @@ -137,7 +144,7 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { func copyFileContent(src string, dst string) error { sourceFileStat, err := os.Stat(src) if err != nil { - return err + return err } if !sourceFileStat.Mode().IsRegular() { diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..b072033 --- /dev/null +++ b/config/config.go @@ -0,0 +1,79 @@ +package config + +import ( + "fmt" + "strings" +) + +type Validator func(interface{}) (interface{}, error) + +// Enum is just a string of a list +func EnumValidator(validValues ...string) Validator { + return func(inputValue interface{}) (interface{}, error) { + _, ok := inputValue.(string) + if !ok { + return "", fmt.Errorf("value \"%s\" is not a string, but of type %T", inputValue, inputValue) + } + isInList := false + for _, validValue := range validValues { + if inputValue == validValue { + isInList = true + break + } + } + if !isInList { + return "", fmt.Errorf("value \"%s\" is not a valid value. Valid Values are %s", inputValue, validValues) + } + + return inputValue, nil + } +} + +// SimpleStringValidator validates if a input is a "simple" string - only single-line strings are supported +func SimpleStringValidator() Validator { + return func(inputValue interface{}) (interface{}, error) { + _, ok := inputValue.(string) + if !ok { + return "", fmt.Errorf("value \"%s\" is not a string, but of type %T", inputValue, inputValue) + } + + if strings.ContainsAny(inputValue.(string), "\r\n") { + return "", fmt.Errorf("value \"%s\" contains illegal line break", inputValue) + } + + return inputValue, nil + } +} + +// Entry contains all the data required for Validation and Convertion +type Entry struct { + Validator Validator +} + +type Configuration map[string]Entry + +func (c Configuration) ValidateEntry(key string, value interface{}) (interface{}, error) { + e, ok := c[key] + if !ok { + return "", fmt.Errorf("key \"%s\" is not a valid key", key) + } + return e.Validator(value) +} + +var BbConfigurationValidation Configuration = map[string]Entry{ + "username": { + Validator: SimpleStringValidator(), + }, + "password": { + Validator: SimpleStringValidator(), + }, + "remote": { + Validator: SimpleStringValidator(), + }, + "git_protocol": { + Validator: EnumValidator("ssh", "https"), + }, + "sync-method": { + Validator: EnumValidator("merge", "rebase"), + }, +} From 568d4e0d3f3ee6cd9e05aecc3afb4706673aaf25 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 6 Sep 2021 23:30:09 +0200 Subject: [PATCH 05/12] config: update auth/login and clone use the globalPath and the validator (#18) --- cmd/commands/auth/login/login.go | 37 ++++++++++++++++++++++++-------- cmd/commands/config/config.go | 17 ++------------- cmd/commands/repo/clone/clone.go | 23 +++++++++++++++++--- config/path.go | 17 +++++++++++++++ 4 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 config/path.go diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index 236a80a..8865914 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -2,7 +2,9 @@ package login import ( "fmt" + "path/filepath" + "github.com/craftamap/bb/config" "github.com/craftamap/bb/util/logging" "github.com/AlecAivazis/survey/v2" @@ -15,11 +17,19 @@ import ( func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { loginCmd := &cobra.Command{ Use: "login", - Run: func(cmd *cobra.Command, args []string) { - oldPw := viper.GetString("password") + Run: func(_ *cobra.Command, _ []string) { + configDirectory, filename := config.GetGlobalConfigurationPath() + path := filepath.Join(configDirectory, filename) + // TODO: extract tmpVp stuff to a seperate file + tmpVp := viper.New() + tmpVp.SetConfigType("toml") + tmpVp.SetConfigFile(path) + tmpVp.ReadInConfig() + + oldPw := tmpVp.GetString("password") if oldPw != "" { - fmt.Println(aurora.Yellow("::"), aurora.Bold("Warning:"), "You are already logged in as", viper.GetString("username")) + logging.Warning("You are already logged in as ", tmpVp.GetString("username")) cont := false err := survey.AskOne(&survey.Confirm{Message: "Do you want to overwrite this?"}, &cont) if err != nil { @@ -60,18 +70,27 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { logging.Error(err) return } + username, err := config.BbConfigurationValidation.ValidateEntry("username", answers.Username) + if err != nil { + logging.Error(err) + return + } + password, err := config.BbConfigurationValidation.ValidateEntry("password", answers.Password) + if err != nil { + logging.Error(err) + return + } + + tmpVp.Set("username", username) + tmpVp.Set("password", password) - viper.Set("username", answers.Username) - viper.Set("password", answers.Password) - - // TODO: fix - err = viper.WriteConfig() + err = tmpVp.WriteConfig() if err != nil { logging.Error(err) return } - logging.Success(fmt.Sprint("Stored credentials successfully to", viper.ConfigFileUsed())) + logging.Success(fmt.Sprint("Stored credentials successfully to", tmpVp.ConfigFileUsed())) }, } diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index cde1f66..3e67782 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -10,9 +10,7 @@ import ( "github.com/craftamap/bb/cmd/options" "github.com/craftamap/bb/config" - bbgit "github.com/craftamap/bb/git" "github.com/craftamap/bb/util/logging" - "github.com/kirsle/configdir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -52,13 +50,13 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { var filename string if Local { var err error - configDirectory, filename, err = GetLocalConfigurationPath() + configDirectory, filename, err = config.GetLocalConfigurationPath() if err != nil { logging.Error(err) return } } else { - configDirectory, filename = GetGlobalConfigurationPath() + configDirectory, filename = config.GetGlobalConfigurationPath() } // If the directory does not exist, something is off: @@ -165,14 +163,3 @@ func copyFileContent(src string, dst string) error { _, err = io.Copy(destination, source) return err } - -// TODO: Extract to util -func GetGlobalConfigurationPath() (configDirectory string, filename string) { - configDirectory = configdir.LocalConfig("bb") - return configDirectory, "configuration.toml" -} - -func GetLocalConfigurationPath() (configDirectory, filename string, err error) { - configDirectory, err = bbgit.RepoPath() - return configDirectory, ".bb", err -} diff --git a/cmd/commands/repo/clone/clone.go b/cmd/commands/repo/clone/clone.go index 2287818..f60c965 100644 --- a/cmd/commands/repo/clone/clone.go +++ b/cmd/commands/repo/clone/clone.go @@ -2,8 +2,10 @@ package clone import ( "fmt" + "path/filepath" "strings" + "github.com/craftamap/bb/config" "github.com/craftamap/bb/util/logging" "github.com/AlecAivazis/survey/v2" @@ -34,9 +36,24 @@ func Add(repoCmd *cobra.Command, globalOpts *options.GlobalOptions) { logging.Error(err) return } - viper.Set("git_protocol", gitProtocol) - // TODO: fix - viper.WriteConfig() + + configDirectory, filename := config.GetGlobalConfigurationPath() + path := filepath.Join(configDirectory, filename) + // TODO: extract tmpVp stuff to a seperate file + tmpVp := viper.New() + tmpVp.SetConfigType("toml") + tmpVp.SetConfigFile(path) + tmpVp.ReadInConfig() + + gitProtocolI, err := config.BbConfigurationValidation.ValidateEntry("git_protocol", gitProtocol) + if err != nil { + logging.Error(err) + return + } + gitProtocol = gitProtocolI.(string) + + tmpVp.Set("git_protocol", gitProtocol) + tmpVp.WriteConfig() } if len(args) == 0 { diff --git a/config/path.go b/config/path.go new file mode 100644 index 0000000..3a53912 --- /dev/null +++ b/config/path.go @@ -0,0 +1,17 @@ +package config + +import ( + bbgit "github.com/craftamap/bb/git" + "github.com/kirsle/configdir" +) + +// TODO: Extract to util +func GetGlobalConfigurationPath() (configDirectory string, filename string) { + configDirectory = configdir.LocalConfig("bb") + return configDirectory, "configuration.toml" +} + +func GetLocalConfigurationPath() (configDirectory, filename string, err error) { + configDirectory, err = bbgit.RepoPath() + return configDirectory, ".bb", err +} From 84a70f19bba0120f11d8efdd5e94a32e6b3dd9a2 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 6 Sep 2021 23:44:27 +0200 Subject: [PATCH 06/12] config: use constants instead of magic strings for config keys (#18) --- cmd/commands/auth/login/login.go | 12 ++++++------ cmd/commands/pr/sync/sync.go | 5 +++-- cmd/commands/repo/clone/clone.go | 6 +++--- cmd/root.go | 23 ++++++++++++++++------- config/config.go | 19 ++++++++++++++----- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index 8865914..460f314 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -26,10 +26,10 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { tmpVp.SetConfigFile(path) tmpVp.ReadInConfig() - oldPw := tmpVp.GetString("password") + oldPw := tmpVp.GetString(config.CONFIG_KEY_AUTH_PASSWORD) if oldPw != "" { - logging.Warning("You are already logged in as ", tmpVp.GetString("username")) + logging.Warning("You are already logged in as ", tmpVp.GetString(config.CONFIG_KEY_AUTH_USERNAME)) cont := false err := survey.AskOne(&survey.Confirm{Message: "Do you want to overwrite this?"}, &cont) if err != nil { @@ -70,19 +70,19 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { logging.Error(err) return } - username, err := config.BbConfigurationValidation.ValidateEntry("username", answers.Username) + username, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_AUTH_USERNAME, answers.Username) if err != nil { logging.Error(err) return } - password, err := config.BbConfigurationValidation.ValidateEntry("password", answers.Password) + password, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_AUTH_PASSWORD, answers.Password) if err != nil { logging.Error(err) return } - tmpVp.Set("username", username) - tmpVp.Set("password", password) + tmpVp.Set(config.CONFIG_KEY_AUTH_USERNAME, username) + tmpVp.Set(config.CONFIG_KEY_AUTH_PASSWORD, password) err = tmpVp.WriteConfig() if err != nil { diff --git a/cmd/commands/pr/sync/sync.go b/cmd/commands/pr/sync/sync.go index b7f2522..62f507e 100644 --- a/cmd/commands/pr/sync/sync.go +++ b/cmd/commands/pr/sync/sync.go @@ -10,6 +10,7 @@ import ( "github.com/cli/cli/utils" "github.com/cli/safeexec" "github.com/craftamap/bb/cmd/options" + "github.com/craftamap/bb/config" "github.com/craftamap/bb/internal/run" "github.com/craftamap/bb/util/logging" "github.com/logrusorgru/aurora" @@ -38,7 +39,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { }, PreRunE: func(cmd *cobra.Command, _ []string) error { // In order to check if the method exists in the config, we need to check here - syncMethodIsSet := viper.IsSet("sync-method") + syncMethodIsSet := viper.IsSet(config.CONFIG_KEY_PR_SYNC_SYNC_METHOD) if !syncMethodIsSet { logging.Note( "You can configure your preferred way of syncing by adding the following line to your configuration: ", @@ -48,7 +49,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { ) } if syncMethodIsSet && !cmd.Flags().Lookup("method").Changed { - Method = viper.GetString("sync-method") + Method = viper.GetString(config.CONFIG_KEY_PR_SYNC_SYNC_METHOD) } if Method != MethodOptionRebase && Method != MethodOptionMerge { diff --git a/cmd/commands/repo/clone/clone.go b/cmd/commands/repo/clone/clone.go index f60c965..769cc4c 100644 --- a/cmd/commands/repo/clone/clone.go +++ b/cmd/commands/repo/clone/clone.go @@ -26,7 +26,7 @@ func Add(repoCmd *cobra.Command, globalOpts *options.GlobalOptions) { Run: func(cmd *cobra.Command, args []string) { c := globalOpts.Client - gitProtocol := viper.GetString("git_protocol") + gitProtocol := viper.GetString(config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL) if gitProtocol == "" || (gitProtocol != "ssh" && gitProtocol != "https") { err := survey.AskOne(&survey.Select{ Message: "Please select a prefered protocol of cloning repositories", @@ -45,14 +45,14 @@ func Add(repoCmd *cobra.Command, globalOpts *options.GlobalOptions) { tmpVp.SetConfigFile(path) tmpVp.ReadInConfig() - gitProtocolI, err := config.BbConfigurationValidation.ValidateEntry("git_protocol", gitProtocol) + gitProtocolI, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, gitProtocol) if err != nil { logging.Error(err) return } gitProtocol = gitProtocolI.(string) - tmpVp.Set("git_protocol", gitProtocol) + tmpVp.Set(config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, gitProtocol) tmpVp.WriteConfig() } diff --git a/cmd/root.go b/cmd/root.go index df44d69..6d02c8f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,6 +17,7 @@ import ( "github.com/craftamap/bb/cmd/commands/pr" "github.com/craftamap/bb/cmd/commands/repo" "github.com/craftamap/bb/cmd/options" + configuration "github.com/craftamap/bb/config" bbgit "github.com/craftamap/bb/git" "github.com/kirsle/configdir" "github.com/logrusorgru/aurora" @@ -34,9 +35,9 @@ var ( Long: "Work seamlessly with Bitbucket.org from the command line.", Example: `$ bb pr list`, PersistentPreRun: func(cmd *cobra.Command, args []string) { - username := viper.GetString("username") - password := viper.GetString("password") - remoteName := viper.GetString("remote") + username := viper.GetString(configuration.CONFIG_KEY_AUTH_USERNAME) + password := viper.GetString(configuration.CONFIG_KEY_AUTH_PASSWORD) + remoteName := viper.GetString(configuration.CONFIG_KEY_GIT_REMOTE) if _, ok := cmd.Annotations["RequiresRepository"]; ok { bbrepo, err := bbgit.GetBitbucketRepo(remoteName) @@ -87,21 +88,21 @@ func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/bb)") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username") rootCmd.PersistentFlags().StringVar(&password, "password", "", "app password") - rootCmd.PersistentFlags().StringVar(&remoteName, "remote", "", "if you are in a repository and don't want to interact with the default origin, you can change it") + rootCmd.PersistentFlags().StringVar(&remoteName, "remote", "origin", "if you are in a repository and don't want to interact with the default remote, you can change it") rootCmd.PersistentFlags().BoolVar(&logging.PrintDebugLogs, "debug", false, "enabling this flag allows debug logs to be printed") - err := viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) + err := viper.BindPFlag(configuration.CONFIG_KEY_AUTH_USERNAME, rootCmd.PersistentFlags().Lookup("username")) if err != nil { logging.Error(err) return } - err = viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + err = viper.BindPFlag(configuration.CONFIG_KEY_AUTH_PASSWORD, rootCmd.PersistentFlags().Lookup("password")) if err != nil { logging.Error(err) return } - err = viper.BindPFlag("remote", rootCmd.PersistentFlags().Lookup("remote")) + err = viper.BindPFlag(configuration.CONFIG_KEY_GIT_REMOTE, rootCmd.PersistentFlags().Lookup("remote")) if err != nil { logging.Error(err) return @@ -177,6 +178,14 @@ func initConfig() { } } } + + // Register Aliases for backward compability + viper.RegisterAlias("username", configuration.CONFIG_KEY_AUTH_USERNAME) + viper.RegisterAlias("password", configuration.CONFIG_KEY_AUTH_PASSWORD) + viper.RegisterAlias("remote", configuration.CONFIG_KEY_GIT_REMOTE) + viper.RegisterAlias("git_protocol", configuration.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL) + viper.RegisterAlias("sync-method", configuration.CONFIG_KEY_PR_SYNC_SYNC_METHOD) + logging.Debugf("%+v", viper.AllSettings()) } diff --git a/config/config.go b/config/config.go index b072033..d668517 100644 --- a/config/config.go +++ b/config/config.go @@ -60,20 +60,29 @@ func (c Configuration) ValidateEntry(key string, value interface{}) (interface{} return e.Validator(value) } +const ( + CONFIG_KEY_AUTH_USERNAME = "auth.username" + CONFIG_KEY_AUTH_PASSWORD = "auth.password" + CONFIG_KEY_GIT_REMOTE = "git.remote" + CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL = "repo.clone.git_protocol" + CONFIG_KEY_PR_SYNC_SYNC_METHOD = "pr.sync.sync_method" +) + + var BbConfigurationValidation Configuration = map[string]Entry{ - "username": { + CONFIG_KEY_AUTH_USERNAME: { Validator: SimpleStringValidator(), }, - "password": { + CONFIG_KEY_AUTH_PASSWORD: { Validator: SimpleStringValidator(), }, - "remote": { + CONFIG_KEY_GIT_REMOTE: { Validator: SimpleStringValidator(), }, - "git_protocol": { + CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL: { Validator: EnumValidator("ssh", "https"), }, - "sync-method": { + CONFIG_KEY_PR_SYNC_SYNC_METHOD: { Validator: EnumValidator("merge", "rebase"), }, } From 76f9f7243d7a3f93223cce6990b0dea0279f52c7 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 9 Sep 2021 01:37:32 +0200 Subject: [PATCH 07/12] config: add config helper methods (#18) --- cmd/commands/auth/login/login.go | 28 +++---- cmd/commands/config/config.go | 66 ++------------- cmd/commands/repo/clone/clone.go | 12 +-- config/config.go | 135 +++++++++++++++++++++++++++---- 4 files changed, 136 insertions(+), 105 deletions(-) diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index 460f314..204bd78 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -11,20 +11,19 @@ import ( "github.com/craftamap/bb/cmd/options" "github.com/logrusorgru/aurora" "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { +func Add(authCmd *cobra.Command, _ *options.GlobalOptions) { loginCmd := &cobra.Command{ Use: "login", Run: func(_ *cobra.Command, _ []string) { configDirectory, filename := config.GetGlobalConfigurationPath() path := filepath.Join(configDirectory, filename) - // TODO: extract tmpVp stuff to a seperate file - tmpVp := viper.New() - tmpVp.SetConfigType("toml") - tmpVp.SetConfigFile(path) - tmpVp.ReadInConfig() + tmpVp, err := config.GetViperForPath(path) + if err != nil { + logging.Error(err) + return + } oldPw := tmpVp.GetString(config.CONFIG_KEY_AUTH_PASSWORD) @@ -51,7 +50,7 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { Password string }{} - err := survey.Ask([]*survey.Question{ + err = survey.Ask([]*survey.Question{ { Name: "username", Prompt: &survey.Input{ @@ -70,21 +69,12 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { logging.Error(err) return } - username, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_AUTH_USERNAME, answers.Username) - if err != nil { - logging.Error(err) - return - } - password, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_AUTH_PASSWORD, answers.Password) + _, err = config.ValidateAndUpdateEntryWithViper(tmpVp, config.CONFIG_KEY_AUTH_USERNAME, answers.Username) if err != nil { logging.Error(err) return } - - tmpVp.Set(config.CONFIG_KEY_AUTH_USERNAME, username) - tmpVp.Set(config.CONFIG_KEY_AUTH_PASSWORD, password) - - err = tmpVp.WriteConfig() + _, err = config.ValidateAndUpdateEntryWithViper(tmpVp, config.CONFIG_KEY_AUTH_PASSWORD, answers.Password) if err != nil { logging.Error(err) return diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index 3e67782..c80c1d3 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -2,8 +2,6 @@ package config import ( "fmt" - "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -12,7 +10,6 @@ import ( "github.com/craftamap/bb/config" "github.com/craftamap/bb/util/logging" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var ( @@ -38,7 +35,7 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { } else { key := args[0] inputValue := args[1] - + newValue, err := config.BbConfigurationValidation.ValidateEntry(key, inputValue) if err != nil { @@ -79,17 +76,18 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { logging.Debugf("Config file path: %s", path) - tmpVp := viper.New() - tmpVp.SetConfigType("toml") - tmpVp.SetConfigFile(path) - tmpVp.ReadInConfig() + tmpVp, err := config.GetViperForPath(path) + if err != nil { + logging.Error(err) + return + } isSetAlready := tmpVp.IsSet(key) oldValue := tmpVp.Get(key) if isSetAlready { // Don't print old password values - if strings.ToLower(key) == "password" { + if strings.ToLower(key) == config.CONFIG_KEY_AUTH_PASSWORD { oldValue = "(truncated)" } logging.Warning(fmt.Sprintf("\"%s\" is already set. This will overwrite the value of \"%s\" from \"%s\" to \"%s\".", key, key, oldValue, newValue)) @@ -103,30 +101,7 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { tmpVp.Set(key, newValue) logging.Debugf("%+v", tmpVp.AllSettings()) - // WORKAROUND: currently, WriteConfig does not support writing to `.bb`-files despite setting SetConfigType. - // Therefore, we create a temporary file, write there, and try to copy the file over. - tmpFh, err := ioutil.TempFile(os.TempDir(), "bb-tmpconfig.*.toml") - if err != nil { - logging.Error("Failed to create temporary configuration file") - return - } - tmpFilename := tmpFh.Name() - logging.Debugf("tmpFilename: %s", tmpFilename) - err = tmpFh.Close() - if err != nil { - logging.Error("Failed to create temporary configuration file") - return - } - err = tmpVp.WriteConfigAs(tmpFilename) - if err != nil { - logging.Error(fmt.Sprintf("Failed to write temporary config %s: %s", path, err)) - return - } - err = copyFileContent(tmpFilename, path) - if err != nil { - logging.Error(fmt.Sprintf("Failed to write config %s -> %s: %s", tmpFilename, path, err)) - return - } + config.WriteViper(tmpVp, path) logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) } @@ -138,28 +113,3 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { rootCmd.AddCommand(&configCommand) } - -func copyFileContent(src string, dst string) error { - sourceFileStat, err := os.Stat(src) - if err != nil { - return err - } - - if !sourceFileStat.Mode().IsRegular() { - return fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(src) - if err != nil { - return err - } - defer source.Close() - - destination, err := os.Create(dst) // Create or trunicate - if err != nil { - return err - } - defer destination.Close() - _, err = io.Copy(destination, source) - return err -} diff --git a/cmd/commands/repo/clone/clone.go b/cmd/commands/repo/clone/clone.go index 769cc4c..bf989ad 100644 --- a/cmd/commands/repo/clone/clone.go +++ b/cmd/commands/repo/clone/clone.go @@ -39,21 +39,11 @@ func Add(repoCmd *cobra.Command, globalOpts *options.GlobalOptions) { configDirectory, filename := config.GetGlobalConfigurationPath() path := filepath.Join(configDirectory, filename) - // TODO: extract tmpVp stuff to a seperate file - tmpVp := viper.New() - tmpVp.SetConfigType("toml") - tmpVp.SetConfigFile(path) - tmpVp.ReadInConfig() - - gitProtocolI, err := config.BbConfigurationValidation.ValidateEntry(config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, gitProtocol) + _, err = config.ValidateAndUpdateEntry(path, config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, gitProtocol) if err != nil { logging.Error(err) return } - gitProtocol = gitProtocolI.(string) - - tmpVp.Set(config.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, gitProtocol) - tmpVp.WriteConfig() } if len(args) == 0 { diff --git a/config/config.go b/config/config.go index d668517..9c2d190 100644 --- a/config/config.go +++ b/config/config.go @@ -2,7 +2,21 @@ package config import ( "fmt" + "io" + "io/ioutil" + "os" "strings" + + "github.com/craftamap/bb/util/logging" + "github.com/spf13/viper" +) + +const ( + CONFIG_KEY_AUTH_USERNAME = "auth.username" + CONFIG_KEY_AUTH_PASSWORD = "auth.password" + CONFIG_KEY_GIT_REMOTE = "git.remote" + CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL = "repo.clone.git_protocol" + CONFIG_KEY_PR_SYNC_SYNC_METHOD = "pr.sync.sync_method" ) type Validator func(interface{}) (interface{}, error) @@ -52,23 +66,6 @@ type Entry struct { type Configuration map[string]Entry -func (c Configuration) ValidateEntry(key string, value interface{}) (interface{}, error) { - e, ok := c[key] - if !ok { - return "", fmt.Errorf("key \"%s\" is not a valid key", key) - } - return e.Validator(value) -} - -const ( - CONFIG_KEY_AUTH_USERNAME = "auth.username" - CONFIG_KEY_AUTH_PASSWORD = "auth.password" - CONFIG_KEY_GIT_REMOTE = "git.remote" - CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL = "repo.clone.git_protocol" - CONFIG_KEY_PR_SYNC_SYNC_METHOD = "pr.sync.sync_method" -) - - var BbConfigurationValidation Configuration = map[string]Entry{ CONFIG_KEY_AUTH_USERNAME: { Validator: SimpleStringValidator(), @@ -86,3 +83,107 @@ var BbConfigurationValidation Configuration = map[string]Entry{ Validator: EnumValidator("merge", "rebase"), }, } + +func (c Configuration) ValidateEntry(key string, value interface{}) (interface{}, error) { + e, ok := c[key] + if !ok { + return "", fmt.Errorf("key \"%s\" is not a valid key", key) + } + return e.Validator(value) +} + +func ValidateEntry(key string, value interface{}) (interface{}, error) { + return BbConfigurationValidation.ValidateEntry(key, value) +} + +func ValidateAndUpdateEntry(filepath string, key string, value interface{}) (interface{}, error) { + sanitizedValue, err := ValidateEntry(key, value) + if err != nil { + return "", err + } + + // TODO: Add a filename-to-tmpVp cache - this way, we can prevent creating a new viper every time we want to set a value + vp, err := GetViperForPath(filepath) + if err != nil { + return sanitizedValue, err + } + + vp.Set(key, sanitizedValue) + err = WriteViper(vp, filepath) + + return sanitizedValue, err +} + +func ValidateAndUpdateEntryWithViper(vp viper.Viper, key string, value interface{}) (interface{}, error) { + sanitizedValue, err := ValidateEntry(key, value) + if err != nil { + return "", err + } + + vp.Set(key, sanitizedValue) + err = WriteViper(vp, vp.ConfigFileUsed()) + + return sanitizedValue, err +} + +func WriteViper(vp viper.Viper, path string) error { + // WORKAROUND: currently, WriteConfig does not support writing to `.bb`-files despite setting SetConfigType. + // Therefore, we create a temporary file, write there, and try to copy the file over. + tmpFh, err := ioutil.TempFile(os.TempDir(), "bb-tmpconfig.*.toml") + if err != nil { + logging.Error("Failed to create temporary configuration file") + return err + } + tmpFilename := tmpFh.Name() + logging.Debugf("tmpFilename: %s", tmpFilename) + err = tmpFh.Close() + if err != nil { + logging.Error("Failed to create temporary configuration file") + return err + } + err = vp.WriteConfigAs(tmpFilename) + if err != nil { + logging.Error(fmt.Sprintf("Failed to write temporary config %s: %s", path, err)) + return err + } + err = copyFileContent(tmpFilename, path) + if err != nil { + logging.Error(fmt.Sprintf("Failed to write config %s -> %s: %s", tmpFilename, path, err)) + return err + } + return nil +} + +func copyFileContent(src string, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) // Create or trunicate + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + return err +} + +func GetViperForPath(path string) (viper.Viper, error) { + tmpVp := viper.New() + tmpVp.SetConfigType("toml") + tmpVp.SetConfigFile(path) + err := tmpVp.ReadInConfig() + + return *tmpVp, err +} From 4552740b1d6bae8e4a26a8d58abb360228a9e91f Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 9 Sep 2021 01:48:24 +0200 Subject: [PATCH 08/12] config: typos (#18) --- cmd/commands/config/config.go | 2 +- cmd/root.go | 2 +- config/config.go | 6 +++--- config/path.go | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index c80c1d3..378e010 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -64,7 +64,7 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { return } path := filepath.Join(configDirectory, filename) - // If the config itself does not exist, it's fine (although wierd for global) - we create it now + // If the config itself does not exist, it's fine (although weird for global) - we create it now if _, err := os.Stat(path); os.IsNotExist(err) { logging.Note(fmt.Sprintf("Creating config file %s", path)) fh, err := os.Create(path) diff --git a/cmd/root.go b/cmd/root.go index 6d02c8f..44ec80c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -179,7 +179,7 @@ func initConfig() { } } - // Register Aliases for backward compability + // Register Aliases for backward compatibility viper.RegisterAlias("username", configuration.CONFIG_KEY_AUTH_USERNAME) viper.RegisterAlias("password", configuration.CONFIG_KEY_AUTH_PASSWORD) viper.RegisterAlias("remote", configuration.CONFIG_KEY_GIT_REMOTE) diff --git a/config/config.go b/config/config.go index 9c2d190..78b70b7 100644 --- a/config/config.go +++ b/config/config.go @@ -21,7 +21,7 @@ const ( type Validator func(interface{}) (interface{}, error) -// Enum is just a string of a list +// Enum is just a string of a list. func EnumValidator(validValues ...string) Validator { return func(inputValue interface{}) (interface{}, error) { _, ok := inputValue.(string) @@ -43,7 +43,7 @@ func EnumValidator(validValues ...string) Validator { } } -// SimpleStringValidator validates if a input is a "simple" string - only single-line strings are supported +// SimpleStringValidator validates if a input is a "simple" string - only single-line strings are supported. func SimpleStringValidator() Validator { return func(inputValue interface{}) (interface{}, error) { _, ok := inputValue.(string) @@ -59,7 +59,7 @@ func SimpleStringValidator() Validator { } } -// Entry contains all the data required for Validation and Convertion +// Entry contains all the data required for Validation and Convertion. type Entry struct { Validator Validator } diff --git a/config/path.go b/config/path.go index 3a53912..8c8eece 100644 --- a/config/path.go +++ b/config/path.go @@ -5,7 +5,6 @@ import ( "github.com/kirsle/configdir" ) -// TODO: Extract to util func GetGlobalConfigurationPath() (configDirectory string, filename string) { configDirectory = configdir.LocalConfig("bb") return configDirectory, "configuration.toml" From b0382541eb0b520d790f368263a2a9796f7d8578 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 9 Sep 2021 01:48:38 +0200 Subject: [PATCH 09/12] config: add missing error check (#18) --- cmd/commands/config/config.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index 378e010..b37ab09 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -96,12 +96,14 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { logging.Note(fmt.Sprintf("Setting \"%s\" to \"%s\" in %s", key, newValue, path)) logging.Debugf("%+v", tmpVp.AllSettings()) - // This will most likely save everything as a string - // TODO: find this out and find a way to save bools and numbers tmpVp.Set(key, newValue) logging.Debugf("%+v", tmpVp.AllSettings()) - config.WriteViper(tmpVp, path) + err = config.WriteViper(tmpVp, path) + if err != nil { + logging.Error(err) + return + } logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) } From d42365ae7cd566aa4be7c36b9b354c94d62d5598 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 10 Sep 2021 21:41:31 +0200 Subject: [PATCH 10/12] config: Add Get command (#18) --- cmd/commands/config/config.go | 198 +++++++++++++++++++++------------- config/config.go | 2 + 2 files changed, 125 insertions(+), 75 deletions(-) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index b37ab09..f1a25d1 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/craftamap/bb/cmd/options" "github.com/craftamap/bb/config" @@ -31,81 +30,9 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { }, Run: func(_ *cobra.Command, args []string) { if Get { - // TODO: code here + GetValue(args) } else { - key := args[0] - inputValue := args[1] - - newValue, err := config.BbConfigurationValidation.ValidateEntry(key, inputValue) - - if err != nil { - logging.Error(fmt.Sprintf("failed to validate %s: %s", inputValue, err)) - return - } - - var configDirectory string - var filename string - if Local { - var err error - configDirectory, filename, err = config.GetLocalConfigurationPath() - if err != nil { - logging.Error(err) - return - } - } else { - configDirectory, filename = config.GetGlobalConfigurationPath() - } - - // If the directory does not exist, something is off: - // - The global configuration directory get's created in root - // - The local configuration directory is a repository, which always exists - if _, err := os.Stat(configDirectory); os.IsNotExist(err) { - logging.Error(fmt.Sprintf("Expected directory \"%s\", but the directory does not exist", configDirectory)) - return - } - path := filepath.Join(configDirectory, filename) - // If the config itself does not exist, it's fine (although weird for global) - we create it now - if _, err := os.Stat(path); os.IsNotExist(err) { - logging.Note(fmt.Sprintf("Creating config file %s", path)) - fh, err := os.Create(path) - if err != nil { - logging.Error(fmt.Sprintf("Unable to create file %s", path)) - } - fh.Close() - } - - logging.Debugf("Config file path: %s", path) - - tmpVp, err := config.GetViperForPath(path) - if err != nil { - logging.Error(err) - return - } - - isSetAlready := tmpVp.IsSet(key) - oldValue := tmpVp.Get(key) - - if isSetAlready { - // Don't print old password values - if strings.ToLower(key) == config.CONFIG_KEY_AUTH_PASSWORD { - oldValue = "(truncated)" - } - logging.Warning(fmt.Sprintf("\"%s\" is already set. This will overwrite the value of \"%s\" from \"%s\" to \"%s\".", key, key, oldValue, newValue)) - } - - logging.Note(fmt.Sprintf("Setting \"%s\" to \"%s\" in %s", key, newValue, path)) - logging.Debugf("%+v", tmpVp.AllSettings()) - - tmpVp.Set(key, newValue) - logging.Debugf("%+v", tmpVp.AllSettings()) - - err = config.WriteViper(tmpVp, path) - if err != nil { - logging.Error(err) - return - } - - logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) + SetValue(args) } }, } @@ -115,3 +42,124 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { rootCmd.AddCommand(&configCommand) } + +func SetValue(args []string) { + key := args[0] + inputValue := args[1] + + newValue, err := config.BbConfigurationValidation.ValidateEntry(key, inputValue) + + if err != nil { + logging.Error(fmt.Sprintf("failed to validate %s: %s", inputValue, err)) + return + } + + var configDirectory string + var filename string + if Local { + var err error + configDirectory, filename, err = config.GetLocalConfigurationPath() + if err != nil { + logging.Error(err) + return + } + } else { + configDirectory, filename = config.GetGlobalConfigurationPath() + } + + // If the directory does not exist, something is off: + // - The global configuration directory get's created in root + // - The local configuration directory is a repository, which always exists + if _, err := os.Stat(configDirectory); os.IsNotExist(err) { + logging.Error(fmt.Sprintf("Expected directory \"%s\", but the directory does not exist", configDirectory)) + return + } + path := filepath.Join(configDirectory, filename) + // If the config itself does not exist, it's fine (although weird for global) - we create it now + if _, err := os.Stat(path); os.IsNotExist(err) { + logging.Note(fmt.Sprintf("Creating config file %s", path)) + fh, err := os.Create(path) + if err != nil { + logging.Error(fmt.Sprintf("Unable to create file %s", path)) + } + fh.Close() + } + + logging.Debugf("Config file path: %s", path) + + tmpVp, err := config.GetViperForPath(path) + if err != nil { + logging.Error(err) + return + } + + isSetAlready := tmpVp.IsSet(key) + oldValue := tmpVp.Get(key) + + if isSetAlready { + // Don't print old password values + if config.BbConfigurationValidation[key].Hidden { + oldValue = "(hidden)" + } + logging.Warning(fmt.Sprintf("\"%s\" is already set. This will overwrite the value of \"%s\" from \"%s\" to \"%s\".", key, key, oldValue, newValue)) + } + + logging.Note(fmt.Sprintf("Setting \"%s\" to \"%s\" in %s", key, newValue, path)) + logging.Debugf("%+v", tmpVp.AllSettings()) + + tmpVp.Set(key, newValue) + logging.Debugf("%+v", tmpVp.AllSettings()) + + err = config.WriteViper(tmpVp, path) + if err != nil { + logging.Error(err) + return + } + + logging.SuccessExclamation(fmt.Sprintf("Successfully updated configuration %s", path)) +} + +func GetValue(args []string) { + key := args[0] + + entry, ok := config.BbConfigurationValidation[key] + if !ok { + logging.Warning(fmt.Sprintf("\"%s\" is not a valid key", key)) + return + } + + var configDirectory string + var filename string + if Local { + var err error + configDirectory, filename, err = config.GetLocalConfigurationPath() + if err != nil { + logging.Error(err) + return + } + } else { + configDirectory, filename = config.GetGlobalConfigurationPath() + } + + path := filepath.Join(configDirectory, filename) + if _, err := os.Stat(path); os.IsNotExist(err) { + logging.Error(fmt.Sprintf("config file %s does not exist yet", path)) + return + } + + tmpVp, err := config.GetViperForPath(path) + if err != nil { + logging.Error(err) + return + } + value := tmpVp.Get(key) + if value == nil { + logging.Warning(fmt.Sprintf("%s is not set yet.", key)) + return + } + if entry.Hidden { + value = "(hidden)" + } + + logging.Success(fmt.Sprintf("%s = %s", key, value)) +} diff --git a/config/config.go b/config/config.go index 78b70b7..2fa3780 100644 --- a/config/config.go +++ b/config/config.go @@ -62,6 +62,7 @@ func SimpleStringValidator() Validator { // Entry contains all the data required for Validation and Convertion. type Entry struct { Validator Validator + Hidden bool } type Configuration map[string]Entry @@ -72,6 +73,7 @@ var BbConfigurationValidation Configuration = map[string]Entry{ }, CONFIG_KEY_AUTH_PASSWORD: { Validator: SimpleStringValidator(), + Hidden: true, }, CONFIG_KEY_GIT_REMOTE: { Validator: SimpleStringValidator(), From 1b20feb0d9b829b7e9613fc6b9b97993c38543bc Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 10 Sep 2021 22:22:26 +0200 Subject: [PATCH 11/12] config: Add --get-all option (#18) --- cmd/commands/config/config.go | 53 ++++++++++++++++++++++++++++++++--- config/config.go | 2 +- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index f1a25d1..28fa21c 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -12,8 +12,9 @@ import ( ) var ( - Local bool - Get bool + Local bool + Get bool + GetAll bool ) func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { @@ -21,15 +22,26 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { Use: "config", Short: "configure bb", Long: "configure bb", + PreRunE: func(_ *cobra.Command, _ []string) error { + if Get && GetAll { + logging.Error("--get and --get-all are mutually exclusive") + return fmt.Errorf("") // FIXME: return empty error, so the command fails, but we can use our own method to print out the error message + } + return nil + }, Args: func(cmd *cobra.Command, args []string) error { - if Get { + if GetAll { + return cobra.ExactArgs(0)(cmd, args) + } else if Get { return cobra.ExactArgs(1)(cmd, args) } else { return cobra.ExactArgs(2)(cmd, args) } }, Run: func(_ *cobra.Command, args []string) { - if Get { + if GetAll { + GetAllValues(args) + } else if Get { GetValue(args) } else { SetValue(args) @@ -39,10 +51,43 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { configCommand.Flags().BoolVar(&Local, "local", false, "local allows to modify the local configuration") configCommand.Flags().BoolVar(&Get, "get", false, "gets a configuration value instead of setting it") + configCommand.Flags().BoolVar(&GetAll, "get-all", false, "prints out all configuration values of the selected configuration") rootCmd.AddCommand(&configCommand) } +func GetAllValues(_ []string) { + var configDirectory string + var filename string + if Local { + var err error + configDirectory, filename, err = config.GetLocalConfigurationPath() + if err != nil { + logging.Error(err) + return + } + } else { + configDirectory, filename = config.GetGlobalConfigurationPath() + } + path := filepath.Join(configDirectory, filename) + tmpVp, err := config.GetViperForPath(path) + if err != nil { + logging.Error(err) + return + } + for key, entry := range config.BbConfigurationValidation { + value := tmpVp.Get(key) + if value == nil { + continue + } + if entry.Hidden { + value = "(hidden)" + } + + fmt.Printf("%s = %s\n", key, value) + } +} + func SetValue(args []string) { key := args[0] inputValue := args[1] diff --git a/config/config.go b/config/config.go index 2fa3780..93fa76f 100644 --- a/config/config.go +++ b/config/config.go @@ -73,7 +73,7 @@ var BbConfigurationValidation Configuration = map[string]Entry{ }, CONFIG_KEY_AUTH_PASSWORD: { Validator: SimpleStringValidator(), - Hidden: true, + Hidden: true, }, CONFIG_KEY_GIT_REMOTE: { Validator: SimpleStringValidator(), From b6bf94702425501e65d25986478315762a78d5e2 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 26 Nov 2021 00:35:30 +0100 Subject: [PATCH 12/12] config: Add better help text for config (#18) --- cmd/commands/config/config.go | 14 ++++++++++++-- cmd/root.go | 3 --- config/config.go | 8 ++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/commands/config/config.go b/cmd/commands/config/config.go index 28fa21c..271d420 100644 --- a/cmd/commands/config/config.go +++ b/cmd/commands/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/craftamap/bb/cmd/options" "github.com/craftamap/bb/config" @@ -21,7 +22,16 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { configCommand := cobra.Command{ Use: "config", Short: "configure bb", - Long: "configure bb", + Long: fmt.Sprintf(`configure bb and change it's behaviour. +bb sources configuration values from multiple sources: + 1. The global configuration (usually located at $HOME/.config/bb/configuration.toml) + 2. The local configuration (a .bb file in your repository root) + 3. Environment variables + 4. command-line flags +This command allows you to modify and retrieve the configuration values without editing the configuration values by yourself. + +The following keys are supported: + %s`, strings.Join(config.ConfigKeys, ", ")), PreRunE: func(_ *cobra.Command, _ []string) error { if Get && GetAll { logging.Error("--get and --get-all are mutually exclusive") @@ -49,7 +59,7 @@ func Add(rootCmd *cobra.Command, _ *options.GlobalOptions) { }, } - configCommand.Flags().BoolVar(&Local, "local", false, "local allows to modify the local configuration") + configCommand.Flags().BoolVar(&Local, "local", false, "modify or retrieve the local configuration") configCommand.Flags().BoolVar(&Get, "get", false, "gets a configuration value instead of setting it") configCommand.Flags().BoolVar(&GetAll, "get-all", false, "prints out all configuration values of the selected configuration") diff --git a/cmd/root.go b/cmd/root.go index 44ec80c..2fcc405 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -185,7 +185,4 @@ func initConfig() { viper.RegisterAlias("remote", configuration.CONFIG_KEY_GIT_REMOTE) viper.RegisterAlias("git_protocol", configuration.CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL) viper.RegisterAlias("sync-method", configuration.CONFIG_KEY_PR_SYNC_SYNC_METHOD) - - logging.Debugf("%+v", viper.AllSettings()) - } diff --git a/config/config.go b/config/config.go index 93fa76f..bb46f28 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,14 @@ const ( CONFIG_KEY_PR_SYNC_SYNC_METHOD = "pr.sync.sync_method" ) +var ConfigKeys = []string{ + CONFIG_KEY_AUTH_USERNAME, + CONFIG_KEY_AUTH_PASSWORD, + CONFIG_KEY_GIT_REMOTE, + CONFIG_KEY_REPO_CLONE_GIT_PROTOCOL, + CONFIG_KEY_PR_SYNC_SYNC_METHOD, +} + type Validator func(interface{}) (interface{}, error) // Enum is just a string of a list.