2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-12-26 07:42:17 +08:00

(cli): Improve help command output

This commit is contained in:
Ken Hibino 2022-05-06 16:18:40 -07:00 committed by GitHub
parent 9116c096ec
commit 4dd2b5738a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 425 additions and 107 deletions

View File

@ -11,6 +11,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/MakeNowJust/heredoc/v2"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -24,21 +25,30 @@ func init() {
} }
var cronCmd = &cobra.Command{ var cronCmd = &cobra.Command{
Use: "cron", Use: "cron <command> [flags]",
Short: "Manage cron", Short: "Manage cron",
Example: heredoc.Doc(`
$ asynq cron ls
$ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1`),
} }
var cronListCmd = &cobra.Command{ var cronListCmd = &cobra.Command{
Use: "ls", Use: "list",
Short: "List cron entries", Aliases: []string{"ls"},
Run: cronList, Short: "List cron entries",
Run: cronList,
} }
var cronHistoryCmd = &cobra.Command{ var cronHistoryCmd = &cobra.Command{
Use: "history [ENTRY_ID...]", Use: "history <entry_id> [<entry_id>...]",
Short: "Show history of each cron tasks", Short: "Show history of each cron tasks",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: cronHistory, 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) { func cronList(cmd *cobra.Command, args []string) {

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -19,15 +20,18 @@ func init() {
} }
var groupCmd = &cobra.Command{ var groupCmd = &cobra.Command{
Use: "group", Use: "group <command> [flags]",
Short: "Manage groups", Short: "Manage groups",
Example: heredoc.Doc(`
$ asynq group list --queue=myqueue`),
} }
var groupListCmd = &cobra.Command{ var groupListCmd = &cobra.Command{
Use: "ls", Use: "list",
Short: "List groups", Aliases: []string{"ls"},
Args: cobra.NoArgs, Short: "List groups",
Run: groupLists, Args: cobra.NoArgs,
Run: groupLists,
} }
func groupLists(cmd *cobra.Command, args []string) { func groupLists(cmd *cobra.Command, args []string) {

View File

@ -9,6 +9,7 @@ import (
"io" "io"
"os" "os"
"github.com/MakeNowJust/heredoc/v2"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/hibiken/asynq/internal/errors" "github.com/hibiken/asynq/internal/errors"
@ -31,51 +32,75 @@ func init() {
} }
var queueCmd = &cobra.Command{ var queueCmd = &cobra.Command{
Use: "queue", Use: "queue <command> [flags]",
Short: "Manage queues", Short: "Manage queues",
Example: heredoc.Doc(`
$ asynq queue ls
$ asynq queue inspect myqueue
$ asynq queue pause myqueue`),
} }
var queueListCmd = &cobra.Command{ var queueListCmd = &cobra.Command{
Use: "ls", Use: "list",
Short: "List queues", Short: "List queues",
Aliases: []string{"ls"},
// TODO: Use RunE instead? // TODO: Use RunE instead?
Run: queueList, Run: queueList,
} }
var queueInspectCmd = &cobra.Command{ var queueInspectCmd = &cobra.Command{
Use: "inspect QUEUE [QUEUE...]", Use: "inspect <queue> [<queue>...]",
Short: "Display detailed information on one or more queues", Short: "Display detailed information on one or more queues",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
// TODO: Use RunE instead? // TODO: Use RunE instead?
Run: queueInspect, Run: queueInspect,
Example: heredoc.Doc(`
$ asynq queue inspect myqueue
$ asynq queue inspect queue1 queue2 queue3`),
} }
var queueHistoryCmd = &cobra.Command{ var queueHistoryCmd = &cobra.Command{
Use: "history QUEUE [QUEUE...]", Use: "history <queue> [<queue>...]",
Short: "Display historical aggregate data from one or more queues", Short: "Display historical aggregate data from one or more queues",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: queueHistory, 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{ var queuePauseCmd = &cobra.Command{
Use: "pause QUEUE [QUEUE...]", Use: "pause <queue> [<queue>...]",
Short: "Pause one or more queues", Short: "Pause one or more queues",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: queuePause, Run: queuePause,
Example: heredoc.Doc(`
$ asynq queue pause myqueue
$ asynq queue pause queue1 queue2 queue3`),
} }
var queueUnpauseCmd = &cobra.Command{ var queueUnpauseCmd = &cobra.Command{
Use: "unpause QUEUE [QUEUE...]", Use: "resume <queue> [<queue>...]",
Short: "Unpause one or more queues", Short: "Resume (unpause) one or more queues",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: queueUnpause, Aliases: []string{"unpause"},
Run: queueUnpause,
Example: heredoc.Doc(`
$ asynq queue resume myqueue
$ asynq queue resume queue1 queue2 queue3`),
} }
var queueRemoveCmd = &cobra.Command{ var queueRemoveCmd = &cobra.Command{
Use: "rm QUEUE [QUEUE...]", Use: "remove <queue> [<queue>...]",
Short: "Remove one or more queues", Short: "Remove one or more queues",
Args: cobra.MinimumNArgs(1), Aliases: []string{"rm", "delete"},
Run: queueRemove, 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) { func queueList(cmd *cobra.Command, args []string) {

View File

@ -14,11 +14,15 @@ import (
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/MakeNowJust/heredoc/v2"
"github.com/fatih/color"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/hibiken/asynq/internal/base" "github.com/hibiken/asynq/internal/base"
"github.com/hibiken/asynq/internal/rdb" "github.com/hibiken/asynq/internal/rdb"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/exp/utf8string"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -39,10 +43,22 @@ var (
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "asynq", Use: "asynq <command> <subcommand> [flags]",
Short: "A monitoring tool for asynq queues", Short: "Asynq CLI",
Long: `Asynq is a montoring CLI to inspect tasks and queues managed by asynq.`, Long: `Command line tool to inspect tasks and queues managed by Asynq`,
Version: base.Version, 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) 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 '<name> <desc>',
// 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 <command> <subcommand> --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() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
rootCmd.SetHelpFunc(rootHelpFunc)
rootCmd.SetUsageFunc(rootUsageFunc)
rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
rootCmd.SetVersionTemplate(versionOutput) rootCmd.SetVersionTemplate(versionOutput)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file to set flag defaut values (default is $HOME/.asynq.yaml)") 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().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().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().StringVarP(&password, "password", "p", "", "Password to use when connecting to redis server")
rootCmd.PersistentFlags().BoolVar(&useRedisCluster, "cluster", false, "connect to redis cluster") rootCmd.PersistentFlags().BoolVar(&useRedisCluster, "cluster", false, "Connect to redis cluster")
rootCmd.PersistentFlags().StringVar(&clusterAddrs, "cluster_addrs", 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", "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", rootCmd.PersistentFlags().StringVar(&tlsServerName, "tls_server",
"", "server name for TLS validation") "", "Server name for TLS validation")
// Bind flags with config. // Bind flags with config.
viper.BindPFlag("uri", rootCmd.PersistentFlags().Lookup("uri")) viper.BindPFlag("uri", rootCmd.PersistentFlags().Lookup("uri"))
viper.BindPFlag("db", rootCmd.PersistentFlags().Lookup("db")) viper.BindPFlag("db", rootCmd.PersistentFlags().Lookup("db"))

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -21,13 +22,16 @@ func init() {
} }
var serverCmd = &cobra.Command{ var serverCmd = &cobra.Command{
Use: "server", Use: "server <command> [flags]",
Short: "Manage servers", Short: "Manage servers",
Example: heredoc.Doc(`
$ asynq server list`),
} }
var serverListCmd = &cobra.Command{ var serverListCmd = &cobra.Command{
Use: "ls", Use: "list",
Short: "List servers", Aliases: []string{"ls"},
Short: "List servers",
Long: `Server list (asynq server ls) shows all running worker servers Long: `Server list (asynq server ls) shows all running worker servers
pulling tasks from the given redis instance. pulling tasks from the given redis instance.

View File

@ -16,6 +16,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/MakeNowJust/heredoc/v2"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/hibiken/asynq/internal/rdb" "github.com/hibiken/asynq/internal/rdb"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -24,19 +25,15 @@ import (
// statsCmd represents the stats command // statsCmd represents the stats command
var statsCmd = &cobra.Command{ var statsCmd = &cobra.Command{
Use: "stats", Use: "stats",
Short: "Shows current state of the tasks and queues", Short: "View current state",
Long: `Stats (aysnq stats) will show the overview of tasks and queues at that instant. Long: heredoc.Doc(`
Stats shows the overview of tasks and queues at that instant.
Specifically, the command shows the following: The command shows the following:
* Number of tasks in each state * Number of tasks in each state
* Number of tasks in each queue * Number of tasks in each queue
* Aggregate data for the current day * Aggregate data for the current day
* Basic information about the running redis instance * 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`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: stats, Run: stats,
} }

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"time" "time"
"github.com/MakeNowJust/heredoc/v2"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -18,8 +19,8 @@ import (
func init() { func init() {
rootCmd.AddCommand(taskCmd) rootCmd.AddCommand(taskCmd)
taskCmd.AddCommand(taskListCmd) taskCmd.AddCommand(taskListCmd)
taskListCmd.Flags().StringP("queue", "q", "", "queue to inspect") taskListCmd.Flags().StringP("queue", "q", "", "queue to inspect (required)")
taskListCmd.Flags().StringP("state", "s", "", "state of the tasks to inspect") 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("page", 1, "page number")
taskListCmd.Flags().Int("size", 30, "page size") taskListCmd.Flags().Int("size", 30, "page size")
taskListCmd.Flags().StringP("group", "g", "", "group to inspect (required for listing aggregating tasks)") taskListCmd.Flags().StringP("group", "g", "", "group to inspect (required for listing aggregating tasks)")
@ -29,141 +30,155 @@ func init() {
taskCmd.AddCommand(taskCancelCmd) taskCmd.AddCommand(taskCancelCmd)
taskCmd.AddCommand(taskInspectCmd) taskCmd.AddCommand(taskInspectCmd)
taskInspectCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") taskInspectCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)")
taskInspectCmd.Flags().StringP("id", "i", "", "id of the task") taskInspectCmd.Flags().StringP("id", "i", "", "id of the task (required)")
taskInspectCmd.MarkFlagRequired("queue") taskInspectCmd.MarkFlagRequired("queue")
taskInspectCmd.MarkFlagRequired("id") taskInspectCmd.MarkFlagRequired("id")
taskCmd.AddCommand(taskArchiveCmd) taskCmd.AddCommand(taskArchiveCmd)
taskArchiveCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") taskArchiveCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)")
taskArchiveCmd.Flags().StringP("id", "i", "", "id of the task") taskArchiveCmd.Flags().StringP("id", "i", "", "id of the task (required)")
taskArchiveCmd.MarkFlagRequired("queue") taskArchiveCmd.MarkFlagRequired("queue")
taskArchiveCmd.MarkFlagRequired("id") taskArchiveCmd.MarkFlagRequired("id")
taskCmd.AddCommand(taskDeleteCmd) taskCmd.AddCommand(taskDeleteCmd)
taskDeleteCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") taskDeleteCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)")
taskDeleteCmd.Flags().StringP("id", "i", "", "id of the task") taskDeleteCmd.Flags().StringP("id", "i", "", "id of the task (required)")
taskDeleteCmd.MarkFlagRequired("queue") taskDeleteCmd.MarkFlagRequired("queue")
taskDeleteCmd.MarkFlagRequired("id") taskDeleteCmd.MarkFlagRequired("id")
taskCmd.AddCommand(taskRunCmd) taskCmd.AddCommand(taskRunCmd)
taskRunCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs") taskRunCmd.Flags().StringP("queue", "q", "", "queue to which the task belongs (required)")
taskRunCmd.Flags().StringP("id", "i", "", "id of the task") taskRunCmd.Flags().StringP("id", "i", "", "id of the task (required)")
taskRunCmd.MarkFlagRequired("queue") taskRunCmd.MarkFlagRequired("queue")
taskRunCmd.MarkFlagRequired("id") taskRunCmd.MarkFlagRequired("id")
taskCmd.AddCommand(taskArchiveAllCmd) taskCmd.AddCommand(taskArchiveAllCmd)
taskArchiveAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") taskArchiveAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)")
taskArchiveAllCmd.Flags().StringP("state", "s", "", "state of the tasks") taskArchiveAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { pending | aggregating | scheduled | retry } (required)")
taskArchiveAllCmd.MarkFlagRequired("queue") taskArchiveAllCmd.MarkFlagRequired("queue")
taskArchiveAllCmd.MarkFlagRequired("state") taskArchiveAllCmd.MarkFlagRequired("state")
taskArchiveAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for archiving aggregating tasks)")
taskCmd.AddCommand(taskDeleteAllCmd) taskCmd.AddCommand(taskDeleteAllCmd)
taskDeleteAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") taskDeleteAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)")
taskDeleteAllCmd.Flags().StringP("state", "s", "", "state of the tasks") taskDeleteAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { pending | aggregating | scheduled | retry | archived | completed } (required)")
taskDeleteAllCmd.MarkFlagRequired("queue") taskDeleteAllCmd.MarkFlagRequired("queue")
taskDeleteAllCmd.MarkFlagRequired("state") taskDeleteAllCmd.MarkFlagRequired("state")
taskDeleteAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for deleting aggregating tasks)")
taskCmd.AddCommand(taskRunAllCmd) taskCmd.AddCommand(taskRunAllCmd)
taskRunAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong") taskRunAllCmd.Flags().StringP("queue", "q", "", "queue to which the tasks belong (required)")
taskRunAllCmd.Flags().StringP("state", "s", "", "state of the tasks") taskRunAllCmd.Flags().StringP("state", "s", "", "state of the tasks; one of { scheduled | retry | archived } (required)")
taskRunAllCmd.MarkFlagRequired("queue") taskRunAllCmd.MarkFlagRequired("queue")
taskRunAllCmd.MarkFlagRequired("state") taskRunAllCmd.MarkFlagRequired("state")
taskRunAllCmd.Flags().StringP("group", "g", "", "group to which the tasks belong (required for running aggregating tasks)")
} }
var taskCmd = &cobra.Command{ var taskCmd = &cobra.Command{
Use: "task", Use: "task <command> [flags]",
Short: "Manage tasks", 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{ var taskListCmd = &cobra.Command{
Use: "ls --queue=QUEUE --state=STATE", Use: "list --queue=<queue> --state=<state> [flags]",
Short: "List tasks", Aliases: []string{"ls"},
Long: `List tasks of the given state from the specified queue. 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: The --queue and --state flags are required.
- active
- pending
- aggregating
- scheduled
- retry
- archived
- completed
List opeartion paginates the result set. Note: For aggregating tasks, additional --group flag is required.
By default, the command fetches the first 30 tasks.
Use --page and --size flags to specify the page number and size.
List opeartion paginates the result set. By default, the command fetches the first 30 tasks.
Example: Use --page and --size flags to specify the page number and size.`),
To list pending tasks from "default" queue, run Example: heredoc.Doc(`
asynq task ls --queue=default --state=pending $ asynq task list --queue=myqueue --state=pending
$ asynq task list --queue=myqueue --state=aggregating --group=mygroup
To list the tasks from the second page, run $ asynq task list --queue=myqueue --state=scheduled --page=2`),
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
`,
Run: taskList, Run: taskList,
} }
var taskInspectCmd = &cobra.Command{ var taskInspectCmd = &cobra.Command{
Use: "inspect --queue=QUEUE --id=TASK_ID", Use: "inspect --queue=<queue> --id=<task_id>",
Short: "Display detailed information on the specified task", Short: "Display detailed information on the specified task",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskInspect, Run: taskInspect,
Example: heredoc.Doc(`
$ asynq task inspect --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
} }
var taskCancelCmd = &cobra.Command{ var taskCancelCmd = &cobra.Command{
Use: "cancel TASK_ID [TASK_ID...]", Use: "cancel <task_id> [<task_id>...]",
Short: "Cancel one or more active tasks", Short: "Cancel one or more active tasks",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: taskCancel, Run: taskCancel,
Example: heredoc.Doc(`
$ asynq task cancel f1720682-f5a6-4db1-8953-4f48ae541d0f`),
} }
var taskArchiveCmd = &cobra.Command{ var taskArchiveCmd = &cobra.Command{
Use: "archive --queue=QUEUE --id=TASK_ID", Use: "archive --queue=<queue> --id=<task_id>",
Short: "Archive a task with the given id", Short: "Archive a task with the given id",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskArchive, Run: taskArchive,
Example: heredoc.Doc(`
$ asynq task archive --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
} }
var taskDeleteCmd = &cobra.Command{ var taskDeleteCmd = &cobra.Command{
Use: "delete --queue=QUEUE --id=TASK_ID", Use: "delete --queue=<queue> --id=<task_id>",
Short: "Delete a task with the given id", Aliases: []string{"remove", "rm"},
Args: cobra.NoArgs, Short: "Delete a task with the given id",
Run: taskDelete, Args: cobra.NoArgs,
Run: taskDelete,
Example: heredoc.Doc(`
$ asynq task delete --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
} }
var taskRunCmd = &cobra.Command{ var taskRunCmd = &cobra.Command{
Use: "run --queue=QUEUE --id=TASK_ID", Use: "run --queue=<queue> --id=<task_id>",
Short: "Run a task with the given id", Short: "Run a task with the given id",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskRun, Run: taskRun,
Example: heredoc.Doc(`
$ asynq task run --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
} }
var taskArchiveAllCmd = &cobra.Command{ var taskArchiveAllCmd = &cobra.Command{
Use: "archiveall --queue=QUEUE --state=STATE", Use: "archiveall --queue=<queue> --state=<state>",
Short: "Archive all tasks in the given state", Short: "Archive all tasks in the given state",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskArchiveAll, 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{ var taskDeleteAllCmd = &cobra.Command{
Use: "deleteall --queue=QUEUE --state=STATE", Use: "deleteall --queue=<queue> --state=<state>",
Short: "Delete all tasks in the given state", Short: "Delete all tasks in the given state",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskDeleteAll, 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{ var taskRunAllCmd = &cobra.Command{
Use: "runall --queue=QUEUE --state=STATE", Use: "runall --queue=<queue> --state=<state>",
Short: "Run all tasks in the given state", Short: "Run all tasks in the given state",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: taskRunAll, 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) { func taskList(cmd *cobra.Command, args []string) {
@ -527,6 +542,17 @@ func taskArchiveAll(cmd *cobra.Command, args []string) {
n, err = i.ArchiveAllScheduledTasks(qname) n, err = i.ArchiveAllScheduledTasks(qname)
case "retry": case "retry":
n, err = i.ArchiveAllRetryTasks(qname) 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: default:
fmt.Printf("error: unsupported state %q\n", state) fmt.Printf("error: unsupported state %q\n", state)
os.Exit(1) os.Exit(1)
@ -563,6 +589,17 @@ func taskDeleteAll(cmd *cobra.Command, args []string) {
n, err = i.DeleteAllArchivedTasks(qname) n, err = i.DeleteAllArchivedTasks(qname)
case "completed": case "completed":
n, err = i.DeleteAllCompletedTasks(qname) 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: default:
fmt.Printf("error: unsupported state %q\n", state) fmt.Printf("error: unsupported state %q\n", state)
os.Exit(1) os.Exit(1)
@ -595,6 +632,17 @@ func taskRunAll(cmd *cobra.Command, args []string) {
n, err = i.RunAllRetryTasks(qname) n, err = i.RunAllRetryTasks(qname)
case "archived": case "archived":
n, err = i.RunAllArchivedTasks(qname) 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: default:
fmt.Printf("error: unsupported state %q\n", state) fmt.Printf("error: unsupported state %q\n", state)
os.Exit(1) os.Exit(1)

View File

@ -3,13 +3,15 @@ module github.com/hibiken/asynq/tools
go 1.13 go 1.13
require ( require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/fatih/color v1.9.0 github.com/fatih/color v1.9.0
github.com/go-redis/redis/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.4
github.com/hibiken/asynq v0.23.0 github.com/hibiken/asynq v0.23.0
github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/prometheus/client_golang v1.11.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/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0 github.com/spf13/viper v1.7.0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136
) )

View File

@ -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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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/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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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.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 h1:kmKkNFgqiXBatC8oz94Mer6uvKoGn4STlIVDV5wnKyE=
github.com/hibiken/asynq v0.23.0/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw= 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= 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-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-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-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/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-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=