Merge pull request #7578 from dokku/6953-cron-maintenance
Add ability to set maintenance mode for all cron tasks running for an app
This commit is contained in:
@@ -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 `cron:set` command. Using `maintenance` as an example:
|
||||
|
||||
```shell
|
||||
dokku cron:set --global mailfrom example@example.com
|
||||
dokku cron: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 cron: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 cron: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 cron:set --global maintenance
|
||||
```
|
||||
|
||||
#### Listing Cron tasks
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -42,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
|
||||
@@ -59,6 +64,8 @@ func (t TemplateCommand) CronCommand() string {
|
||||
// FetchCronEntries returns a list of cron commands for a given app
|
||||
func FetchCronEntries(appName string) ([]TemplateCommand, error) {
|
||||
commands := []TemplateCommand{}
|
||||
isMaintenance := reportComputedMaintenance(appName) == "true"
|
||||
|
||||
appJSON, err := appjson.GetAppJSON(appName)
|
||||
if err != nil {
|
||||
return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error())
|
||||
@@ -76,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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user