From c23e878e7dfc08146eb75ac85b630bb3d7e67f93 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 19 Aug 2023 23:59:00 -0400 Subject: [PATCH] feat: add the ability to specify a custom mailto for all cron output --- docs/development/plugin-triggers.md | 24 +++++++-- docs/processes/scheduled-cron-tasks.md | 17 +++++++ plugins/20_events/cron-get-property | 1 + plugins/cron/Makefile | 3 +- plugins/cron/cron.go | 12 +++++ plugins/cron/report.go | 5 ++ plugins/cron/set.go | 11 +++++ plugins/cron/src/triggers/triggers.go | 48 ++++++++++++++++++ plugins/cron/subcommands.go | 10 ++++ plugins/cron/templates/cron.tmpl | 3 ++ plugins/cron/triggers.go | 55 +++++++++++++++++++++ plugins/scheduler-docker-local/functions.go | 3 ++ 12 files changed, 187 insertions(+), 5 deletions(-) create mode 120000 plugins/20_events/cron-get-property create mode 100644 plugins/cron/set.go create mode 100644 plugins/cron/src/triggers/triggers.go create mode 100644 plugins/cron/triggers.go diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index b1646649f..a565068c9 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -549,18 +549,18 @@ echo "clock: some-command" >> Procfile popd &>/dev/null ``` -### `cron-write` +### `cron-get-property` -- Description: Force triggers writing out cron entries +- Description: Return the value for an app's cron property - Invoked by: -- Arguments: +- Arguments: `$APP $KEY` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; PROPERTY="$2" # TODO ``` @@ -583,6 +583,22 @@ DOKKU_SCHEDULER="$1" # TODO ``` +### `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 +``` + ### `dependencies` - Description: Used to install system-level dependencies. diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index 1a4212407..c82f59c3b 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -46,10 +46,27 @@ When running scheduled cron tasks, there are a few items to be aware of: - Scheduled cron tasks are performed within the app environment available at runtime. If the app image does not exist, the command may fail to execute. - Schedules are performed on the hosting server's timezone, which is typically UTC. - At this time, only the `PATH` and `SHELL` environment variables are specified in the cron template. + - A `MAILTO` value can be set via the `cron:set` command. - Each scheduled task is executed within a one-off `run` container, and thus inherit any docker-options specified for `run` containers. Resources are never shared between scheduled tasks. - Scheduled cron tasks are supported on a per-scheduler basis, and are currently only implemented by the `docker-local` scheduler. - Tasks for _all_ apps managed by the `docker-local` scheduler are written to a single crontab file owned by the `dokku` user. The `dokku` user's crontab should be considered reserved for this purpose. +#### Specifying a MAILTO value + +By default, cron tasks complete and do not perform any reporting. Users can specify a value for `MAILTO` via the global `mailto` cron property by using the `cron:set` command. + +```shell +dokku cron:set --global mailto example@example.com +``` + +All output for individual cron runs will be sent to the specified email. + +Cron emails can be disabled by running the `cron:set` command for the global `mailto` property with no value. + +```shell +dokku cron:set --global mailto +``` + #### Listing Cron tasks Cron tasks for an app can be listed via the `cron:list` command. This command takes an `app` argument. diff --git a/plugins/20_events/cron-get-property b/plugins/20_events/cron-get-property new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/cron-get-property @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/cron/Makefile b/plugins/cron/Makefile index 85c72cfea..914884ed1 100644 --- a/plugins/cron/Makefile +++ b/plugins/cron/Makefile @@ -1,5 +1,6 @@ SUBCOMMANDS = subcommands/list subcommands/report subcommands/run -BUILD = commands subcommands +TRIGGERS = triggers/cron-get-property +BUILD = commands subcommands triggers PLUGIN_NAME = cron include ../../common.mk diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index 7c71f0735..35eb42078 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -13,6 +13,18 @@ import ( cronparser "github.com/robfig/cron/v3" ) +var ( + // DefaultProperties is a map of all valid network properties with corresponding default property values + DefaultProperties = map[string]string{ + "mailto": "", + } + + // GlobalProperties is a map of all valid global network properties + GlobalProperties = map[string]bool{ + "mailto": true, + } +) + type TemplateCommand struct { ID string `json:"id"` App string `json:"app"` diff --git a/plugins/cron/report.go b/plugins/cron/report.go index da87de902..98af84c12 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -13,6 +13,7 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { } flags := map[string]common.ReportFunc{ + "--cron-mailto": reportMailto, "--cron-task-count": reportTasks, } @@ -27,6 +28,10 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { return common.ReportSingleApp("cron", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) } +func reportMailto(_ string) string { + return common.PropertyGet("cron", "--global", "mailto") +} + func reportTasks(appName string) string { c, _ := FetchCronEntries(appName) return strconv.Itoa(len(c)) diff --git a/plugins/cron/set.go b/plugins/cron/set.go new file mode 100644 index 000000000..47da8f167 --- /dev/null +++ b/plugins/cron/set.go @@ -0,0 +1,11 @@ +package cron + +import "errors" + +func validateSetValue(appName string, key string, value string) error { + if key == "mailto" && appName != "--global" { + return errors.New("Property cannot be specified on a per-app basis") + } + + return nil +} diff --git a/plugins/cron/src/triggers/triggers.go b/plugins/cron/src/triggers/triggers.go new file mode 100644 index 000000000..063d35425 --- /dev/null +++ b/plugins/cron/src/triggers/triggers.go @@ -0,0 +1,48 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/cron" +) + +// main entrypoint to all triggers +func main() { + parts := strings.Split(os.Args[0], "/") + trigger := parts[len(parts)-1] + flag.Parse() + + var err error + switch trigger { + case "cron-get-property": + appName := flag.Arg(0) + property := flag.Arg(1) + err = cron.TriggerCronGetProperty(appName, property) + case "install": + err = cron.TriggerInstall() + case "post-app-clone-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = cron.TriggerPostAppCloneSetup(oldAppName, newAppName) + case "post-app-rename-setup": + oldAppName := flag.Arg(0) + newAppName := flag.Arg(1) + err = cron.TriggerPostAppRenameSetup(oldAppName, newAppName) + case "post-delete": + appName := flag.Arg(0) + err = cron.TriggerPostDelete(appName) + case "report": + appName := flag.Arg(0) + err = cron.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/cron/subcommands.go b/plugins/cron/subcommands.go index 33949f3ab..f36545710 100644 --- a/plugins/cron/subcommands.go +++ b/plugins/cron/subcommands.go @@ -112,3 +112,13 @@ func CommandRun(appName string, cronID string, detached bool) error { args := append([]string{scheduler, appName, "0"}, fields...) return common.PlugnTrigger("scheduler-run", args...) } + +// CommandSet set or clear a cron property for an app +func CommandSet(appName string, property string, value string) error { + if err := validateSetValue(appName, property, value); err != nil { + return err + } + + common.CommandPropertySet("cron", appName, property, value, DefaultProperties, GlobalProperties) + return common.PlugnTrigger("cron-write") +} diff --git a/plugins/cron/templates/cron.tmpl b/plugins/cron/templates/cron.tmpl index 12e964d72..d171faf14 100644 --- a/plugins/cron/templates/cron.tmpl +++ b/plugins/cron/templates/cron.tmpl @@ -1,3 +1,6 @@ +{{ if .Mailto -}} +MAILTO={{ .Mailto }} +{{ end -}} PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go new file mode 100644 index 000000000..682e7b3e2 --- /dev/null +++ b/plugins/cron/triggers.go @@ -0,0 +1,55 @@ +package cron + +import ( + "errors" + "fmt" + + "github.com/dokku/dokku/plugins/common" +) + +// TriggerCronGetProperty writes the cron key to stdout for a given app container +func TriggerCronGetProperty(appName string, key string) error { + if key != "mailto" { + return errors.New("Invalid cron property specified") + } + + fmt.Println(common.PropertyGet("cron", appName, key)) + return nil +} + +// TriggerInstall runs the install step for the cron plugin +func TriggerInstall() error { + if err := common.PropertySetup("cron"); err != nil { + return fmt.Errorf("Unable to install the cron plugin: %s", err.Error()) + } + + return nil +} + +// TriggerPostAppCloneSetup creates new cron files +func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error { + err := common.PropertyClone("cron", oldAppName, newAppName) + if err != nil { + return err + } + + return nil +} + +// TriggerPostAppRenameSetup renames cron files +func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { + if err := common.PropertyClone("cron", oldAppName, newAppName); err != nil { + return err + } + + if err := common.PropertyDestroy("cron", oldAppName); err != nil { + return err + } + + return nil +} + +// TriggerPostDelete destroys the cron property for a given app container +func TriggerPostDelete(appName string) error { + return common.PropertyDestroy("cron", appName) +} diff --git a/plugins/scheduler-docker-local/functions.go b/plugins/scheduler-docker-local/functions.go index 89d1f49e4..2198f6061 100644 --- a/plugins/scheduler-docker-local/functions.go +++ b/plugins/scheduler-docker-local/functions.go @@ -116,8 +116,11 @@ func writeCronEntries() error { return deleteCrontab() } + mailto, _ := common.PlugnTriggerOutputAsString("cron-get-property", []string{"--global", "mailto"}...) + data := map[string]interface{}{ "Commands": commands, + "Mailto": mailto, } t, err := getCronTemplate()