From fdbbb0f7047de101fd8c2397d1e3279fe8fe2451 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 19:51:30 -0300 Subject: [PATCH 1/6] Add interactive prompts to APIs --- internal/cli/apis.go | 65 +++++++++++++++++++++++++++------------ internal/cli/cli.go | 10 ++++++ internal/prompt/prompt.go | 4 +-- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/internal/cli/apis.go b/internal/cli/apis.go index e7198a9bc..57f67c38e 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -1,6 +1,7 @@ package cli import ( + "github.com/AlecAivazis/survey/v2" "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/prompt" "github.com/spf13/cobra" @@ -55,8 +56,8 @@ Lists your existing APIs. To create one try: func createApiCmd(cli *cli) *cobra.Command { var flags struct { - name string - identifier string + Name string + Identifier string } cmd := &cobra.Command{ @@ -66,10 +67,22 @@ func createApiCmd(cli *cli) *cobra.Command { auth0 apis create --name myapi --identifier http://my-api `, + PreRun: func(cmd *cobra.Command, args []string) { + checkFlags(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { + if !hasFlags(cmd) { + name := prompt.TextInput("name", "Name:", "Name of the API. You can change the API name later in the API settings.", true) + identifier := prompt.TextInput("identifier", "Identifier:", "Identifier of the API. Cannot be changed once set.", true) + + if err := prompt.Ask([]*survey.Question {name, identifier}, &flags); err != nil { + return err + } + } + api := &management.ResourceServer{ - Name: &flags.name, - Identifier: &flags.identifier, + Name: &flags.Name, + Identifier: &flags.Identifier, } err := ansi.Spinner("Creating API", func() error { @@ -85,8 +98,8 @@ auth0 apis create --name myapi --identifier http://my-api }, } - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the API.") - cmd.Flags().StringVarP(&flags.identifier, "identifier", "i", "", "Identifier of the API.") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "Name of the API.") + cmd.Flags().StringVarP(&flags.Identifier, "identifier", "i", "", "Identifier of the API.") mustRequireFlags(cmd, "name", "identifier") @@ -95,8 +108,8 @@ auth0 apis create --name myapi --identifier http://my-api func updateApiCmd(cli *cli) *cobra.Command { var flags struct { - id string - name string + ID string + Name string } cmd := &cobra.Command{ @@ -106,11 +119,23 @@ func updateApiCmd(cli *cli) *cobra.Command { auth0 apis update --id id --name myapi `, + PreRun: func(cmd *cobra.Command, args []string) { + checkFlags(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { - api := &management.ResourceServer{Name: &flags.name} + if !hasFlags(cmd) { + id := prompt.TextInput("id", "Id:", "Id of the API.", true) + name := prompt.TextInput("name", "Name:", "Name of the API.", true) + + if err := prompt.Ask([]*survey.Question {id, name}, &flags); err != nil { + return err + } + } + + api := &management.ResourceServer{Name: &flags.Name} err := ansi.Spinner("Updating API", func() error { - return cli.api.ResourceServer.Update(flags.id, api) + return cli.api.ResourceServer.Update(flags.ID, api) }) if err != nil { @@ -122,8 +147,8 @@ auth0 apis update --id id --name myapi }, } - cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the API.") - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the API.") + cmd.Flags().StringVarP(&flags.ID, "id", "i", "", "ID of the API.") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "Name of the API.") mustRequireFlags(cmd, "id", "name") @@ -132,7 +157,8 @@ auth0 apis update --id id --name myapi func deleteApiCmd(cli *cli) *cobra.Command { var flags struct { - id string + id string + force bool } cmd := &cobra.Command{ @@ -140,13 +166,15 @@ func deleteApiCmd(cli *cli) *cobra.Command { Short: "Delete an API", Long: `Deletes an API: -auth0 apis delete --id id +auth0 apis delete --id id --force `, RunE: func(cmd *cobra.Command, args []string) error { - if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { - return nil + if !flags.force { + if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { + return nil + } } - + err := ansi.Spinner("Deleting API", func() error { return cli.api.ResourceServer.Delete(flags.id) }) @@ -160,8 +188,7 @@ auth0 apis delete --id id } cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the API.") - - mustRequireFlags(cmd, "id") + cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Do not ask for confirmation.") return cmd } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 96d3710f0..a55c742f7 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -216,6 +216,16 @@ func (c *cli) initContext() (err error) { return nil } +func hasFlags(cmd *cobra.Command) bool { + return cmd.Flags().NFlag() > 0 +} + +func checkFlags(cmd *cobra.Command) { + if (!hasFlags(cmd)) { + cmd.ResetFlags() + } +} + func mustRequireFlags(cmd *cobra.Command, flags ...string) { for _, f := range flags { if err := cmd.MarkFlagRequired(f); err != nil { diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 796538390..1251646c8 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -12,10 +12,10 @@ func Ask(inputs []*survey.Question, response interface{}) error { return survey.Ask(inputs, response, stdErrWriter) } -func TextInput(name string, message string, required bool) *survey.Question { +func TextInput(name string, message string, help string, required bool) *survey.Question { input := &survey.Question{ Name: name, - Prompt: &survey.Input{Message: message}, + Prompt: &survey.Input{Message: message, Help: help}, Transform: survey.Title, } From 307cec6f88f9bd7c7679c65bc83d4776b65e3c2d Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 20:23:51 -0300 Subject: [PATCH 2/6] Add interactive promp to rules --- internal/cli/apis.go | 19 +++++++++--- internal/cli/rules.go | 62 +++++++++++++++++++++++++++------------ internal/prompt/prompt.go | 18 ++++++++++-- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/internal/cli/apis.go b/internal/cli/apis.go index 57f67c38e..eeeda6430 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -72,8 +72,17 @@ auth0 apis create --name myapi --identifier http://my-api }, RunE: func(cmd *cobra.Command, args []string) error { if !hasFlags(cmd) { - name := prompt.TextInput("name", "Name:", "Name of the API. You can change the API name later in the API settings.", true) - identifier := prompt.TextInput("identifier", "Identifier:", "Identifier of the API. Cannot be changed once set.", true) + name := prompt.TextInput( + "name", "Name:", + "Name of the API. You can change the API name later in the API settings.", + "", + true) + + identifier := prompt.TextInput( + "identifier", "Identifier:", + "Identifier of the API. Cannot be changed once set.", + "", + true) if err := prompt.Ask([]*survey.Question {name, identifier}, &flags); err != nil { return err @@ -124,8 +133,8 @@ auth0 apis update --id id --name myapi }, RunE: func(cmd *cobra.Command, args []string) error { if !hasFlags(cmd) { - id := prompt.TextInput("id", "Id:", "Id of the API.", true) - name := prompt.TextInput("name", "Name:", "Name of the API.", true) + id := prompt.TextInput("id", "Id:", "Id of the API.", "", true) + name := prompt.TextInput("name", "Name:", "Name of the API.", "", true) if err := prompt.Ask([]*survey.Question {id, name}, &flags); err != nil { return err @@ -190,5 +199,7 @@ auth0 apis delete --id id --force cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the API.") cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Do not ask for confirmation.") + mustRequireFlags(cmd, "id") + return cmd } diff --git a/internal/cli/rules.go b/internal/cli/rules.go index aee45fbb6..8f0b1a877 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/AlecAivazis/survey/v2" "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth0" "github.com/auth0/auth0-cli/internal/prompt" @@ -147,10 +148,10 @@ func disableRuleCmd(cli *cli) *cobra.Command { func createRulesCmd(cli *cli) *cobra.Command { var flags struct { - name string - script string - order int - enabled bool + Name string + Script string + Order int + Enabled bool } cmd := &cobra.Command{ @@ -160,12 +161,31 @@ func createRulesCmd(cli *cli) *cobra.Command { auth0 rules create --name "My Rule" --script "function (user, context, callback) { console.log( 'Hello, world!' ); return callback(null, user, context); }" `, + PreRun: func(cmd *cobra.Command, args []string) { + checkFlags(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { + if !hasFlags(cmd) { + name := prompt.TextInput( + "name", "Name:", + "Name of the rule. You can change the rule name later in the rule settings.", + "", + true) + + script := prompt.TextInput("script", "Script:", "Script of the rule.", "", true) + order := prompt.TextInput("order", "Order:", "Order of the rule.", "0", false) + enabled := prompt.BoolInput("enabled", "Enabled:", "Enable the rule.", false) + + if err := prompt.Ask([]*survey.Question {name, script, order, enabled}, &flags); err != nil { + return err + } + } + r := &management.Rule{ - Name: &flags.name, - Script: &flags.script, - Order: &flags.order, - Enabled: &flags.enabled, + Name: &flags.Name, + Script: &flags.Script, + Order: &flags.Order, + Enabled: &flags.Enabled, } err := ansi.Spinner("Creating rule", func() error { @@ -176,22 +196,23 @@ func createRulesCmd(cli *cli) *cobra.Command { return err } - cli.renderer.Infof("Your rule `%s` was successfully created.", flags.name) + cli.renderer.Infof("Your rule `%s` was successfully created.", flags.Name) return nil }, } - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of this rule (required)") - cmd.Flags().StringVarP(&flags.script, "script", "s", "", "Code to be executed when this rule runs (required)") - cmd.Flags().IntVarP(&flags.order, "order", "o", 0, "Order that this rule should execute in relative to other rules. Lower-valued rules execute first.") - cmd.Flags().BoolVarP(&flags.enabled, "enabled", "e", false, "Whether the rule is enabled (true), or disabled (false).") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "Name of this rule (required)") + cmd.Flags().StringVarP(&flags.Script, "script", "s", "", "Code to be executed when this rule runs (required)") + cmd.Flags().IntVarP(&flags.Order, "order", "o", 0, "Order that this rule should execute in relative to other rules. Lower-valued rules execute first.") + cmd.Flags().BoolVarP(&flags.Enabled, "enabled", "e", false, "Whether the rule is enabled (true), or disabled (false).") mustRequireFlags(cmd, "name", "script") return cmd } func deleteRulesCmd(cli *cli) *cobra.Command { var flags struct { - id string + id string + force bool } cmd := &cobra.Command{ @@ -199,14 +220,16 @@ func deleteRulesCmd(cli *cli) *cobra.Command { Short: "Delete a rule", Long: `Delete a rule: - auth0 rules delete --id "12345"`, + auth0 rules delete --id "12345" --force`, RunE: func(cmd *cobra.Command, args []string) error { - r := &management.Rule{ID: &flags.id} + if !flags.force { + if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { + return nil + } + } // TODO: Should add validation of rule - if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { - return nil - } + r := &management.Rule{ID: &flags.id} err := ansi.Spinner("Deleting rule", func() error { return cli.api.Rule.Delete(*r.ID) @@ -221,6 +244,7 @@ func deleteRulesCmd(cli *cli) *cobra.Command { } cmd.Flags().StringVar(&flags.id, "id", "", "ID of the rule to delete (required)") + cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Do not ask for confirmation.") mustRequireFlags(cmd, "id") return cmd diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 1251646c8..edcb7aae6 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -12,10 +12,24 @@ func Ask(inputs []*survey.Question, response interface{}) error { return survey.Ask(inputs, response, stdErrWriter) } -func TextInput(name string, message string, help string, required bool) *survey.Question { +func TextInput(name string, message string, help string, value string, required bool) *survey.Question { input := &survey.Question{ Name: name, - Prompt: &survey.Input{Message: message, Help: help}, + Prompt: &survey.Input{Message: message, Help: help, Default: value}, + Transform: survey.Title, + } + + if required { + input.Validate = survey.Required + } + + return input +} + +func BoolInput(name string, message string, help string, required bool) *survey.Question { + input := &survey.Question{ + Name: name, + Prompt: &survey.Confirm{Message: message, Help: help}, Transform: survey.Title, } From 57e4924e4b11239fef3d9d16444609f04145e176 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 20:29:45 -0300 Subject: [PATCH 3/6] Style prompts --- internal/cli/rules.go | 2 +- internal/prompt/prompt.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/cli/rules.go b/internal/cli/rules.go index 8f0b1a877..5f5f9bffa 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -243,7 +243,7 @@ func deleteRulesCmd(cli *cli) *cobra.Command { }, } - cmd.Flags().StringVar(&flags.id, "id", "", "ID of the rule to delete (required)") + cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the rule to delete (required)") cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Do not ask for confirmation.") mustRequireFlags(cmd, "id") diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index edcb7aae6..782d2b0b6 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -8,8 +8,16 @@ import ( var stdErrWriter = survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) +var icons = survey.WithIcons(func(icons *survey.IconSet) { + icons.Question.Text = "" +}) + func Ask(inputs []*survey.Question, response interface{}) error { - return survey.Ask(inputs, response, stdErrWriter) + return survey.Ask(inputs, response, stdErrWriter, icons) +} + +func AskOne(prompt survey.Prompt, response interface{}) error { + return survey.AskOne(prompt, response, stdErrWriter, icons) } func TextInput(name string, message string, help string, value string, required bool) *survey.Question { @@ -46,7 +54,7 @@ func Confirm(message string) bool { Message: message, } - if err := survey.AskOne(prompt, &result, stdErrWriter); err != nil { + if err := AskOne(prompt, &result); err != nil { return false } From 43eea56fc53dcb5adabf3e931a6c81e28587e8c3 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 20:47:56 -0300 Subject: [PATCH 4/6] Re-add delete rule by name --- internal/cli/rules.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/cli/rules.go b/internal/cli/rules.go index 5f5f9bffa..22657314a 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "regexp" "github.com/AlecAivazis/survey/v2" "github.com/auth0/auth0-cli/internal/ansi" @@ -212,6 +213,7 @@ func createRulesCmd(cli *cli) *cobra.Command { func deleteRulesCmd(cli *cli) *cobra.Command { var flags struct { id string + name string force bool } @@ -221,6 +223,12 @@ func deleteRulesCmd(cli *cli) *cobra.Command { Long: `Delete a rule: auth0 rules delete --id "12345" --force`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if flags.id != "" && flags.name != "" { + return fmt.Errorf("TMI! 🤯 use either --name or --id") + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if !flags.force { if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { @@ -229,7 +237,31 @@ func deleteRulesCmd(cli *cli) *cobra.Command { } // TODO: Should add validation of rule - r := &management.Rule{ID: &flags.id} + var r *management.Rule + ruleIDPattern := "^rul_[A-Za-z0-9]{16}$" + re := regexp.MustCompile(ruleIDPattern) + + if flags.id != "" { + if !re.Match([]byte(flags.id)) { + return fmt.Errorf("Rule with id %q does not match pattern %s", flags.id, ruleIDPattern) + } + + rule, err := cli.api.Rule.Read(flags.id) + if err != nil { + return err + } + r = rule + } else { + data, err := getRules(cli) + if err != nil { + return err + } + if rule := findRuleByName(flags.name, data.Rules); rule != nil { + r = rule + } else { + return fmt.Errorf("No rule found with name: %q", flags.name) + } + } err := ansi.Spinner("Deleting rule", func() error { return cli.api.Rule.Delete(*r.ID) @@ -244,8 +276,8 @@ func deleteRulesCmd(cli *cli) *cobra.Command { } cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the rule to delete (required)") + cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the rule to delete") cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Do not ask for confirmation.") - mustRequireFlags(cmd, "id") return cmd } From 94569351893246954535a0a7b332b5aac3235b69 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 20:49:23 -0300 Subject: [PATCH 5/6] Remove extra whitespace --- internal/cli/rules.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/cli/rules.go b/internal/cli/rules.go index 22657314a..b16054dca 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -223,11 +223,11 @@ func deleteRulesCmd(cli *cli) *cobra.Command { Long: `Delete a rule: auth0 rules delete --id "12345" --force`, - PreRunE: func(cmd *cobra.Command, args []string) error { - if flags.id != "" && flags.name != "" { - return fmt.Errorf("TMI! 🤯 use either --name or --id") - } - return nil + PreRunE: func(cmd *cobra.Command, args []string) error { + if flags.id != "" && flags.name != "" { + return fmt.Errorf("TMI! 🤯 use either --name or --id") + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { if !flags.force { From 53e7a1decbd41a77f7e15b1b12effd6ef6174d3c Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 26 Jan 2021 20:59:03 -0300 Subject: [PATCH 6/6] Remove title transform --- internal/prompt/prompt.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 782d2b0b6..aa2b34311 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -24,7 +24,6 @@ func TextInput(name string, message string, help string, value string, required input := &survey.Question{ Name: name, Prompt: &survey.Input{Message: message, Help: help, Default: value}, - Transform: survey.Title, } if required {