Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[A0CLI-48] feat: improve interactivity in APIs and rules #67

Merged
merged 4 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
150 changes: 111 additions & 39 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 (
apiID = "id"
apiName = "name"
apiIdentifier = "identifier"
)

func apisCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "apis",
Short: "manage resources for APIs.",
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, apiID) {
input := prompt.TextInput(apiID, "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, apiID, "i", "", "ID of the API.")
mustRequireFlags(cmd, apiID)

return cmd
}

func createApiCmd(cli *cli) *cobra.Command {
var flags struct {
Name string
Expand All @@ -68,23 +121,29 @@ func createApiCmd(cli *cli) *cobra.Command {
auth0 apis create --name myapi --identifier http://my-api
`,
PreRun: func(cmd *cobra.Command, args []string) {
checkFlags(cmd)
prepareInteractivity(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.",
"",
if shouldPrompt(cmd, apiName) {
input := prompt.TextInput(
apiName, "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.",
"",
if err := prompt.AskOne(input, &flags); err != nil {
return err
}
}

if shouldPrompt(cmd, apiIdentifier) {
input := prompt.TextInput(
apiIdentifier, "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, apiName, "n", "", "Name of the API.")
cmd.Flags().StringVarP(&flags.Identifier, apiIdentifier, "i", "", "Identifier of the API.")
mustRequireFlags(cmd, apiName, apiIdentifier)

return cmd
}
Expand All @@ -129,14 +187,21 @@ func updateApiCmd(cli *cli) *cobra.Command {
auth0 apis update --id id --name myapi
`,
PreRun: func(cmd *cobra.Command, args []string) {
checkFlags(cmd)
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, apiID) {
input := prompt.TextInput(apiID, "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, apiName) {
input := prompt.TextInput(apiName, "Name:", "Name of the API.", "", true)

if err := prompt.AskOne(input, &flags); err != nil {
return err
}
}
Expand All @@ -156,36 +221,45 @@ 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, apiID, "i", "", "ID of the API.")
cmd.Flags().StringVarP(&flags.Name, apiName, "n", "", "Name of the API.")
mustRequireFlags(cmd, apiID, apiName)

return cmd
}

func deleteApiCmd(cli *cli) *cobra.Command {
var flags struct {
id string
force bool
ID string
}

cmd := &cobra.Command{
Use: "delete",
Short: "Delete an API",
Long: `Deletes an API:

auth0 apis delete --id id --force
auth0 apis delete --id id
`,
PreRun: func(cmd *cobra.Command, args []string) {
prepareInteractivity(cmd)
},
RunE: func(cmd *cobra.Command, args []string) error {
if !flags.force {
if shouldPrompt(cmd, apiID) {
input := prompt.TextInput(apiID, "Id:", "Id of the API.", "", true)

if err := prompt.AskOne(input, &flags); err != nil {
return err
}
}

if !cli.force && canPrompt(cmd) {
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 +270,8 @@ 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")
cmd.Flags().StringVarP(&flags.ID, apiID, "i", "", "ID of the API.")
mustRequireFlags(cmd, apiID)

return cmd
}
35 changes: 25 additions & 10 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 @@ -58,6 +60,7 @@ type cli struct {
tenant string
format string
force bool
noInput bool

// config state management.
initOnce sync.Once
Expand Down Expand Up @@ -221,20 +224,32 @@ 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 {
panic(err)
}
}
}

func canPrompt(cmd *cobra.Command) bool {
noInput, err := cmd.Root().Flags().GetBool("no-input")

if err != nil {
return false
}

return ansi.IsTerminal() && !noInput
}

func shouldPrompt(cmd *cobra.Command, flag string) bool {
return canPrompt(cmd) && !cmd.Flags().Changed(flag)
}

func prepareInteractivity(cmd *cobra.Command) {
if canPrompt(cmd) {
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
3 changes: 3 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func Execute() {

rootCmd.PersistentFlags().BoolVar(&cli.force,
"force", false, "Skip confirmation.")

rootCmd.PersistentFlags().BoolVar(&cli.noInput,
"no-input", false, "Disable interactivity.")

rootCmd.AddCommand(loginCmd(cli))
rootCmd.AddCommand(clientsCmd(cli))
Expand Down
Loading