diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index ac1ed480949..76c3f2232f4 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -112,6 +112,17 @@ dokku cron:list node-js-app --format json [{"id":"cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5","app":"node-js-app","command":"node index.js","schedule":"@daily"}] ``` +To fetch global tasks, use the `--global` flag: + +```shell +dokku cron:list --global +``` + +``` +ID Schedule Command +5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true +``` + #### Executing a cron task on the fly Cron tasks can be invoked via the `cron:run` command. This command takes an `app` argument and a `cron id` (retrievable from `cron:list` output). diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index 8013debe122..12754f846b4 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -2,6 +2,7 @@ package cron import ( "fmt" + "strings" appjson "github.com/dokku/dokku/plugins/app-json" "github.com/dokku/dokku/plugins/common" @@ -32,11 +33,14 @@ type TemplateCommand struct { ID string `json:"id"` // App is the app the cron command belongs to - App string `json:"app"` + App string `json:"app,omitempty"` // Command is the command to run Command string `json:"command"` + // Global is whether the cron command is global + Global bool `json:"global,omitempty"` + // Schedule is the cron schedule Schedule string `json:"schedule"` @@ -125,6 +129,43 @@ func FetchCronEntries(input FetchCronEntriesInput) ([]TemplateCommand, error) { return commands, nil } +// FetchGlobalCronEntries returns a list of global cron commands +// This function should only be used for the cron:list --global command +// and not internally by the cron plugin +func FetchGlobalCronEntries() ([]TemplateCommand, error) { + commands := []TemplateCommand{} + response, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "cron-entries", + Args: []string{"docker-local"}, + }) + for _, line := range strings.Split(response.StdoutContents(), "\n") { + if strings.TrimSpace(line) == "" { + continue + } + + parts := strings.Split(line, ";") + if len(parts) != 2 && len(parts) != 3 { + common.LogWarn(fmt.Sprintf("Invalid injected cron task: %v", line)) + continue + } + + id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;"))) + command := TemplateCommand{ + ID: id, + Schedule: parts[0], + Command: parts[1], + AltCommand: parts[1], + Maintenance: false, + Global: true, + } + if len(parts) == 3 { + command.LogFile = parts[2] + } + commands = append(commands, command) + } + return commands, nil +} + // GenerateCommandID creates a unique ID for a given app/command/schedule combination func GenerateCommandID(appName string, c appjson.CronCommand) string { return base36.EncodeToStringLc([]byte(appName + "===" + c.Command + "===" + c.Schedule)) diff --git a/plugins/cron/src/subcommands/subcommands.go b/plugins/cron/src/subcommands/subcommands.go index f536f763fd1..329b240e813 100644 --- a/plugins/cron/src/subcommands/subcommands.go +++ b/plugins/cron/src/subcommands/subcommands.go @@ -20,9 +20,13 @@ func main() { switch subcommand { case "list": args := flag.NewFlagSet("cron:list", flag.ExitOnError) + global := args.Bool("global", false, "--global: set a global property") format := args.String("format", "stdout", "format: [ stdout | json ]") args.Parse(os.Args[2:]) appName := args.Arg(0) + if *global { + appName = "--global" + } err = cron.CommandList(appName, *format) case "report": args := flag.NewFlagSet("cron:report", flag.ExitOnError) diff --git a/plugins/cron/subcommands.go b/plugins/cron/subcommands.go index 2d5717b4b57..f8d0e941e42 100644 --- a/plugins/cron/subcommands.go +++ b/plugins/cron/subcommands.go @@ -14,10 +14,6 @@ import ( // CommandList lists all scheduled cron tasks for a given app func CommandList(appName string, format string) error { - if err := common.VerifyAppName(appName); err != nil { - return err - } - if format == "" { format = "stdout" } @@ -26,9 +22,22 @@ func CommandList(appName string, format string) error { return fmt.Errorf("Invalid format specified, supported formats: json, stdout") } - entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName}) - if err != nil { - return err + var entries []TemplateCommand + if appName == "--global" { + var err error + entries, err = FetchGlobalCronEntries() + if err != nil { + return err + } + } else { + var err error + if err := common.VerifyAppName(appName); err != nil { + return err + } + entries, err = FetchCronEntries(FetchCronEntriesInput{AppName: appName}) + if err != nil { + return err + } } if format == "stdout" { diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index e22145aa79c..77a22c2ea3e 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -14,7 +14,6 @@ version = "0.0.1" [plugin.config] EOF cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml - } teardown() { @@ -126,6 +125,12 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku cron:list --global --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output '[{"id":"5cruaotm4yzzpnjlsdunblj8qyjp","command":"/bin/true","global":true,"schedule":"@daily","maintenance":false}]' + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status"