From ec6e2b0a1b3e11b53dd64bfaddc012f32a62bf38 Mon Sep 17 00:00:00 2001 From: Cyril David Date: Fri, 4 Sep 2020 21:33:36 -0700 Subject: [PATCH] commands => cli; refactor --- cmd/auth0/main.go | 4 +- internal/{commands => cli}/actions.go | 98 ++++++++++++++++++++++--- internal/cli/cli.go | 11 +++ internal/cli/root.go | 53 +++++++++++++ internal/{commands => cli}/templates.go | 15 ++-- internal/{commands => cli}/triggers.go | 2 +- internal/commands/root.go | 61 --------------- 7 files changed, 163 insertions(+), 81 deletions(-) rename internal/{commands => cli}/actions.go (53%) create mode 100644 internal/cli/cli.go create mode 100644 internal/cli/root.go rename internal/{commands => cli}/templates.go (95%) rename internal/{commands => cli}/triggers.go (98%) delete mode 100644 internal/commands/root.go diff --git a/cmd/auth0/main.go b/cmd/auth0/main.go index 0153bb3d9..053853ef9 100644 --- a/cmd/auth0/main.go +++ b/cmd/auth0/main.go @@ -1,7 +1,7 @@ package main -import "github.com/auth0/auth0-cli/internal/commands" +import "github.com/auth0/auth0-cli/internal/cli" func main() { - commands.Execute() + cli.Execute() } diff --git a/internal/commands/actions.go b/internal/cli/actions.go similarity index 53% rename from internal/commands/actions.go rename to internal/cli/actions.go index 2041c488b..4ae451874 100644 --- a/internal/commands/actions.go +++ b/internal/cli/actions.go @@ -1,9 +1,12 @@ -package commands +package cli import ( "fmt" + "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/config" + "github.com/auth0/auth0-cli/internal/validators" + "github.com/cyx/auth0/management" "github.com/spf13/cobra" ) @@ -31,9 +34,14 @@ func listActionsCmd(cfg *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "List existing actions", - Long: `List all actions within a tenant.`, + Long: `List actions within a specific trigger.`, RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("list called") + list, err := cfg.API.Action.List(management.WithTriggerID(management.TriggerID(flags.trigger))) + if err != nil { + return err + } + + cfg.Renderer.ActionList(list.Actions) return nil }, } @@ -41,6 +49,7 @@ func listActionsCmd(cfg *config.Config) *cobra.Command { cmd.Flags().StringVarP(&flags.trigger, "trigger", "t", "", "Only list actions within this trigger.", ) + mustRequireFlags(cmd, "trigger") return cmd } @@ -52,6 +61,7 @@ func createActionCmd(cfg *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "create ", + Args: validators.ExactArgs(""), Short: "Create an action.", Long: `Creates an action, and generates a few files for working with actions: @@ -59,14 +69,31 @@ func createActionCmd(cfg *config.Config) *cobra.Command { - testdata.json - sample payload for testing the action. `, RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("create called") - return nil + // TODO(cyx): cache / list the set of triggers + // somewhere maybe? From there we can use them to + // determine what the valid triggers are. + action := &management.Action{ + Name: args[0], + SupportedTriggers: []management.Trigger{ + { + ID: management.TriggerID(flags.trigger), + Version: "v1", + }, + }, + } + + return ansi.Spinner("Creating action", func() error { + return cfg.API.Action.Create(action) + }) + + // TODO: add some more help text here. }, } cmd.Flags().StringVarP(&flags.trigger, "trigger", "t", "", "Supported trigger for the action.", ) + mustRequireFlags(cmd, "trigger") return cmd } @@ -93,8 +120,13 @@ The deploy lifecycle is as follows: } func renameActionCmd(cfg *config.Config) *cobra.Command { + var flags struct { + newname string + } + cmd := &cobra.Command{ Use: "rename ", + Args: validators.ExactArgs(""), Short: "Rename an existing action.", Long: `Renames an action. If any generated files are found those files are also renamed.: @@ -104,11 +136,23 @@ The following generated files will be moved: - testdata.json `, RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("rename called") - return nil + name := args[0] + action, err := findActionByName(cfg, name) + if err != nil { + return err + } + + return ansi.Spinner("Renaming action", func() error { + return cfg.API.Action.Update(action.ID, &management.Action{Name: flags.newname}) + }) }, } + cmd.Flags().StringVarP(&flags.newname, + "newname", "n", "", "New name of the action.", + ) + mustRequireFlags(cmd, "newname") + return cmd } @@ -119,6 +163,7 @@ func deleteActionCmd(cfg *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "delete ", + Args: validators.ExactArgs(""), Short: "Delete an existing action.", Long: `Deletes an existing action. Only actions not bound to triggers can be deleted. @@ -130,14 +175,49 @@ To delete an action already bound, you have to: Note that all code artifacts will also be deleted. `, RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("delete called") - return nil + name := args[0] + + if flags.confirm != args[0] { + return fmt.Errorf("Confirmation required. Try running `auth0 actions delete %s --confirm %s`", name, name) + } + + action, err := findActionByName(cfg, name) + if err != nil { + return err + } + + return ansi.Spinner("Deleting action", func() error { + return cfg.API.Action.Delete(action.ID) + }) }, } cmd.Flags().StringVarP(&flags.confirm, "confirm", "c", "", "Confirm the action name to be deleted.", ) + mustRequireFlags(cmd, "confirm") return cmd } + +func findActionByName(cfg *config.Config, name string) (*management.Action, error) { + // TODO(cyx): add a WithName and a filter by name in + // the management API. For now we're gonna use + // post-login since that's all we're creating to test + // it out. + list, err := cfg.API.Action.List(management.WithTriggerID(management.TriggerID("post-login"))) + if err != nil { + return nil, err + } + + // Temporary shim: when we have a list by name, we'll + // just straight check the count and ensure it's 1 + // then. + for _, a := range list.Actions { + if a.Name == name { + return a, nil + } + } + + return nil, fmt.Errorf("Action with name `%s` not found.", name) +} diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 000000000..a64e4ea02 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,11 @@ +package cli + +import "github.com/spf13/cobra" + +func mustRequireFlags(cmd *cobra.Command, flags ...string) { + for _, f := range flags { + if err := cmd.MarkFlagRequired(f); err != nil { + panic(err) + } + } +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 000000000..d96d55e96 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,53 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/auth0/auth0-cli/internal/config" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +// Execute is the primary entrypoint of the CLI app. +func Execute() { + // fs is a mock friendly os.File interface. + fs := afero.NewOsFs() + + // cfg contains tenant related information, e.g. `travel0-dev`, + // `travel0-prod`. some of its information can be sourced via: + // 1. env var (e.g. AUTH0_API_KEY) + // 2. global flag (e.g. --api-key) + // 3. JSON file (e.g. api_key = "..." in ~/.config/auth0/config.json) + cfg := &config.Config{} + + rootCmd := &cobra.Command{ + Use: "auth0", + SilenceUsage: true, + SilenceErrors: true, + Short: "Command-line tool to interact with Auth0.", + Long: "Command-line tool to interact with Auth0.\n" + getLogin(&fs, cfg), + + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // Initialize everything once. Later callers can then + // freely assume that config is fully primed and ready + // to go. + return cfg.Init() + }, + } + + rootCmd.SetUsageTemplate(namespaceUsageTemplate()) + rootCmd.PersistentFlags().StringVar(&cfg.Tenant, + "tenant", "", "Specific tenant to use") + + rootCmd.PersistentFlags().BoolVar(&cfg.Verbose, + "verbose", false, "Enable verbose mode.") + + rootCmd.AddCommand(actionsCmd(cfg)) + rootCmd.AddCommand(triggersCmd(cfg)) + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/internal/commands/templates.go b/internal/cli/templates.go similarity index 95% rename from internal/commands/templates.go rename to internal/cli/templates.go index b02e5be69..dc56b3691 100644 --- a/internal/commands/templates.go +++ b/internal/cli/templates.go @@ -1,9 +1,7 @@ -package commands +package cli import ( "fmt" - "os" - "path/filepath" "strings" "github.com/auth0/auth0-cli/internal/ansi" @@ -77,13 +75,14 @@ func WrappedNonRequestParamsFlagUsages(cmd *cobra.Command) string { // func getLogin(fs *afero.Fs, cfg *config.Config) string { - // We're checking against the path because we don't initialize the config - // at this point of execution. - path := cfg.GetConfigFolder(os.Getenv("XDG_CONFIG_HOME")) - file := filepath.Join(path, "config.toml") + // // We're checking against the path because we don't initialize the config + // // at this point of execution. + // path := cfg.GetConfigFolder(os.Getenv("XDG_CONFIG_HOME")) + // file := filepath.Join(path, "config.toml") - exists, _ := afero.Exists(*fs, file) + // exists, _ := afero.Exists(*fs, file) + exists := false if !exists { return ` Before using the CLI, you'll need to login: diff --git a/internal/commands/triggers.go b/internal/cli/triggers.go similarity index 98% rename from internal/commands/triggers.go rename to internal/cli/triggers.go index 4c0029079..b849a97f3 100644 --- a/internal/commands/triggers.go +++ b/internal/cli/triggers.go @@ -1,4 +1,4 @@ -package commands +package cli import ( "fmt" diff --git a/internal/commands/root.go b/internal/commands/root.go deleted file mode 100644 index ef530c9e3..000000000 --- a/internal/commands/root.go +++ /dev/null @@ -1,61 +0,0 @@ -package commands - -import ( - "fmt" - "os" - - "github.com/auth0/auth0-cli/internal/config" - "github.com/spf13/afero" - "github.com/spf13/cobra" -) - -// Execute is the primary entrypoint of the CLI app. -func Execute() { - // fs is a mock friendly os.File interface. - fs := afero.NewOsFs() - - // cfg contains tenant related information, e.g. `travel0-dev`, - // `travel0-prod`. some of its information can be source via: - // 1. env var (e.g. AUTH0_API_KEY) - // 2. global flag (e.g. --api-key) - // 3. toml file (e.g. api_key = "..." in ~/.config/auth0/config.toml) - cfg := &config.Config{} - - rootCmd := &cobra.Command{ - Use: "auth0", - SilenceUsage: true, - SilenceErrors: true, - Short: "Command-line tool to interact with Auth0.", - Long: "Command-line tool to interact with Auth0.\n" + getLogin(&fs, cfg), - - PersistentPreRun: func(cmd *cobra.Command, args []string) { - // We want cfg.Init to run for any command since it provides the - // underlying tenant information we'll need to fulfill the user's - // request. - cfg.Init() - }, - } - - rootCmd.SetUsageTemplate(namespaceUsageTemplate()) - rootCmd.PersistentFlags().StringVar(&cfg.Profile.APIKey, - "api-key", "", "Your API key to use for the command") - rootCmd.PersistentFlags().StringVar(&cfg.Color, - "color", "", "turn on/off color output (on, off, auto)") - rootCmd.PersistentFlags().StringVar(&cfg.ProfilesFile, - "config", "", "config file (default is $HOME/.config/auth0/config.toml)") - rootCmd.PersistentFlags().StringVar(&cfg.Profile.DeviceName, - "device-name", "", "device name") - rootCmd.PersistentFlags().StringVar(&cfg.LogLevel, - "log-level", "info", "log level (debug, info, warn, error)") - rootCmd.PersistentFlags().StringVarP(&cfg.Profile.ProfileName, - "tenant", "", "default", "the tenant info to read from for config") - rootCmd.Flags().BoolP("version", "v", false, "Get the version of the Auth0 CLI") - - rootCmd.AddCommand(actionsCmd(cfg)) - rootCmd.AddCommand(triggersCmd(cfg)) - - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -}