From e1e926b4b11ad5e104b0f7f1337c959e8bbe1aee Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Wed, 27 Jan 2021 00:19:55 -0300 Subject: [PATCH] Make interactivity tty-based for APIs --- go.mod | 1 + internal/ansi/ansi.go | 13 ++-- internal/cli/apis.go | 136 ++++++++++++++++++++++++++++-------- internal/cli/cli.go | 18 +++++ internal/cli/clients.go | 2 +- internal/cli/connections.go | 2 +- internal/cli/rules.go | 2 +- internal/display/apis.go | 5 ++ internal/display/display.go | 2 +- internal/prompt/prompt.go | 8 ++- 10 files changed, 143 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 44c92913e..e57964e91 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/jsanda/tablewriter v0.0.2-0.20190614032957-c4e45dc9c708 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mattn/go-colorable v0.1.6 // indirect + github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-runewidth v0.0.10 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/cobra v1.0.0 diff --git a/internal/ansi/ansi.go b/internal/ansi/ansi.go index 3ab65b763..e8b456142 100644 --- a/internal/ansi/ansi.go +++ b/internal/ansi/ansi.go @@ -6,8 +6,8 @@ import ( "os" "github.com/logrusorgru/aurora" + "github.com/mattn/go-isatty" "github.com/tidwall/pretty" - "golang.org/x/crypto/ssh/terminal" ) var darkTerminalStyle = &pretty.Style{ @@ -100,17 +100,12 @@ func StrikeThrough(text string) string { return color.Sprintf(color.StrikeThrough(text)) } -func isTerminal(w io.Writer) bool { - switch v := w.(type) { - case *os.File: - return terminal.IsTerminal(int(v.Fd())) - default: - return false - } +func IsTerminal() bool { + return isatty.IsTerminal(os.Stdout.Fd()) } func shouldUseColors(w io.Writer) bool { - useColors := ForceColors || isTerminal(w) + useColors := ForceColors || IsTerminal() if EnvironmentOverrideColors { force, ok := os.LookupEnv("CLICOLOR_FORCE") diff --git a/internal/cli/apis.go b/internal/cli/apis.go index eeeda6430..a6a478861 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -1,21 +1,28 @@ 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" "gopkg.in/auth0.v5/management" ) +const ( + id = "id" + name = "name" + identifier = "identifier" +) + func apisCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "apis", Short: "manage resources for APIs.", + Aliases: []string{"resource-servers"}, } cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(listApisCmd(cli)) + cmd.AddCommand(showApiCmd(cli)) cmd.AddCommand(createApiCmd(cli)) cmd.AddCommand(updateApiCmd(cli)) cmd.AddCommand(deleteApiCmd(cli)) @@ -35,9 +42,8 @@ Lists your existing APIs. To create one try: RunE: func(cmd *cobra.Command, args []string) error { var list *management.ResourceServerList - err := ansi.Spinner("Getting APIs", func() error { + err := ansi.Spinner("Loading APIs", func() error { var err error - list, err = cli.api.ResourceServer.List() return err }) @@ -54,6 +60,53 @@ Lists your existing APIs. To create one try: return cmd } +func showApiCmd(cli *cli) *cobra.Command { + var flags struct { + ID string + } + + cmd := &cobra.Command{ + Use: "show", + Short: "Show an API", + Long: `Shows an API: + +auth0 apis show --id id +`, + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if shouldPrompt(cmd, id) { + input := prompt.TextInput(id, "Id:", "Id of the API.", "", true) + + if err := prompt.AskOne(input, &flags); err != nil { + return err + } + } + + api := &management.ResourceServer{ID: &flags.ID} + + err := ansi.Spinner("Loading API", func() error { + var err error + api, err = cli.api.ResourceServer.Read(flags.ID) + return err + }) + + if err != nil { + return err + } + + cli.renderer.ApiShow(api) + return nil + }, + } + + cmd.Flags().StringVarP(&flags.ID, id, "i", "", "ID of the API.") + mustRequireFlags(cmd, id) + + return cmd +} + func createApiCmd(cli *cli) *cobra.Command { var flags struct { Name string @@ -67,24 +120,30 @@ func createApiCmd(cli *cli) *cobra.Command { auth0 apis create --name myapi --identifier http://my-api `, - PreRun: func(cmd *cobra.Command, args []string) { - checkFlags(cmd) + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if !hasFlags(cmd) { - name := prompt.TextInput( - "name", "Name:", + if shouldPrompt(cmd, name) { + input := 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:", + if err := prompt.AskOne(input, &flags); err != nil { + return err + } + } + + if shouldPrompt(cmd, identifier) { + input := 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 { + if err := prompt.AskOne(input, &flags); err != nil { return err } } @@ -107,10 +166,9 @@ 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.") - - mustRequireFlags(cmd, "name", "identifier") + 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) return cmd } @@ -128,15 +186,22 @@ func updateApiCmd(cli *cli) *cobra.Command { auth0 apis update --id id --name myapi `, - PreRun: func(cmd *cobra.Command, args []string) { - checkFlags(cmd) + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) }, 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) + if shouldPrompt(cmd, id) { + input := prompt.TextInput(id, "Id:", "Id of the API.", "", true) - if err := prompt.Ask([]*survey.Question {id, name}, &flags); err != nil { + if err := prompt.AskOne(input, &flags); err != nil { + return err + } + } + + if shouldPrompt(cmd, name) { + input := prompt.TextInput(name, "Name:", "Name of the API.", "", true) + + if err := prompt.AskOne(input, &flags); err != nil { return err } } @@ -156,17 +221,16 @@ 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.") - - mustRequireFlags(cmd, "id", "name") + 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) return cmd } func deleteApiCmd(cli *cli) *cobra.Command { var flags struct { - id string + ID string force bool } @@ -177,15 +241,26 @@ func deleteApiCmd(cli *cli) *cobra.Command { auth0 apis delete --id id --force `, + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { - if !flags.force { + if shouldPrompt(cmd, id) { + input := prompt.TextInput(id, "Id:", "Id of the API.", "", true) + + if err := prompt.AskOne(input, &flags); err != nil { + return err + } + } + + if !flags.force && canPrompt() { 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) + return cli.api.ResourceServer.Delete(flags.ID) }) if err != nil { @@ -196,10 +271,9 @@ auth0 apis delete --id id --force }, } - cmd.Flags().StringVarP(&flags.id, "id", "i", "", "ID of the API.") + 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") + mustRequireFlags(cmd, id) return cmd } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 335f60301..72201ef87 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -12,9 +12,11 @@ import ( "sync" "time" + "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth0" "github.com/auth0/auth0-cli/internal/display" "github.com/spf13/cobra" + "github.com/spf13/pflag" "gopkg.in/auth0.v5/management" ) @@ -238,3 +240,19 @@ func mustRequireFlags(cmd *cobra.Command, flags ...string) { } } } + +func canPrompt() bool { + return ansi.IsTerminal() +} + +func shouldPrompt(cmd *cobra.Command, flag string) bool { + return ansi.IsTerminal() && !cmd.Flags().Changed(flag) +} + +func prepareInteractivity(cmd *cobra.Command) { + if canPrompt() { + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + cmd.Flags().SetAnnotation(flag.Name, cobra.BashCompOneRequiredFlag, []string{"false"}) + }) + } +} diff --git a/internal/cli/clients.go b/internal/cli/clients.go index 67b3e0628..34fef721f 100644 --- a/internal/cli/clients.go +++ b/internal/cli/clients.go @@ -34,7 +34,7 @@ Lists your existing clients. To create one try: `, RunE: func(cmd *cobra.Command, args []string) error { var list *management.ClientList - err := ansi.Spinner("Getting clients", func() error { + err := ansi.Spinner("Loading clients", func() error { var err error list, err = cli.api.Client.List() return err diff --git a/internal/cli/connections.go b/internal/cli/connections.go index a0201be50..44c405bc8 100644 --- a/internal/cli/connections.go +++ b/internal/cli/connections.go @@ -29,7 +29,7 @@ Lists your existing connections. To create one try: `, RunE: func(cmd *cobra.Command, args []string) error { var list *management.ConnectionList - err := ansi.Spinner("Getting connections", func() error { + err := ansi.Spinner("Loading connections", func() error { var err error list, err = cli.api.Connection.List() return err diff --git a/internal/cli/rules.go b/internal/cli/rules.go index b16054dca..f8fc98938 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -35,7 +35,7 @@ func listRulesCmd(cli *cli) *cobra.Command { Long: `Lists the rules in your current tenant.`, RunE: func(cmd *cobra.Command, args []string) error { var rules *management.RuleList - err := ansi.Spinner("Getting rules", func() error { + err := ansi.Spinner("Loading rules", func() error { var err error rules, err = getRules(cli) return err diff --git a/internal/display/apis.go b/internal/display/apis.go index 1ec1ea122..f0f3fdf4c 100644 --- a/internal/display/apis.go +++ b/internal/display/apis.go @@ -32,6 +32,11 @@ func (r *Renderer) ApiList(apis []*management.ResourceServer) { r.Results(res) } +func (r *Renderer) ApiShow(api *management.ResourceServer) { + r.Heading(ansi.Bold(r.Tenant), "API\n") + r.Results([]View{makeView(api)}) +} + func (r *Renderer) ApiCreate(api *management.ResourceServer) { r.Heading(ansi.Bold(r.Tenant), "API created\n") r.Results([]View{makeView(api)}) diff --git a/internal/display/display.go b/internal/display/display.go index 80badf674..1023a5265 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -55,7 +55,7 @@ func (r *Renderer) Errorf(format string, a ...interface{}) { } func (r *Renderer) Heading(text ...string) { - fmt.Fprintf(r.MessageWriter, "%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) + fmt.Fprintf(r.MessageWriter, "\n%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) } type View interface { diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index aa2b34311..07c117eeb 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -16,7 +16,11 @@ func Ask(inputs []*survey.Question, response interface{}) error { return survey.Ask(inputs, response, stdErrWriter, icons) } -func AskOne(prompt survey.Prompt, response interface{}) error { +func AskOne(input *survey.Question, response interface{}) error { + return survey.Ask([]*survey.Question {input}, response, stdErrWriter, icons) +} + +func askOne(prompt survey.Prompt, response interface{}) error { return survey.AskOne(prompt, response, stdErrWriter, icons) } @@ -53,7 +57,7 @@ func Confirm(message string) bool { Message: message, } - if err := AskOne(prompt, &result); err != nil { + if err := askOne(prompt, &result); err != nil { return false }