Skip to content

Commit

Permalink
Make interactivity tty-based for APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Widcket committed Jan 27, 2021
1 parent 9f05f0c commit e1e926b
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 46 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 4 additions & 9 deletions internal/ansi/ansi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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")
Expand Down
136 changes: 105 additions & 31 deletions internal/cli/apis.go
Original file line number Diff line number Diff line change
@@ -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))
Expand All @@ -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
})
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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
}
Expand All @@ -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
}
}
Expand All @@ -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
}

Expand All @@ -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 {
Expand All @@ -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
}
18 changes: 18 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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"})
})
}
}
2 changes: 1 addition & 1 deletion internal/cli/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions internal/display/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
Expand Down
2 changes: 1 addition & 1 deletion internal/display/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit e1e926b

Please sign in to comment.