diff --git a/docs/azdo_config_list.md b/docs/azdo_config_list.md index 234f3a7b..d5b9493f 100644 --- a/docs/azdo_config_list.md +++ b/docs/azdo_config_list.md @@ -6,6 +6,10 @@ azdo config list [flags] ### Options +* `--all` + + Show config options which are not configured + * `-o`, `--organization` `string` Get per-organization configuration diff --git a/docs/azdo_config_set.md b/docs/azdo_config_set.md index b0073453..ed4a1e00 100644 --- a/docs/azdo_config_set.md +++ b/docs/azdo_config_set.md @@ -10,6 +10,10 @@ azdo config set [flags] Set per-organization setting +* `-r`, `--remove` + + Remove config item for an organization, so that the default value will be in effect again + ### Examples @@ -18,6 +22,7 @@ $ azdo config set editor vim $ azdo config set editor "code --wait" $ azdo config set git_protocol ssh --organization myorg $ azdo config set prompt disabled +$ azdo config set -r -o myorg git_protocol ``` ### See also diff --git a/docs/azdo_help_reference.md b/docs/azdo_help_reference.md index 65722f92..a321cb3a 100644 --- a/docs/azdo_help_reference.md +++ b/docs/azdo_help_reference.md @@ -57,6 +57,7 @@ Print the value of a given configuration key Print a list of configuration keys and values ``` + --all Show config options which are not configured -o, --organization string Get per-organization configuration ```` @@ -66,6 +67,7 @@ Update configuration with a value for the given key ``` -o, --organization string Set per-organization setting +-r, --remove Remove config item for an organization, so that the default value will be in effect again ```` ## `azdo project [flags]` diff --git a/internal/cmd/config/get/get.go b/internal/cmd/config/get/get.go index aaf82b60..c50fe7e9 100644 --- a/internal/cmd/config/get/get.go +++ b/internal/cmd/config/get/get.go @@ -4,14 +4,15 @@ import ( "fmt" "github.com/MakeNowJust/heredoc" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/tmeckel/azdo-cli/internal/cmd/util" "github.com/tmeckel/azdo-cli/internal/config" ) type getOptions struct { - OrganizationName string - Key string + organizationName string + key string } func NewCmdConfigGet(ctx util.CmdContext) *cobra.Command { @@ -26,13 +27,13 @@ func NewCmdConfigGet(ctx util.CmdContext) *cobra.Command { `), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Key = args[0] + opts.key = args[0] return getRun(ctx, opts) }, } - cmd.Flags().StringVarP(&opts.OrganizationName, "organization", "o", "", "Get per-organization setting") + cmd.Flags().StringVarP(&opts.organizationName, "organization", "o", "", "Get per-organization setting") return cmd } @@ -42,33 +43,44 @@ func getRun(ctx util.CmdContext, opts *getOptions) (err error) { if err != nil { return util.FlagErrorf("error getting io configuration: %w", err) } - iostreams, err := ctx.IOStreams() + iostrms, err := ctx.IOStreams() if err != nil { return util.FlagErrorf("error getting io streams: %w", err) } + if opts.organizationName != "" { + if !lo.Contains(cfg.Authentication().GetOrganizations(), opts.organizationName) { + fmt.Fprintf( + iostrms.ErrOut, + "You are not logged the Azure DevOps organization %q. Run %s to authenticate.\n", + opts.organizationName, iostrms.ColorScheme().Bold("azdo auth login"), + ) + return util.ErrSilent + } + } + // search keyring storage when fetching the `oauth_token` value - if opts.OrganizationName != "" && opts.Key == "pat" { - token, err := cfg.Authentication().GetToken(opts.OrganizationName) + if opts.organizationName != "" && opts.key == "pat" { + token, err := cfg.Authentication().GetToken(opts.organizationName) if err != nil { - return util.FlagErrorf("failed to get token for organization %s; %w", opts.OrganizationName, err) + return util.FlagErrorf("failed to get token for organization %s; %w", opts.organizationName, err) } - fmt.Fprintf(iostreams.Out, "%s\n", token) + fmt.Fprintf(iostrms.Out, "%s\n", token) return nil } keys := []string{} - if opts.OrganizationName != "" { - keys = append(keys, config.Organizations, opts.OrganizationName) + if opts.organizationName != "" { + keys = append(keys, config.Organizations, opts.organizationName) } - keys = append(keys, opts.Key) + keys = append(keys, opts.key) val, err := cfg.GetOrDefault(keys) if err != nil { return err } if val != "" { - fmt.Fprintf(iostreams.Out, "%s\n", val) + fmt.Fprintf(iostrms.Out, "%s\n", val) } return nil } diff --git a/internal/cmd/config/list/list.go b/internal/cmd/config/list/list.go index 50071e01..fbfc7421 100644 --- a/internal/cmd/config/list/list.go +++ b/internal/cmd/config/list/list.go @@ -3,6 +3,7 @@ package list import ( "fmt" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/tmeckel/azdo-cli/internal/cmd/util" "github.com/tmeckel/azdo-cli/internal/config" @@ -10,6 +11,7 @@ import ( type listOptions struct { organizationName string + all bool } func NewCmdConfigList(ctx util.CmdContext) *cobra.Command { @@ -26,7 +28,7 @@ func NewCmdConfigList(ctx util.CmdContext) *cobra.Command { } cmd.Flags().StringVarP(&opts.organizationName, "organization", "o", "", "Get per-organization configuration") - + cmd.Flags().BoolVar(&opts.all, "all", false, "Show config options which are not configured") return cmd } @@ -35,24 +37,29 @@ func listRun(ctx util.CmdContext, opts *listOptions) error { if err != nil { return util.FlagErrorf("error getting io configuration: %w", err) } - iostreams, err := ctx.IOStreams() + iostrms, err := ctx.IOStreams() if err != nil { return util.FlagErrorf("error getting io streams: %w", err) } - var host string if opts.organizationName != "" { - host = opts.organizationName - } else { - host, _ = cfg.Authentication().GetDefaultOrganization() + if !lo.Contains(cfg.Authentication().GetOrganizations(), opts.organizationName) { + fmt.Fprintf( + iostrms.ErrOut, + "You are not logged the Azure DevOps organization %q. Run %s to authenticate.\n", + opts.organizationName, iostrms.ColorScheme().Bold("azdo auth login"), + ) + return util.ErrSilent + } } configOptions := config.Options() var keys []string - if host != "" { + if opts.organizationName != "" { keys = make([]string, 3) - keys = append(keys, config.Organizations, host) + keys[0] = config.Organizations + keys[1] = opts.organizationName } else { keys = make([]string, 1) } @@ -63,7 +70,9 @@ func listRun(ctx util.CmdContext, opts *listOptions) error { if err != nil { return err } - fmt.Fprintf(iostreams.Out, "%s=%s\n", key.Key, val) + if val != "" || opts.all { + fmt.Fprintf(iostrms.Out, "%s=%s\n", key.Key, val) + } } return nil diff --git a/internal/cmd/config/set/set.go b/internal/cmd/config/set/set.go index 5904e96b..8d4dd055 100644 --- a/internal/cmd/config/set/set.go +++ b/internal/cmd/config/set/set.go @@ -6,15 +6,17 @@ import ( "strings" "github.com/MakeNowJust/heredoc" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/tmeckel/azdo-cli/internal/cmd/util" "github.com/tmeckel/azdo-cli/internal/config" ) type setOptions struct { - Key string - Value string - OrganizationName string + key string + value string + organizationName string + remove bool } func NewCmdConfigSet(ctx util.CmdContext) *cobra.Command { @@ -28,17 +30,33 @@ func NewCmdConfigSet(ctx util.CmdContext) *cobra.Command { $ azdo config set editor "code --wait" $ azdo config set git_protocol ssh --organization myorg $ azdo config set prompt disabled + $ azdo config set -r -o myorg git_protocol `), - Args: cobra.ExactArgs(2), + PreRunE: func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("remove") { + if !cmd.Flags().Changed("organization") { + return errors.New("configration values can only be removed for organizations. Please specify the organization via -o") + } + if len(args) != 1 { + return fmt.Errorf("accepts %d arg(s), received %d", 1, len(args)) + } + } else if len(args) != 2 { + return fmt.Errorf("accepts %d arg(s), received %d", 2, len(args)) + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { - opts.Key = args[0] - opts.Value = args[1] + opts.key = args[0] + if !opts.remove { + opts.value = args[1] + } return setRun(ctx, opts) }, } - cmd.Flags().StringVarP(&opts.OrganizationName, "organization", "o", "", "Set per-organization setting") + cmd.Flags().StringVarP(&opts.organizationName, "organization", "o", "", "Set per-organization setting") + cmd.Flags().BoolVarP(&opts.remove, "remove", "r", false, "Remove config item for an organization, so that the default value will be in effect again") return cmd } @@ -48,33 +66,54 @@ func setRun(ctx util.CmdContext, opts *setOptions) (err error) { if err != nil { return util.FlagErrorf("error getting io configuration: %w", err) } - iostreams, err := ctx.IOStreams() + iostrms, err := ctx.IOStreams() if err != nil { return util.FlagErrorf("error getting io streams: %w", err) } - err = validateKey(opts.Key) + err = validateKey(opts.key) if err != nil { - warningIcon := iostreams.ColorScheme().WarningIcon() - fmt.Fprintf(iostreams.ErrOut, "%s warning: '%s' is not a known configuration key\n", warningIcon, opts.Key) + warningIcon := iostrms.ColorScheme().WarningIcon() + fmt.Fprintf(iostrms.ErrOut, "%s warning: '%s' is not a known configuration key\n", warningIcon, opts.key) } - err = validateValue(opts.Key, opts.Value) - if err != nil { - var invalidValue InvalidValueError - if errors.As(err, &invalidValue) { - var values []string - for _, v := range invalidValue.ValidValues { - values = append(values, fmt.Sprintf("'%s'", v)) - } - return fmt.Errorf("failed to set %q to %q: valid values are %v", opts.Key, opts.Value, strings.Join(values, ", ")) + if opts.organizationName != "" { + if !lo.Contains(cfg.Authentication().GetOrganizations(), opts.organizationName) { + fmt.Fprintf( + iostrms.ErrOut, + "You are not logged the Azure DevOps organization %q. Run %s to authenticate.\n", + opts.organizationName, iostrms.ColorScheme().Bold("azdo auth login"), + ) + return util.ErrSilent } } - if opts.OrganizationName != "" { - cfg.Set([]string{config.Organizations, opts.OrganizationName, opts.Key}, opts.Value) + if opts.remove { + err = cfg.Remove([]string{config.Organizations, opts.organizationName, opts.key}) + if err != nil { + if !errors.Is(err, &config.KeyNotFoundError{}) { + return err + } + return nil // no need to write configuration because it didn't change + } } else { - cfg.Set([]string{opts.Key}, opts.Value) + err = validateValue(opts.key, opts.value) + if err != nil { + var invalidValue InvalidValueError + if errors.As(err, &invalidValue) { + var values []string + for _, v := range invalidValue.ValidValues { + values = append(values, fmt.Sprintf("'%s'", v)) + } + return fmt.Errorf("failed to set %q to %q: valid values are %v", opts.key, opts.value, strings.Join(values, ", ")) + } + } + + if opts.organizationName != "" { + cfg.Set([]string{config.Organizations, opts.organizationName, opts.key}, opts.value) + } else { + cfg.Set([]string{opts.key}, opts.value) + } } err = cfg.Write() diff --git a/internal/config/config.go b/internal/config/config.go index 694cabf3..4f6af615 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,5 +1,7 @@ package config +import "go.uber.org/zap" + const ( Aliases = "aliases" Organizations = "organizations" @@ -46,23 +48,33 @@ func NewConfig() (Config, error) { } func (c *cfg) Keys(keys []string) (values []string, err error) { + zap.L().Sugar().Debugf("Keys: %+v", keys) + values, err = c.cfg.Keys(keys) return } func (c *cfg) Get(keys []string) (string, error) { + zap.L().Sugar().Debugf("Get: %+v", keys) + return c.cfg.Get(keys) } func (c *cfg) GetOrDefault(keys []string) (val string, err error) { + zap.L().Sugar().Debugf("GetOrDefault: %+v", keys) + return c.cfg.GetOrDefault(keys) } func (c *cfg) Set(keys []string, value string) { + zap.L().Sugar().Debugf("Set: %+v -> %q", keys, value) + c.cfg.Set(keys, value) } func (c *cfg) Remove(keys []string) error { + zap.L().Sugar().Debugf("Remove: %+v", keys) + return c.cfg.Remove(keys) }