From c0eb12f572312ac74286dae1c66a27292955480b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 9 Mar 2025 04:56:49 -0400 Subject: [PATCH 1/3] feat: add ability to set maintenance mode for all cron tasks running for an app Closes #6953 --- docs/processes/scheduled-cron-tasks.md | 34 +++++++++++++------------- plugins/cron/Makefile | 2 +- plugins/cron/cron.go | 14 ++++++++--- plugins/cron/report.go | 26 +++++++++++++++++--- plugins/cron/src/triggers/triggers.go | 5 ++++ plugins/cron/triggers.go | 17 ++++++++++--- 6 files changed, 70 insertions(+), 28 deletions(-) diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index 145c890c6..4994d507c 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -53,39 +53,39 @@ When running scheduled cron tasks, there are a few items to be aware of: - 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 MAILFROM value -> [!IMPORTANT] -> New as of 0.35.14 +### Changing cron management settings -Users can specify a value for `MAILFROM` via the global `mailfrom` cron property by using the `cron:set` command. +The `cron` plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: + +| Name | Description | Level | Global Default | +|-----------------------|----------------------------------------------------------------|-------------|----------------| +| `mailfrom` | Sets the `MAILFROM` variable in a cron file for cron reporting | Global-only | empty string | +| `maintenance` | Whether to have cron running for the app or not. | App-only | `false` | +| `mailto` | Sets the `MAILTO` variable in a cron file for cron reporting | Global-only | empty string | + +All settings can be set via the `ps:set` command. Using `maintenance` as an example: ```shell -dokku cron:set --global mailfrom example@example.com +dokku ps:set node-js-app maintenance true ``` -All output for individual cron runs will be sent from the specified email. - -Cron emails can be reset to use the system email by running the `cron:set` command for the global `mailfrom` property with no value. +The default value may be set by passing an empty value for the option in question: ```shell -dokku cron:set --global mailfrom +dokku ps:set node-js-app maintenance ``` -#### 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. +If a property can be set globally - such as `mailto`, use the `--global` flag. If not set for an app, the global value will apply if it exists. ```shell -dokku cron:set --global mailto example@example.com +dokku ps:set --global maintenance true ``` -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. +The global default value may be set by passing an empty value for the option. ```shell -dokku cron:set --global mailto +dokku ps:set --global maintenance ``` #### Listing Cron tasks diff --git a/plugins/cron/Makefile b/plugins/cron/Makefile index e0e1dbe33..920941136 100644 --- a/plugins/cron/Makefile +++ b/plugins/cron/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/list subcommands/report subcommands/run subcommands/set -TRIGGERS = triggers/cron-get-property +TRIGGERS = triggers/cron-get-property triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/scheduler-stop BUILD = commands subcommands triggers PLUGIN_NAME = cron diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index 056eadb4c..61a86bfce 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -12,14 +12,16 @@ import ( var ( // DefaultProperties is a map of all valid cron properties with corresponding default property values DefaultProperties = map[string]string{ - "mailfrom": "", - "mailto": "", + "mailfrom": "", + "mailto": "", + "maintenance": "false", } // GlobalProperties is a map of all valid global cron properties GlobalProperties = map[string]bool{ - "mailfrom": true, - "mailto": true, + "mailfrom": true, + "mailto": true, + "maintenance": true, } ) @@ -59,6 +61,10 @@ func (t TemplateCommand) CronCommand() string { // FetchCronEntries returns a list of cron commands for a given app func FetchCronEntries(appName string) ([]TemplateCommand, error) { commands := []TemplateCommand{} + if reportComputedMaintenance(appName) == "true" { + return commands, nil + } + appJSON, err := appjson.GetAppJSON(appName) if err != nil { return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error()) diff --git a/plugins/cron/report.go b/plugins/cron/report.go index c5fe8dc5d..769ed59a6 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -13,9 +13,12 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { } flags := map[string]common.ReportFunc{ - "--cron-mailfrom": reportMailfrom, - "--cron-mailto": reportMailto, - "--cron-task-count": reportTasks, + "--cron-mailfrom": reportMailfrom, + "--cron-mailto": reportMailto, + "--cron-task-count": reportTasks, + "--cron-global-maintenance": reportGlobalMaintenance, + "--cron-computed-maintenance": reportComputedMaintenance, + "--cron-maintenance": reportMaintenance, } flagKeys := []string{} @@ -41,3 +44,20 @@ func reportTasks(appName string) string { c, _ := FetchCronEntries(appName) return strconv.Itoa(len(c)) } + +func reportGlobalMaintenance(_ string) string { + return common.PropertyGet("cron", "--global", "maintenance") +} + +func reportComputedMaintenance(appName string) string { + maintenance := common.PropertyGet("cron", appName, "maintenance") + if maintenance == "true" { + return "true" + } + + return common.PropertyGetDefault("cron", "--global", "maintenance", DefaultProperties["maintenance"]) +} + +func reportMaintenance(appName string) string { + return common.PropertyGet("cron", appName, "maintenance") +} diff --git a/plugins/cron/src/triggers/triggers.go b/plugins/cron/src/triggers/triggers.go index 083cbb25b..6e0de5e7a 100644 --- a/plugins/cron/src/triggers/triggers.go +++ b/plugins/cron/src/triggers/triggers.go @@ -43,6 +43,11 @@ func main() { case "report": appName := flag.Arg(0) err = cron.ReportSingleApp(appName, "", "") + case "scheduler-stop": + scheduler := flag.Arg(0) + appName := flag.Arg(1) + removeContainers := flag.Arg(2) + err = cron.TriggerSchedulerStop(scheduler, appName, removeContainers) default: err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) } diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go index 42f99044a..321e2b8a9 100644 --- a/plugins/cron/triggers.go +++ b/plugins/cron/triggers.go @@ -10,14 +10,16 @@ import ( // TriggerCronGetProperty writes the cron key to stdout for a given app container func TriggerCronGetProperty(appName string, key string) error { validProperties := map[string]bool{ - "mailfrom": true, - "mailto": true, + "mailfrom": true, + "mailto": true, + "maintenance": true, } if !validProperties[key] { return errors.New("Invalid cron property specified") } - fmt.Println(common.PropertyGet("cron", appName, key)) + value := common.PropertyGetDefault("cron", appName, key, DefaultProperties[key]) + fmt.Println(value) return nil } @@ -57,3 +59,12 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { func TriggerPostDelete(appName string) error { return common.PropertyDestroy("cron", appName) } + +// TriggerSchedulerStop stops the scheduler for a given app container +func TriggerSchedulerStop(scheduler string, appName string, removeContainers string) error { + if scheduler != "docker-local" { + return nil + } + + return nil +} From 0f4b5d43050d6e03914021fc5a24401bf8b305ff Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 9 Mar 2025 05:03:43 -0400 Subject: [PATCH 2/3] fix: continue including maintenance mode cron tasks in the cron:list subcommand --- plugins/cron/cron.go | 16 +++++++++------- plugins/cron/subcommands.go | 4 ++-- plugins/scheduler-docker-local/functions.go | 9 +++++---- plugins/scheduler-k3s/triggers.go | 9 ++++++++- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index 61a86bfce..dd30c25e8 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -44,6 +44,9 @@ type TemplateCommand struct { // LogFile is the log file to write to LogFile string `json:"-"` + + // Maintenance is whether the cron command is in maintenance mode + Maintenance bool `json:"maintenance"` } // CronCommand returns the command to run for a given cron command @@ -61,9 +64,7 @@ func (t TemplateCommand) CronCommand() string { // FetchCronEntries returns a list of cron commands for a given app func FetchCronEntries(appName string) ([]TemplateCommand, error) { commands := []TemplateCommand{} - if reportComputedMaintenance(appName) == "true" { - return commands, nil - } + isMaintenance := reportComputedMaintenance(appName) == "true" appJSON, err := appjson.GetAppJSON(appName) if err != nil { @@ -82,10 +83,11 @@ func FetchCronEntries(appName string) ([]TemplateCommand, error) { } commands = append(commands, TemplateCommand{ - App: appName, - Command: c.Command, - Schedule: c.Schedule, - ID: GenerateCommandID(appName, c), + App: appName, + Command: c.Command, + Schedule: c.Schedule, + ID: GenerateCommandID(appName, c), + Maintenance: isMaintenance, }) } diff --git a/plugins/cron/subcommands.go b/plugins/cron/subcommands.go index 593f6212a..4e0e56462 100644 --- a/plugins/cron/subcommands.go +++ b/plugins/cron/subcommands.go @@ -32,9 +32,9 @@ func CommandList(appName string, format string) error { } if format == "stdout" { - output := []string{"ID | Schedule | Command"} + output := []string{"ID | Schedule | Maintenance | Command"} for _, entry := range entries { - output = append(output, fmt.Sprintf("%s | %s | %s", entry.ID, entry.Schedule, entry.Command)) + output = append(output, fmt.Sprintf("%s | %s | %t | %s", entry.ID, entry.Schedule, entry.Maintenance, entry.Command)) } result := columnize.SimpleFormat(output) diff --git a/plugins/scheduler-docker-local/functions.go b/plugins/scheduler-docker-local/functions.go index bcf852426..e6ec71eea 100644 --- a/plugins/scheduler-docker-local/functions.go +++ b/plugins/scheduler-docker-local/functions.go @@ -83,9 +83,10 @@ func generateCronEntries() ([]cron.TemplateCommand, error) { id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;"))) command := cron.TemplateCommand{ - ID: id, - Schedule: parts[0], - AltCommand: parts[1], + ID: id, + Schedule: parts[0], + AltCommand: parts[1], + Maintenance: false, } if len(parts) == 3 { command.LogFile = parts[2] @@ -106,7 +107,7 @@ func generateCronEntries() ([]cron.TemplateCommand, error) { for result := range results { c := result - if len(c) > 0 { + if len(c) > 0 && !c[0].Maintenance { commands = append(commands, c...) } } diff --git a/plugins/scheduler-k3s/triggers.go b/plugins/scheduler-k3s/triggers.go index cb4c27e49..5fa5e8b62 100644 --- a/plugins/scheduler-k3s/triggers.go +++ b/plugins/scheduler-k3s/triggers.go @@ -266,10 +266,17 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e workingDir := common.GetWorkingDir(appName, image) - cronEntries, err := cron.FetchCronEntries(appName) + allCronEntries, err := cron.FetchCronEntries(appName) if err != nil { return fmt.Errorf("Error fetching cron entries: %w", err) } + // remove maintenance cron entries + cronEntries := []cron.TemplateCommand{} + for _, cronEntry := range allCronEntries { + if !cronEntry.Maintenance { + cronEntries = append(cronEntries, cronEntry) + } + } domains := []string{} if _, ok := processes["web"]; ok { From d0b87936db50126d8ffbf853859c8e96659769e6 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 8 Jun 2025 22:35:17 -0400 Subject: [PATCH 3/3] docs: fix typo --- docs/processes/scheduled-cron-tasks.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index 4994d507c..ac1ed4809 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -64,28 +64,28 @@ The `cron` plugin provides a number of settings that can be used to managed depl | `maintenance` | Whether to have cron running for the app or not. | App-only | `false` | | `mailto` | Sets the `MAILTO` variable in a cron file for cron reporting | Global-only | empty string | -All settings can be set via the `ps:set` command. Using `maintenance` as an example: +All settings can be set via the `cron:set` command. Using `maintenance` as an example: ```shell -dokku ps:set node-js-app maintenance true +dokku cron:set node-js-app maintenance true ``` The default value may be set by passing an empty value for the option in question: ```shell -dokku ps:set node-js-app maintenance +dokku cron:set node-js-app maintenance ``` If a property can be set globally - such as `mailto`, use the `--global` flag. If not set for an app, the global value will apply if it exists. ```shell -dokku ps:set --global maintenance true +dokku cron:set --global maintenance true ``` The global default value may be set by passing an empty value for the option. ```shell -dokku ps:set --global maintenance +dokku cron:set --global maintenance ``` #### Listing Cron tasks