From e6131177a2b18b2fd9716fa2d2deefb09ac05977 Mon Sep 17 00:00:00 2001 From: Cyril David Date: Tue, 23 Mar 2021 18:31:15 -0700 Subject: [PATCH] apps, apis, rules: first stab at generalizing picker options [CLI-82] (#182) --- internal/cli/apis.go | 35 +++++++++++-- internal/cli/apps.go | 34 ++++++++++-- internal/cli/arguments.go | 37 ++++++++++++- internal/cli/cli.go | 8 --- internal/cli/picker_options.go | 32 ++++++++++++ internal/cli/rules.go | 94 +++++++++++----------------------- 6 files changed, 157 insertions(+), 83 deletions(-) create mode 100644 internal/cli/picker_options.go diff --git a/internal/cli/apis.go b/internal/cli/apis.go index 122818545..2733fb8ec 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "github.com/auth0/auth0-cli/internal/ansi" @@ -113,7 +114,8 @@ auth0 apis show }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - if err := apiID.Ask(cmd, &inputs.ID); err != nil { + err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) + if err != nil { return err } } else { @@ -217,7 +219,8 @@ auth0 apis update --name myapi var current *management.ResourceServer if len(args) == 0 { - if err := apiID.Ask(cmd, &inputs.ID); err != nil { + err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) + if err != nil { return err } } else { @@ -289,7 +292,8 @@ auth0 apis delete }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - if err := apiID.Ask(cmd, &inputs.ID); err != nil { + err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) + if err != nil { return err } } else { @@ -333,7 +337,8 @@ auth0 apis scopes list }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - if err := apiID.Ask(cmd, &inputs.ID); err != nil { + err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) + if err != nil { return err } } else { @@ -368,3 +373,25 @@ func apiScopesFor(scopes []string) []*management.ResourceServerScope { return models } + +func (c *cli) apiPickerOptions() (pickerOptions, error) { + list, err := c.api.ResourceServer.List() + if err != nil { + return nil, err + } + + // NOTE: because client names are not unique, we'll just number these + // labels. + var opts pickerOptions + for _, r := range list.ResourceServers { + label := fmt.Sprintf("%s %s", r.GetName(), ansi.Faint("("+r.GetIdentifier()+")")) + + opts = append(opts, pickerOption{value: r.GetID(), label: label}) + } + + if len(opts) == 0 { + return nil, errors.New("There are currently no applications.") + } + + return opts, nil +} diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 100cf1dc9..048e9840f 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "strings" @@ -165,7 +166,8 @@ auth0 apps show }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - if err := appID.Ask(cmd, &inputs.ID); err != nil { + err := appID.Pick(cmd, &inputs.ID, cli.appPickerOptions) + if err != nil { return err } } else { @@ -209,7 +211,8 @@ auth0 apps delete }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - if err := appID.Ask(cmd, &inputs.ID); err != nil { + err := appID.Pick(cmd, &inputs.ID, cli.appPickerOptions) + if err != nil { return err } } else { @@ -389,9 +392,9 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] RunE: func(cmd *cobra.Command, args []string) error { var current *management.Client - // Get app id if len(args) == 0 { - if err := appID.Ask(cmd, &inputs.ID); err != nil { + err := appID.Pick(cmd, &inputs.ID, cli.appPickerOptions) + if err != nil { return err } } else { @@ -680,3 +683,26 @@ func interfaceToStringSlice(s []interface{}) []string { } return result } + +func (c *cli) appPickerOptions() (pickerOptions, error) { + list, err := c.api.Client.List() + if err != nil { + return nil, err + } + + // NOTE: because client names are not unique, we'll just number these + // labels. + var opts pickerOptions + for _, c := range list.Clients { + value := c.GetClientID() + label := fmt.Sprintf("%s %s", c.GetName(), ansi.Faint("("+value+")")) + + opts = append(opts, pickerOption{value: value, label: label}) + } + + if len(opts) == 0 { + return nil, errors.New("There are currently no applications.") + } + + return opts, nil +} diff --git a/internal/cli/arguments.go b/internal/cli/arguments.go index 52e410e13..be5d59cb9 100644 --- a/internal/cli/arguments.go +++ b/internal/cli/arguments.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/auth0/auth0-cli/internal/ansi" "github.com/spf13/cobra" ) @@ -31,10 +32,42 @@ func (a *Argument) Ask(cmd *cobra.Command, value interface{}) error { return askArgument(cmd, a, value) } +type pickerOptionsFunc func() (pickerOptions, error) + +func (a *Argument) Pick(cmd *cobra.Command, result *string, fn pickerOptionsFunc) error { + var opts pickerOptions + err := ansi.Waiting(func() error { + var err error + opts, err = fn() + return err + }) + + if err != nil { + return err + } + + defaultLabel := opts.defaultLabel() + var val string + if err := selectArgument(cmd, a, &val, opts.labels(), &defaultLabel); err != nil { + return err + } + + *result = opts.getValue(val) + return nil +} + +func selectArgument(cmd *cobra.Command, a *Argument, value interface{}, options []string, defaultValue *string) error { + if canPrompt(cmd) { + return _select(cmd, a, value, options, defaultValue, false) + } + + return fmt.Errorf("Missing a required argument: %s", a.GetName()) +} + func askArgument(cmd *cobra.Command, i commandInput, value interface{}) error { if canPrompt(cmd) { return ask(cmd, i, value, nil, true) - } else { - return fmt.Errorf("Missing a required argument: %s", i.GetName()) } + + return fmt.Errorf("Missing a required argument: %s", i.GetName()) } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 8d768626e..9cf7221ea 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -381,11 +381,3 @@ func prepareInteractivity(cmd *cobra.Command) { }) } } - -func flagOptionsFromMapping(mapping map[string]string) []string { - result := make([]string, 0, len(mapping)) - for k := range mapping { - result = append(result, k) - } - return result -} diff --git a/internal/cli/picker_options.go b/internal/cli/picker_options.go new file mode 100644 index 000000000..e73d431ad --- /dev/null +++ b/internal/cli/picker_options.go @@ -0,0 +1,32 @@ +package cli + +type pickerOptions []pickerOption + +func (p pickerOptions) labels() []string { + result := make([]string, 0, len(p)) + for _, o := range p { + result = append(result, o.label) + } + return result +} + +func (p pickerOptions) defaultLabel() string { + if len(p) > 0 { + return p[0].label + } + return "" +} + +func (p pickerOptions) getValue(label string) string { + for _, o := range p { + if o.label == label { + return o.value + } + } + return "" +} + +type pickerOption struct { + label string + value string +} diff --git a/internal/cli/rules.go b/internal/cli/rules.go index f84b4fc1e..dd647ac7c 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "github.com/auth0/auth0-cli/internal/ansi" @@ -11,6 +12,11 @@ import ( ) var ( + ruleID = Argument{ + Name: "Rule ID", + Help: "Id of the rule.", + } + ruleName = Flag{ Name: "Name", LongForm: "name", @@ -26,8 +32,6 @@ var ( Help: "Template to use for the rule.", } - ruleTemplateOptions = flagOptionsFromMapping(ruleTemplateMappings) - ruleEnabled = Flag{ Name: "Enabled", LongForm: "enabled", @@ -35,12 +39,12 @@ var ( Help: "Enable (or disable) a rule.", } - ruleTemplateMappings = map[string]string{ - "Empty rule": ruleTemplateEmptyRule, - "Add email to access token": ruleTemplateAddEmailToAccessToken, - "Check last password reset": ruleTemplateCheckLastPasswordReset, - "IP address allow list": ruleTemplateIPAddressAllowList, - "IP address deny list": ruleTemplateIPAddressDenyList, + ruleTemplateOptions = pickerOptions{ + {"Empty rule", ruleTemplateEmptyRule}, + {"Add email to access token", ruleTemplateAddEmailToAccessToken}, + {"Check last password reset", ruleTemplateCheckLastPasswordReset}, + {"IP address allow list", ruleTemplateIPAddressAllowList}, + {"IP address deny list", ruleTemplateIPAddressDenyList}, } ) @@ -110,7 +114,7 @@ auth0 rules create --name "My Rule" --template [empty-rule]" return err } - if err := ruleTemplate.Select(cmd, &inputs.Template, ruleTemplateOptions, nil); err != nil { + if err := ruleTemplate.Select(cmd, &inputs.Template, ruleTemplateOptions.labels(), nil); err != nil { return err } @@ -118,7 +122,7 @@ auth0 rules create --name "My Rule" --template [empty-rule]" // `--stdin` based commands. For now we don't have // those yet, so keeping this simple. script, err := prompt.CaptureInputViaEditor( - ruleTemplateMappings[inputs.Template], + ruleTemplateOptions.getValue(inputs.Template), inputs.Name+".*.js", ) if err != nil { @@ -171,18 +175,10 @@ auth0 rules show if len(args) > 0 { inputs.ID = args[0] } else { - // TODO(cyx): Consider making a primitive for - // Argument to ask using a provided func. - var err error - inputs.ID, err = promptForRuleViaDropdown(cli, cmd) + err := ruleID.Pick(cmd, &inputs.ID, cli.rulePickerOptions) if err != nil { return err } - - if inputs.ID == "" { - cli.renderer.Infof("There are currently no rules.") - return nil - } } var rule *management.Rule @@ -215,7 +211,7 @@ func deleteRuleCmd(cli *cli) *cobra.Command { Short: "Delete a rule", Long: `Delete a rule: -auth0 rules delete rul_d2VSaGlyaW5n`, +auth0 rules delete `, PreRun: func(cmd *cobra.Command, args []string) { prepareInteractivity(cmd) }, @@ -223,18 +219,10 @@ auth0 rules delete rul_d2VSaGlyaW5n`, if len(args) > 0 { inputs.ID = args[0] } else { - // TODO(cyx): Consider making a primitive for - // Argument to ask using a provided func. - var err error - inputs.ID, err = promptForRuleViaDropdown(cli, cmd) + err := ruleID.Pick(cmd, &inputs.ID, cli.rulePickerOptions) if err != nil { return err } - - if inputs.ID == "" { - cli.renderer.Infof("There are currently no rules.") - return nil - } } err := ansi.Spinner("Deleting rule", func() error { @@ -264,7 +252,7 @@ func updateRuleCmd(cli *cli) *cobra.Command { Short: "Update a rule", Long: `Update a rule: -auth0 rules update --id rul_d2VSaGlyaW5n --name "My Updated Rule" --enabled=false +auth0 rules update --name "My Updated Rule" --enabled=false `, PreRun: func(cmd *cobra.Command, args []string) { prepareInteractivity(cmd) @@ -273,18 +261,10 @@ auth0 rules update --id rul_d2VSaGlyaW5n --name "My Updated Rule" --enabled=fal if len(args) > 0 { inputs.ID = args[0] } else { - // TODO(cyx): Consider making a primitive for - // Argument to ask using a provided func. - var err error - inputs.ID, err = promptForRuleViaDropdown(cli, cmd) + err := ruleID.Pick(cmd, &inputs.ID, cli.rulePickerOptions) if err != nil { return err } - - if inputs.ID == "" { - cli.renderer.Infof("There are currently no rules.") - return nil - } } var rule *management.Rule @@ -345,36 +325,20 @@ auth0 rules update --id rul_d2VSaGlyaW5n --name "My Updated Rule" --enabled=fal return cmd } -func promptForRuleViaDropdown(cli *cli, cmd *cobra.Command) (id string, err error) { - dropdown := Flag{Name: "Rule"} - - var rules []*management.Rule - - // == Start experimental dropdown for names => id. - // TODO(cyx): Consider extracting this - // pattern once we've done more of it. - err = ansi.Waiting(func() error { - list, err := cli.api.Rule.List() - if err != nil { - return err - } - rules = list.Rules - return nil - }) - - if err != nil || len(rules) == 0 { - return "", err +func (c *cli) rulePickerOptions() (pickerOptions, error) { + list, err := c.api.Rule.List() + if err != nil { + return nil, err } - mapping := map[string]string{} - for _, r := range rules { - mapping[r.GetName()] = r.GetID() + var opts pickerOptions + for _, r := range list.Rules { + opts = append(opts, pickerOption{value: r.GetID(), label: r.GetName()}) } - var name string - if err := dropdown.Select(cmd, &name, flagOptionsFromMapping(mapping), nil); err != nil { - return "", err + if len(opts) == 0 { + return nil, errors.New("There are currently no rules.") } - return mapping[name], nil + return opts, nil }