diff --git a/tools/asynq/cmd/cron.go b/tools/asynq/cmd/cron.go index 87bd749..52bdc74 100644 --- a/tools/asynq/cmd/cron.go +++ b/tools/asynq/cmd/cron.go @@ -11,6 +11,7 @@ import ( "sort" "time" + "github.com/MakeNowJust/heredoc/v2" "github.com/hibiken/asynq" "github.com/spf13/cobra" ) @@ -24,21 +25,30 @@ func init() { } var cronCmd = &cobra.Command{ - Use: "cron", + Use: "cron [flags]", Short: "Manage cron", + Example: heredoc.Doc(` + $ asynq cron ls + $ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1`), } var cronListCmd = &cobra.Command{ - Use: "ls", - Short: "List cron entries", - Run: cronList, + Use: "list", + Aliases: []string{"ls"}, + Short: "List cron entries", + Run: cronList, } var cronHistoryCmd = &cobra.Command{ - Use: "history [ENTRY_ID...]", + Use: "history [...]", Short: "Show history of each cron tasks", Args: cobra.MinimumNArgs(1), Run: cronHistory, + Example: heredoc.Doc(` + $ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 + $ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 bf6a8594-cd03-4968-b36a-8572c5e160dd + $ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 --size=100 + $ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 --page=2`), } func cronList(cmd *cobra.Command, args []string) { diff --git a/tools/asynq/cmd/group.go b/tools/asynq/cmd/group.go index 761433e..2a6f400 100644 --- a/tools/asynq/cmd/group.go +++ b/tools/asynq/cmd/group.go @@ -8,6 +8,7 @@ import ( "fmt" "os" + "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" ) @@ -19,15 +20,18 @@ func init() { } var groupCmd = &cobra.Command{ - Use: "group", + Use: "group [flags]", Short: "Manage groups", + Example: heredoc.Doc(` + $ asynq group list --queue=myqueue`), } var groupListCmd = &cobra.Command{ - Use: "ls", - Short: "List groups", - Args: cobra.NoArgs, - Run: groupLists, + Use: "list", + Aliases: []string{"ls"}, + Short: "List groups", + Args: cobra.NoArgs, + Run: groupLists, } func groupLists(cmd *cobra.Command, args []string) { diff --git a/tools/asynq/cmd/queue.go b/tools/asynq/cmd/queue.go index 6c8ba8a..875dca5 100644 --- a/tools/asynq/cmd/queue.go +++ b/tools/asynq/cmd/queue.go @@ -9,6 +9,7 @@ import ( "io" "os" + "github.com/MakeNowJust/heredoc/v2" "github.com/fatih/color" "github.com/hibiken/asynq" "github.com/hibiken/asynq/internal/errors" @@ -31,51 +32,75 @@ func init() { } var queueCmd = &cobra.Command{ - Use: "queue", + Use: "queue [flags]", Short: "Manage queues", + Example: heredoc.Doc(` + $ asynq queue ls + $ asynq queue inspect myqueue + $ asynq queue pause myqueue`), } var queueListCmd = &cobra.Command{ - Use: "ls", - Short: "List queues", + Use: "list", + Short: "List queues", + Aliases: []string{"ls"}, // TODO: Use RunE instead? Run: queueList, } var queueInspectCmd = &cobra.Command{ - Use: "inspect QUEUE [QUEUE...]", + Use: "inspect [...]", Short: "Display detailed information on one or more queues", Args: cobra.MinimumNArgs(1), // TODO: Use RunE instead? Run: queueInspect, + Example: heredoc.Doc(` + $ asynq queue inspect myqueue + $ asynq queue inspect queue1 queue2 queue3`), } var queueHistoryCmd = &cobra.Command{ - Use: "history QUEUE [QUEUE...]", + Use: "history [...]", Short: "Display historical aggregate data from one or more queues", Args: cobra.MinimumNArgs(1), Run: queueHistory, + Example: heredoc.Doc(` + $ asynq queue history myqueue + $ asynq queue history queue1 queue2 queue3 + $ asynq queue history myqueue --days=90`), } var queuePauseCmd = &cobra.Command{ - Use: "pause QUEUE [QUEUE...]", + Use: "pause [...]", Short: "Pause one or more queues", Args: cobra.MinimumNArgs(1), Run: queuePause, + Example: heredoc.Doc(` + $ asynq queue pause myqueue + $ asynq queue pause queue1 queue2 queue3`), } var queueUnpauseCmd = &cobra.Command{ - Use: "unpause QUEUE [QUEUE...]", - Short: "Unpause one or more queues", - Args: cobra.MinimumNArgs(1), - Run: queueUnpause, + Use: "resume [...]", + Short: "Resume (unpause) one or more queues", + Args: cobra.MinimumNArgs(1), + Aliases: []string{"unpause"}, + Run: queueUnpause, + Example: heredoc.Doc(` + $ asynq queue resume myqueue + $ asynq queue resume queue1 queue2 queue3`), } var queueRemoveCmd = &cobra.Command{ - Use: "rm QUEUE [QUEUE...]", - Short: "Remove one or more queues", - Args: cobra.MinimumNArgs(1), - Run: queueRemove, + Use: "remove [...]", + Short: "Remove one or more queues", + Aliases: []string{"rm", "delete"}, + Args: cobra.MinimumNArgs(1), + Run: queueRemove, + Example: heredoc.Doc(` + $ asynq queue rm myqueue + $ asynq queue rm queue1 queue2 queue3 + $ asynq queue rm myqueue --force`), } func queueList(cmd *cobra.Command, args []string) { diff --git a/tools/asynq/cmd/root.go b/tools/asynq/cmd/root.go index 322a19b..bc1795a 100644 --- a/tools/asynq/cmd/root.go +++ b/tools/asynq/cmd/root.go @@ -14,11 +14,15 @@ import ( "unicode" "unicode/utf8" + "github.com/MakeNowJust/heredoc/v2" + "github.com/fatih/color" "github.com/go-redis/redis/v8" "github.com/hibiken/asynq" "github.com/hibiken/asynq/internal/base" "github.com/hibiken/asynq/internal/rdb" "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/exp/utf8string" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" @@ -39,10 +43,22 @@ var ( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "asynq", - Short: "A monitoring tool for asynq queues", - Long: `Asynq is a montoring CLI to inspect tasks and queues managed by asynq.`, + Use: "asynq [flags]", + Short: "Asynq CLI", + Long: `Command line tool to inspect tasks and queues managed by Asynq`, Version: base.Version, + + SilenceUsage: true, + SilenceErrors: true, + + Example: heredoc.Doc(` + $ asynq stats + $ asynq queue pause myqueue + $ asynq task list --queue=myqueue --state=archived`), + Annotations: map[string]string{ + "help:feedback": heredoc.Doc(` + Open an issue at https://github.com/hibiken/asynq/issues/new/choose`), + }, } var versionOutput = fmt.Sprintf("asynq version %s\n", base.Version) @@ -64,22 +80,233 @@ func Execute() { } } +func isRootCmd(cmd *cobra.Command) bool { + return cmd != nil && !cmd.HasParent() +} + +// displayLine represents a line displayed in the output as ' ', +// where pad is used to pad the name from desc. +type displayLine struct { + name string + desc string + pad int // number of rpad +} + +func (l *displayLine) String() string { + return rpad(l.name, l.pad) + l.desc +} + +type displayLines []*displayLine + +func (dls displayLines) String() string { + var lines []string + for _, dl := range dls { + lines = append(lines, dl.String()) + } + return strings.Join(lines, "\n") +} + +// Capitalize the first word in the given string. +func capitalize(s string) string { + str := utf8string.NewString(s) + if str.RuneCount() == 0 { + return "" + } + var b strings.Builder + b.WriteString(strings.ToUpper(string(str.At(0)))) + b.WriteString(str.Slice(1, str.RuneCount())) + return b.String() +} + +func rootHelpFunc(cmd *cobra.Command, args []string) { + // Display helpful error message when user mistypes a subcommand (e.g. 'asynq queue lst'). + if isRootCmd(cmd.Parent()) && len(args) >= 2 && args[1] != "--help" && args[1] != "-h" { + printSubcommandSuggestions(cmd, args[1]) + return + } + + var lines []*displayLine + var commands []*displayLine + for _, c := range cmd.Commands() { + if c.Hidden || c.Short == "" || c.Name() == "help" { + continue + } + l := &displayLine{name: c.Name() + ":", desc: capitalize(c.Short)} + commands = append(commands, l) + lines = append(lines, l) + } + var localFlags []*displayLine + cmd.LocalFlags().VisitAll(func(f *pflag.Flag) { + l := &displayLine{name: "--" + f.Name, desc: capitalize(f.Usage)} + localFlags = append(localFlags, l) + lines = append(lines, l) + }) + var inheritedFlags []*displayLine + cmd.InheritedFlags().VisitAll(func(f *pflag.Flag) { + l := &displayLine{name: "--" + f.Name, desc: capitalize(f.Usage)} + inheritedFlags = append(inheritedFlags, l) + lines = append(lines, l) + }) + adjustPadding(lines...) + + type helpEntry struct { + Title string + Body string + } + var helpEntries []*helpEntry + desc := cmd.Long + if desc == "" { + desc = cmd.Short + } + if desc != "" { + helpEntries = append(helpEntries, &helpEntry{"", desc}) + } + helpEntries = append(helpEntries, &helpEntry{"USAGE", cmd.UseLine()}) + if len(commands) > 0 { + helpEntries = append(helpEntries, &helpEntry{"COMMANDS", displayLines(commands).String()}) + } + if cmd.LocalFlags().HasFlags() { + helpEntries = append(helpEntries, &helpEntry{"FLAGS", displayLines(localFlags).String()}) + } + if cmd.InheritedFlags().HasFlags() { + helpEntries = append(helpEntries, &helpEntry{"INHERITED FLAGS", displayLines(inheritedFlags).String()}) + } + if cmd.Example != "" { + helpEntries = append(helpEntries, &helpEntry{"EXAMPLES", cmd.Example}) + } + helpEntries = append(helpEntries, &helpEntry{"LEARN MORE", heredoc.Doc(` + Use 'asynq --help' for more information about a command.`)}) + if s, ok := cmd.Annotations["help:feedback"]; ok { + helpEntries = append(helpEntries, &helpEntry{"FEEDBACK", s}) + } + + out := cmd.OutOrStdout() + bold := color.New(color.Bold) + for _, e := range helpEntries { + if e.Title != "" { + // If there is a title, add indentation to each line in the body + bold.Fprintln(out, e.Title) + fmt.Fprintln(out, indent(e.Body, 2 /* spaces */)) + } else { + // If there is no title, print the body as is + fmt.Fprintln(out, e.Body) + } + fmt.Fprintln(out) + } +} + +func rootUsageFunc(cmd *cobra.Command) error { + out := cmd.OutOrStdout() + fmt.Fprintf(out, "Usage: %s", cmd.UseLine()) + if subcmds := cmd.Commands(); len(subcmds) > 0 { + fmt.Fprint(out, "\n\nAvailable commands:\n") + for _, c := range subcmds { + if c.Hidden { + continue + } + fmt.Fprintf(out, " %s\n", c.Name()) + } + } + + var localFlags []*displayLine + cmd.LocalFlags().VisitAll(func(f *pflag.Flag) { + localFlags = append(localFlags, &displayLine{name: "--" + f.Name, desc: capitalize(f.Usage)}) + }) + adjustPadding(localFlags...) + if len(localFlags) > 0 { + fmt.Fprint(out, "\n\nFlags:\n") + for _, l := range localFlags { + fmt.Fprintf(out, " %s\n", l.String()) + } + } + return nil +} + +func printSubcommandSuggestions(cmd *cobra.Command, arg string) { + out := cmd.OutOrStdout() + fmt.Fprintf(out, "unknown command %q for %q\n", arg, cmd.CommandPath()) + if cmd.SuggestionsMinimumDistance <= 0 { + cmd.SuggestionsMinimumDistance = 2 + } + candidates := cmd.SuggestionsFor(arg) + if len(candidates) > 0 { + fmt.Fprint(out, "\nDid you mean this?\n") + for _, c := range candidates { + fmt.Fprintf(out, "\t%s\n", c) + } + } + fmt.Fprintln(out) + rootUsageFunc(cmd) +} + +func adjustPadding(lines ...*displayLine) { + // find the maximum width of the name + max := 0 + for _, l := range lines { + if n := utf8.RuneCountInString(l.name); n > max { + max = n + } + } + for _, l := range lines { + l.pad = max + } +} + +// rpad adds padding to the right of a string. +func rpad(s string, padding int) string { + tmpl := fmt.Sprintf("%%-%ds ", padding) + return fmt.Sprintf(tmpl, s) + +} + +// indent indents the given text by given spaces. +func indent(text string, space int) string { + if len(text) == 0 { + return "" + } + var b strings.Builder + indentation := strings.Repeat(" ", space) + lastRune := '\n' + for _, r := range text { + if lastRune == '\n' { + b.WriteString(indentation) + } + b.WriteRune(r) + lastRune = r + } + return b.String() +} + +// dedent removes any indentation from the given text. +func dedent(text string) string { + lines := strings.Split(text, "\n") + var b strings.Builder + for _, l := range lines { + b.WriteString(strings.TrimLeftFunc(l, unicode.IsSpace)) + b.WriteRune('\n') + } + return b.String() +} + func init() { cobra.OnInitialize(initConfig) + rootCmd.SetHelpFunc(rootHelpFunc) + rootCmd.SetUsageFunc(rootUsageFunc) + rootCmd.AddCommand(versionCmd) rootCmd.SetVersionTemplate(versionOutput) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file to set flag defaut values (default is $HOME/.asynq.yaml)") - rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "127.0.0.1:6379", "redis server URI") - rootCmd.PersistentFlags().IntVarP(&db, "db", "n", 0, "redis database number (default is 0)") - rootCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "password to use when connecting to redis server") - rootCmd.PersistentFlags().BoolVar(&useRedisCluster, "cluster", false, "connect to redis cluster") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file to set flag defaut values (default is $HOME/.asynq.yaml)") + rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "127.0.0.1:6379", "Redis server URI") + rootCmd.PersistentFlags().IntVarP(&db, "db", "n", 0, "Redis database number (default is 0)") + rootCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "Password to use when connecting to redis server") + rootCmd.PersistentFlags().BoolVar(&useRedisCluster, "cluster", false, "Connect to redis cluster") rootCmd.PersistentFlags().StringVar(&clusterAddrs, "cluster_addrs", "127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005", - "list of comma-separated redis server addresses") + "List of comma-separated redis server addresses") rootCmd.PersistentFlags().StringVar(&tlsServerName, "tls_server", - "", "server name for TLS validation") + "", "Server name for TLS validation") // Bind flags with config. viper.BindPFlag("uri", rootCmd.PersistentFlags().Lookup("uri")) viper.BindPFlag("db", rootCmd.PersistentFlags().Lookup("db")) diff --git a/tools/asynq/cmd/server.go b/tools/asynq/cmd/server.go index 90e899f..468fa6b 100644 --- a/tools/asynq/cmd/server.go +++ b/tools/asynq/cmd/server.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" ) @@ -21,13 +22,16 @@ func init() { } var serverCmd = &cobra.Command{ - Use: "server", + Use: "server [flags]", Short: "Manage servers", + Example: heredoc.Doc(` + $ asynq server list`), } var serverListCmd = &cobra.Command{ - Use: "ls", - Short: "List servers", + Use: "list", + Aliases: []string{"ls"}, + Short: "List servers", Long: `Server list (asynq server ls) shows all running worker servers pulling tasks from the given redis instance. diff --git a/tools/asynq/cmd/stats.go b/tools/asynq/cmd/stats.go index 864874f..7cb1ccf 100644 --- a/tools/asynq/cmd/stats.go +++ b/tools/asynq/cmd/stats.go @@ -16,6 +16,7 @@ import ( "time" "unicode/utf8" + "github.com/MakeNowJust/heredoc/v2" "github.com/fatih/color" "github.com/hibiken/asynq/internal/rdb" "github.com/spf13/cobra" @@ -24,19 +25,15 @@ import ( // statsCmd represents the stats command var statsCmd = &cobra.Command{ Use: "stats", - Short: "Shows current state of the tasks and queues", - Long: `Stats (aysnq stats) will show the overview of tasks and queues at that instant. + Short: "View current state", + Long: heredoc.Doc(` + Stats shows the overview of tasks and queues at that instant. -Specifically, the command shows the following: -* Number of tasks in each state -* Number of tasks in each queue -* Aggregate data for the current day -* Basic information about the running redis instance - -To monitor the tasks continuously, it's recommended that you run this -command in conjunction with the watch command. - -Example: watch -n 3 asynq stats -> Shows current state of tasks every three seconds`, + The command shows the following: + * Number of tasks in each state + * Number of tasks in each queue + * Aggregate data for the current day + * Basic information about the running redis instance`), Args: cobra.NoArgs, Run: stats, } diff --git a/tools/asynq/cmd/task.go b/tools/asynq/cmd/task.go index 94dec9b..d89ff95 100644 --- a/tools/asynq/cmd/task.go +++ b/tools/asynq/cmd/task.go @@ -10,6 +10,7 @@ import ( "os" "time" + "github.com/MakeNowJust/heredoc/v2" "github.com/fatih/color" "github.com/hibiken/asynq" "github.com/spf13/cobra" @@ -18,8 +19,8 @@ import ( func init() { rootCmd.AddCommand(taskCmd) taskCmd.AddCommand(taskListCmd) - taskListCmd.Flags().StringP("queue", "q", "", "queue to inspect") - taskListCmd.Flags().StringP("state", "s", "", "state of the tasks to inspect") + taskListCmd.Flags().StringP("queue", "q", "", "queue to inspect (required)") + taskListCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { active | pending | aggregating | scheduled | retry | archived | completed } (required)") taskListCmd.Flags().Int("page", 1, "page number") taskListCmd.Flags().Int("size", 30, "page size") taskListCmd.Flags().StringP("group", "g", "", "group to inspect (required for listing aggregating tasks)") @@ -29,141 +30,155 @@ func init() { taskCmd.AddCommand(taskCancelCmd) taskCmd.AddCommand(taskInspectCmd) - taskInspectCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") - taskInspectCmd.Flags().StringP("id", "i", "", "id of the task") + taskInspectCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)") + taskInspectCmd.Flags().StringP("id", "i", "", "id of the task (required)") taskInspectCmd.MarkFlagRequired("queue") taskInspectCmd.MarkFlagRequired("id") taskCmd.AddCommand(taskArchiveCmd) - taskArchiveCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") - taskArchiveCmd.Flags().StringP("id", "i", "", "id of the task") + taskArchiveCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)") + taskArchiveCmd.Flags().StringP("id", "i", "", "id of the task (required)") taskArchiveCmd.MarkFlagRequired("queue") taskArchiveCmd.MarkFlagRequired("id") taskCmd.AddCommand(taskDeleteCmd) - taskDeleteCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") - taskDeleteCmd.Flags().StringP("id", "i", "", "id of the task") + taskDeleteCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)") + taskDeleteCmd.Flags().StringP("id", "i", "", "id of the task (required)") taskDeleteCmd.MarkFlagRequired("queue") taskDeleteCmd.MarkFlagRequired("id") taskCmd.AddCommand(taskRunCmd) - taskRunCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") - taskRunCmd.Flags().StringP("id", "i", "", "id of the task") + taskRunCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)") + taskRunCmd.Flags().StringP("id", "i", "", "id of the task (required)") taskRunCmd.MarkFlagRequired("queue") taskRunCmd.MarkFlagRequired("id") taskCmd.AddCommand(taskArchiveAllCmd) - taskArchiveAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") - taskArchiveAllCmd.Flags().StringP("state", "s", "", "state of the tasks") + taskArchiveAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)") + taskArchiveAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { pending | aggregating | scheduled | retry } (required)") taskArchiveAllCmd.MarkFlagRequired("queue") taskArchiveAllCmd.MarkFlagRequired("state") + taskArchiveAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for archiving aggregating tasks)") taskCmd.AddCommand(taskDeleteAllCmd) - taskDeleteAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") - taskDeleteAllCmd.Flags().StringP("state", "s", "", "state of the tasks") + taskDeleteAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)") + taskDeleteAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { pending | aggregating | scheduled | retry | archived | completed } (required)") taskDeleteAllCmd.MarkFlagRequired("queue") taskDeleteAllCmd.MarkFlagRequired("state") + taskDeleteAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for deleting aggregating tasks)") taskCmd.AddCommand(taskRunAllCmd) - taskRunAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") - taskRunAllCmd.Flags().StringP("state", "s", "", "state of the tasks") + taskRunAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)") + taskRunAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { scheduled | retry | archived } (required)") taskRunAllCmd.MarkFlagRequired("queue") taskRunAllCmd.MarkFlagRequired("state") + taskRunAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for running aggregating tasks)") } var taskCmd = &cobra.Command{ - Use: "task", + Use: "task [flags]", Short: "Manage tasks", + Example: heredoc.Doc(` + $ asynq task list --queue=myqueue --state=scheduled + $ asynq task inspect --queue=myqueue --id=7837f142-6337-4217-9276-8f27281b67d1 + $ asynq task delete --queue=myqueue --id=7837f142-6337-4217-9276-8f27281b67d1 + $ asynq task deleteall --queue=myqueue --state=archived`), } var taskListCmd = &cobra.Command{ - Use: "ls --queue=QUEUE --state=STATE", - Short: "List tasks", - Long: `List tasks of the given state from the specified queue. + Use: "list --queue= --state= [flags]", + Aliases: []string{"ls"}, + Short: "List tasks", + Long: heredoc.Doc(` + List tasks of the given state from the specified queue. -The value for the state flag should be one of: -- active -- pending -- aggregating -- scheduled -- retry -- archived -- completed + The --queue and --state flags are required. -List opeartion paginates the result set. -By default, the command fetches the first 30 tasks. -Use --page and --size flags to specify the page number and size. + Note: For aggregating tasks, additional --group flag is required. - -Example: -To list pending tasks from "default" queue, run - asynq task ls --queue=default --state=pending - -To list the tasks from the second page, run - asynq task ls --queue=default --state=pending --page=1 - -For aggregating tasks, additional --group flag is required. - -Example: - asynq task ls --queue=default --state=aggregating --group=mygroup -`, + List opeartion paginates the result set. By default, the command fetches the first 30 tasks. + Use --page and --size flags to specify the page number and size.`), + Example: heredoc.Doc(` + $ asynq task list --queue=myqueue --state=pending + $ asynq task list --queue=myqueue --state=aggregating --group=mygroup + $ asynq task list --queue=myqueue --state=scheduled --page=2`), Run: taskList, } var taskInspectCmd = &cobra.Command{ - Use: "inspect --queue=QUEUE --id=TASK_ID", + Use: "inspect --queue= --id=", Short: "Display detailed information on the specified task", Args: cobra.NoArgs, Run: taskInspect, + Example: heredoc.Doc(` + $ asynq task inspect --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`), } var taskCancelCmd = &cobra.Command{ - Use: "cancel TASK_ID [TASK_ID...]", + Use: "cancel [...]", Short: "Cancel one or more active tasks", Args: cobra.MinimumNArgs(1), Run: taskCancel, + Example: heredoc.Doc(` + $ asynq task cancel f1720682-f5a6-4db1-8953-4f48ae541d0f`), } var taskArchiveCmd = &cobra.Command{ - Use: "archive --queue=QUEUE --id=TASK_ID", + Use: "archive --queue= --id=", Short: "Archive a task with the given id", Args: cobra.NoArgs, Run: taskArchive, + Example: heredoc.Doc(` + $ asynq task archive --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`), } var taskDeleteCmd = &cobra.Command{ - Use: "delete --queue=QUEUE --id=TASK_ID", - Short: "Delete a task with the given id", - Args: cobra.NoArgs, - Run: taskDelete, + Use: "delete --queue= --id=", + Aliases: []string{"remove", "rm"}, + Short: "Delete a task with the given id", + Args: cobra.NoArgs, + Run: taskDelete, + Example: heredoc.Doc(` + $ asynq task delete --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`), } var taskRunCmd = &cobra.Command{ - Use: "run --queue=QUEUE --id=TASK_ID", + Use: "run --queue= --id=", Short: "Run a task with the given id", Args: cobra.NoArgs, Run: taskRun, + Example: heredoc.Doc(` + $ asynq task run --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`), } var taskArchiveAllCmd = &cobra.Command{ - Use: "archiveall --queue=QUEUE --state=STATE", + Use: "archiveall --queue= --state=", Short: "Archive all tasks in the given state", Args: cobra.NoArgs, Run: taskArchiveAll, + Example: heredoc.Doc(` + $ asynq task archiveall --queue=myqueue --state=retry + $ asynq task archiveall --queue=myqueue --state=aggregating --group=mygroup`), } var taskDeleteAllCmd = &cobra.Command{ - Use: "deleteall --queue=QUEUE --state=STATE", + Use: "deleteall --queue= --state=", Short: "Delete all tasks in the given state", Args: cobra.NoArgs, Run: taskDeleteAll, + Example: heredoc.Doc(` + $ asynq task deleteall --queue=myqueue --state=archived + $ asynq task deleteall --queue=myqueue --state=aggregating --group=mygroup`), } var taskRunAllCmd = &cobra.Command{ - Use: "runall --queue=QUEUE --state=STATE", + Use: "runall --queue= --state=", Short: "Run all tasks in the given state", Args: cobra.NoArgs, Run: taskRunAll, + Example: heredoc.Doc(` + $ asynq task runall --queue=myqueue --state=retry + $ asynq task runall --queue=myqueue --state=aggregating --group=mygroup`), } func taskList(cmd *cobra.Command, args []string) { @@ -527,6 +542,17 @@ func taskArchiveAll(cmd *cobra.Command, args []string) { n, err = i.ArchiveAllScheduledTasks(qname) case "retry": n, err = i.ArchiveAllRetryTasks(qname) + case "aggregating": + group, err := cmd.Flags().GetString("group") + if err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } + if group == "" { + fmt.Println("error: Flag --group is required for aggregating tasks") + os.Exit(1) + } + n, err = i.ArchiveAllAggregatingTasks(qname, group) default: fmt.Printf("error: unsupported state %q\n", state) os.Exit(1) @@ -563,6 +589,17 @@ func taskDeleteAll(cmd *cobra.Command, args []string) { n, err = i.DeleteAllArchivedTasks(qname) case "completed": n, err = i.DeleteAllCompletedTasks(qname) + case "aggregating": + group, err := cmd.Flags().GetString("group") + if err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } + if group == "" { + fmt.Println("error: Flag --group is required for aggregating tasks") + os.Exit(1) + } + n, err = i.DeleteAllAggregatingTasks(qname, group) default: fmt.Printf("error: unsupported state %q\n", state) os.Exit(1) @@ -595,6 +632,17 @@ func taskRunAll(cmd *cobra.Command, args []string) { n, err = i.RunAllRetryTasks(qname) case "archived": n, err = i.RunAllArchivedTasks(qname) + case "aggregating": + group, err := cmd.Flags().GetString("group") + if err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } + if group == "" { + fmt.Println("error: Flag --group is required for aggregating tasks") + os.Exit(1) + } + n, err = i.RunAllAggregatingTasks(qname, group) default: fmt.Printf("error: unsupported state %q\n", state) os.Exit(1) diff --git a/tools/go.mod b/tools/go.mod index fa62bb4..0e4c6ef 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -3,13 +3,15 @@ module github.com/hibiken/asynq/tools go 1.13 require ( + github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/fatih/color v1.9.0 github.com/go-redis/redis/v8 v8.11.4 github.com/hibiken/asynq v0.23.0 github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d github.com/mitchellh/go-homedir v1.1.0 github.com/prometheus/client_golang v1.11.0 - github.com/spf13/afero v1.1.2 // indirect github.com/spf13/cobra v1.1.1 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 + golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 ) diff --git a/tools/go.sum b/tools/go.sum index 814ac02..c2e3f56 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -14,6 +14,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= +github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -140,8 +142,6 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hibiken/asynq v0.19.0/go.mod h1:tyc63ojaW8SJ5SBm8mvI4DDONsguP5HE85EEl4Qr5Ig= -github.com/hibiken/asynq v0.21.0 h1:uH9XogJhjq/S39E0/DEPWLZQ6hHJ73UiblZTe4RzHwA= -github.com/hibiken/asynq v0.21.0/go.mod h1:tyc63ojaW8SJ5SBm8mvI4DDONsguP5HE85EEl4Qr5Ig= github.com/hibiken/asynq v0.23.0 h1:kmKkNFgqiXBatC8oz94Mer6uvKoGn4STlIVDV5wnKyE= github.com/hibiken/asynq v0.23.0/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw= github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d h1:Er+U+9PmnyRHRDQjSjRQ24HoWvOY7w9Pk7bUPYM3Ags= @@ -307,6 +307,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=