diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index bbffd181f..0f6db0532 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -309,6 +309,40 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x curl "http://httpstat.us/200" ``` +### `cron-write` + +- Description: Force triggers writing out cron entries +- Invoked by: +- Arguments: +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +# TODO +``` + +### `cron-entries` + +- Description: Allows injecting cron entries into the written out scheduled cron task list. Each entry is newline delimited, and individual entries come in the form `$SCHEDULE;$FULL_COMMAND;$ARBITRARY_DATA`. Individual implementations of cron writing can decide whether and how to include these cron entries. The `ARBITRARY_DATA` includes the log file path for the basic `docker-local` cron implementation. +- Invoked by: +- Arguments: `$DOKKU_SCHEDULER` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +DOKKU_SCHEDULER="$1" + +# TODO +``` + ### `dependencies` - Description: Used to install system-level dependencies. diff --git a/plugins/20_events/cron-entries b/plugins/20_events/cron-entries new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/cron-entries @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/cron-write b/plugins/20_events/cron-write new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/cron-write @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/cron/.gitignore b/plugins/cron/.gitignore index 06649f17c..6be9e9546 100644 --- a/plugins/cron/.gitignore +++ b/plugins/cron/.gitignore @@ -2,5 +2,6 @@ /subcommands/* /triggers/* /triggers +/cron-* /post-* /report diff --git a/plugins/cron/Makefile b/plugins/cron/Makefile index a0f2f16d7..5f3310f57 100644 --- a/plugins/cron/Makefile +++ b/plugins/cron/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/list subcommands/report -TRIGGERS = triggers/post-delete triggers/post-deploy triggers/report +TRIGGERS = triggers/cron-write triggers/post-delete triggers/post-deploy triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = cron diff --git a/plugins/cron/functions.go b/plugins/cron/functions.go index 8318856ce..57379e7ee 100644 --- a/plugins/cron/functions.go +++ b/plugins/cron/functions.go @@ -4,11 +4,11 @@ import ( "encoding/base64" "encoding/json" "fmt" - "html/template" "io/ioutil" "os" "path/filepath" "strings" + "text/template" appjson "github.com/dokku/dokku/plugins/app-json" "github.com/dokku/dokku/plugins/common" @@ -17,10 +17,23 @@ import ( ) type templateCommand struct { - ID string - App string - Command string - Schedule string + ID string + App string + Command string + Schedule string + AltCommand string + LogFile string +} + +func (t templateCommand) CronCommand() string { + if t.AltCommand != "" { + if t.LogFile != "" { + return fmt.Sprintf("%s &>> %s", t.AltCommand, t.LogFile) + } + return t.AltCommand + } + + return fmt.Sprintf("dokku --rm run --cron-id %s %s", t.ID, t.App, t.Command) } func fetchCronEntries(appName string) ([]templateCommand, error) { @@ -86,11 +99,7 @@ func deleteCrontab() error { } func writeCronEntries() error { - apps, err := common.DokkuApps() - if err != nil { - return deleteCrontab() - } - + apps, _ := common.DokkuApps() commands := []templateCommand{} for _, appName := range apps { scheduler := common.GetAppScheduler(appName) @@ -106,6 +115,30 @@ func writeCronEntries() error { commands = append(commands, c...) } + b, _ := common.PlugnTriggerOutput("cron-entries", "docker-local") + common.Log(fmt.Sprintf("output: %s", string(b))) + for _, line := range strings.Split(strings.TrimSpace(string(b[:])), "\n") { + if strings.TrimSpace(line) == "" { + continue + } + + parts := strings.Split(line, ";") + if len(parts) != 2 && len(parts) != 3 { + return fmt.Errorf("Invalid injected cron task: %v", line) + } + + id := base64.StdEncoding.EncodeToString([]byte(strings.Join(parts, ";;;"))) + command := templateCommand{ + ID: id, + Schedule: parts[0], + AltCommand: parts[1], + } + if len(parts) == 3 { + command.LogFile = parts[2] + } + commands = append(commands, command) + } + if len(commands) == 0 { return deleteCrontab() } @@ -119,7 +152,7 @@ func writeCronEntries() error { return err } - tmpFile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "CopyFromImage")) + tmpFile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "WriteCronEntries")) if err != nil { return fmt.Errorf("Cannot create temporary schedule file: %v", err) } diff --git a/plugins/cron/src/triggers/triggers.go b/plugins/cron/src/triggers/triggers.go index 1e36f272a..f9a4d34ee 100644 --- a/plugins/cron/src/triggers/triggers.go +++ b/plugins/cron/src/triggers/triggers.go @@ -18,6 +18,8 @@ func main() { var err error switch trigger { + case "cron-write": + err = cron.TriggerCronWrite() case "post-delete": err = cron.TriggerPostDelete() case "post-deploy": diff --git a/plugins/cron/templates/cron.tmpl b/plugins/cron/templates/cron.tmpl index d1e32c148..12e964d72 100644 --- a/plugins/cron/templates/cron.tmpl +++ b/plugins/cron/templates/cron.tmpl @@ -2,5 +2,5 @@ PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash {{ range $entry := .Commands -}} -{{ $entry.Schedule }} dokku --rm run --cron-id {{ $entry.ID }} {{ $entry.App }} {{ $entry.Command }} +{{ $entry.Schedule }} {{ $entry.CronCommand }} {{ end -}} diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go index 34f75d070..74c18b19d 100644 --- a/plugins/cron/triggers.go +++ b/plugins/cron/triggers.go @@ -9,3 +9,8 @@ func TriggerPostDelete() error { func TriggerPostDeploy() error { return writeCronEntries() } + +// TriggerCronWrite force updates the cron file for all apps +func TriggerCronWrite() error { + return writeCronEntries() +} diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index e198a32c8..75aecb55c 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -5,9 +5,20 @@ load test_helper setup() { global_setup create_app + mkdir -p /var/lib/dokku/plugins/available/cron-entries + mkdir -p /var/lib/dokku/plugins/enabled/cron-entries + cat >"/var/lib/dokku/plugins/available/cron-entries/plugin.toml" < /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + + run /bin/bash -c "dokku plugin:trigger cron-write" + echo "output: $output" + echo "status: $status" + assert_success + + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "@daily /bin/true" + + # log file + echo "echo '@daily;/bin/true;/var/log/dokku/log.log'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + + run /bin/bash -c "dokku plugin:trigger cron-write" + echo "output: $output" + echo "status: $status" + assert_success + + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "@daily /bin/true &>> /var/log/dokku/log.log" + + # specify matching scheduler + echo "[[ \$1 == 'docker-local' ]] && echo '@daily;/bin/true'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + + run /bin/bash -c "dokku plugin:trigger cron-write" + echo "output: $output" + echo "status: $status" + assert_success + + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "@daily /bin/true" + + # specify non-matching scheduler + echo "[[ \$1 == 'kubernetes' ]] && echo '@daily;/bin/true'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + + run /bin/bash -c "dokku plugin:trigger cron-write" + echo "output: $output" + echo "status: $status" + assert_success + + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_failure +} template_cron_file_invalid() { local APP="$1"