diff --git a/internal/cli/actions.go b/internal/cli/actions.go index 4ae451874..f16c21bdc 100644 --- a/internal/cli/actions.go +++ b/internal/cli/actions.go @@ -2,6 +2,9 @@ package cli import ( "fmt" + "io/ioutil" + "os" + "path" "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/config" @@ -17,7 +20,9 @@ func actionsCmd(cfg *config.Config) *cobra.Command { } cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(initActionsCmd(cfg)) cmd.AddCommand(listActionsCmd(cfg)) + cmd.AddCommand(showActionCmd(cfg)) cmd.AddCommand(createActionCmd(cfg)) cmd.AddCommand(renameActionCmd(cfg)) cmd.AddCommand(deleteActionCmd(cfg)) @@ -26,6 +31,33 @@ func actionsCmd(cfg *config.Config) *cobra.Command { return cmd } +func initActionsCmd(cfg *config.Config) *cobra.Command { + var flags struct { + sync bool + overwrite bool + } + + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize actions project structure.", + Long: `Initialize actions project structure. Optionally sync your actions.`, + RunE: func(cmd *cobra.Command, args []string) error { + return initActions(cfg, flags.sync, flags.overwrite) + }, + } + + cmd.Flags().BoolVarP(&flags.sync, + "sync", "", false, "Sync actions code from the management API.", + ) + + cmd.Flags().BoolVarP(&flags.overwrite, + "overwrite", "", false, "Overwrite existing files.", + ) + + return cmd + +} + func listActionsCmd(cfg *config.Config) *cobra.Command { var flags struct { trigger string @@ -54,6 +86,42 @@ func listActionsCmd(cfg *config.Config) *cobra.Command { return cmd } +func showActionCmd(cfg *config.Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "show ", + Args: validators.ExactArgs(""), + Short: "Show action information.", + Long: "Show action information. Shows existing versions deployed.", + RunE: func(cmd *cobra.Command, args []string) error { + name := args[0] + + var ( + action *management.Action + list *management.ActionVersionList + ) + + err := ansi.Spinner("Fetching action", func() error { + var err error + if action, err = findActionByName(cfg, name); err != nil { + return err + } + + list, err = cfg.API.ActionVersion.List(action.ID) + return err + }) + + if err != nil { + return err + } + + cfg.Renderer.ActionInfo(action, list.Versions) + return nil + }, + } + + return cmd +} + func createActionCmd(cfg *config.Config) *cobra.Command { var flags struct { trigger string @@ -99,8 +167,13 @@ func createActionCmd(cfg *config.Config) *cobra.Command { } func deployActionCmd(cfg *config.Config) *cobra.Command { + var flags struct { + file string + } + cmd := &cobra.Command{ Use: "deploy ", + Args: validators.ExactArgs(""), Short: "Deploy an action.", Long: `Deploy an action. This creates a new version. @@ -111,11 +184,48 @@ The deploy lifecycle is as follows: 3. Bind it to the associated trigger (if not already bound). `, RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("deploy called") - return nil + name := args[0] + + if flags.file == "" { + f, relPath := defaultActionCodePath(cfg.Tenant, name) + if !fileExists(f) { + return fmt.Errorf("`%s` does not exist. Try `auth0 actions init --sync`", relPath) + } + + flags.file = f + } + + action, err := findActionByName(cfg, name) + if err != nil { + return err + } + + code, err := ioutil.ReadFile(flags.file) + if err != nil { + return err + } + + dependencies := []management.Dependency{ + {Name: "lodash", Version: "v4.17.20"}, + } // TODO + runtime := "node12" // TODO + + version := &management.ActionVersion{ + Code: string(code), + Dependencies: dependencies, + Runtime: runtime, + } + + return ansi.Spinner("Deploying action: "+name, func() error { + return cfg.API.ActionVersion.Deploy(action.ID, version) + }) }, } + cmd.Flags().StringVarP(&flags.file, + "file", "f", "", "File which contains code to deploy.", + ) + return cmd } @@ -200,6 +310,46 @@ Note that all code artifacts will also be deleted. return cmd } +func initActions(cfg *config.Config, sync, overwrite bool) error { + // TODO(cyx): should allow lising all actions. for now just limiting to + // post-login + list, err := cfg.API.Action.List(management.WithTriggerID(management.TriggerID("post-login"))) + if err != nil { + return err + } + + for _, a := range list.Actions { + f, relPath := defaultActionCodePath(cfg.Tenant, a.Name) + if fileExists(f) && !overwrite { + cfg.Renderer.Warnf("skip: %s", relPath) + continue + } + + if err := os.MkdirAll(path.Dir(f), 0755); err != nil { + return err + } + + if err := ioutil.WriteFile(f, codeTemplateFor(a), 0644); err != nil { + return err + } + cfg.Renderer.Infof("%s initialized", relPath) + + if sync { + panic("NOT IMPLEMENTED") + } + } + + return nil +} + +func codeTemplateFor(action *management.Action) []byte { + // TODO(cyx): need to find the right template based on supported trigger. + return []byte(`module.exports = function(user, context, cb) { + cb(null, user, context) +} +`) +} + 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 @@ -221,3 +371,23 @@ func findActionByName(cfg *config.Config, name string) (*management.Action, erro return nil, fmt.Errorf("Action with name `%s` not found.", name) } + +func defaultActionCodePath(tenant, name string) (fullPath, relativePath string) { + pwd, err := os.Getwd() + if err != nil { + // This is really exceptional behavior if we can't figure out + // the current working directory. + panic(err) + } + + relativePath = path.Join(tenant, "actions", name, "code.js") + return path.Join(pwd, relativePath), relativePath +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} diff --git a/internal/cli/root.go b/internal/cli/root.go index d96d55e96..7dd740440 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -1,7 +1,6 @@ package cli import ( - "fmt" "os" "github.com/auth0/auth0-cli/internal/config" @@ -47,7 +46,7 @@ func Execute() { rootCmd.AddCommand(triggersCmd(cfg)) if err := rootCmd.Execute(); err != nil { - fmt.Println(err) + cfg.Renderer.Errorf(err.Error()) os.Exit(1) } } diff --git a/internal/display/display.go b/internal/display/display.go index 7c979da0d..6e21d30ec 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -7,6 +7,7 @@ import ( "github.com/auth0/auth0-cli/internal/ansi" "github.com/cyx/auth0/management" + "github.com/logrusorgru/aurora" ) type Renderer struct { @@ -15,6 +16,21 @@ type Renderer struct { Writer io.Writer } +func (r *Renderer) Infof(format string, a ...interface{}) { + fmt.Fprint(r.Writer, aurora.Green(" ▸ ")) + fmt.Fprintf(r.Writer, format+"\n", a...) +} + +func (r *Renderer) Warnf(format string, a ...interface{}) { + fmt.Fprint(r.Writer, aurora.Yellow(" ▸ ")) + fmt.Fprintf(r.Writer, format+"\n", a...) +} + +func (r *Renderer) Errorf(format string, a ...interface{}) { + fmt.Fprint(r.Writer, aurora.BrightRed(" ▸ ")) + fmt.Fprintf(r.Writer, format+"\n", a...) +} + func (r *Renderer) ActionList(actions []*management.Action) { r.Heading(ansi.Bold(r.Tenant), "actions") @@ -23,6 +39,37 @@ func (r *Renderer) ActionList(actions []*management.Action) { } } +func (r *Renderer) ActionInfo(action *management.Action, versions []*management.ActionVersion) { + fmt.Fprintln(r.Writer) + fmt.Fprintf(r.Writer, "%-7s : %s\n", "Name", action.Name) + fmt.Fprintf(r.Writer, "%-7s : %s\n", "Trigger", action.SupportedTriggers[0].ID) + + var ( + lines []string + maxLine int + ) + + for _, v := range versions { + version := fmt.Sprintf("v%d", v.Number) + + // TODO(cyx): fix dates + line := fmt.Sprintf("%-3s | %-10s | %-10s", version, v.Status, "a minute ago") + if n := len(line); n > maxLine { + maxLine = n + } + lines = append(lines, line) + } + + fmt.Fprintln(r.Writer) + fmt.Fprintln(r.Writer, strings.Repeat("-", maxLine)) + + for _, l := range lines { + fmt.Fprintln(r.Writer, l) + } + + fmt.Fprintln(r.Writer, strings.Repeat("-", maxLine)) +} + func (r *Renderer) Heading(text ...string) { fmt.Fprintf(r.Writer, "%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) }