feat: allow specifying maintenance mode in the file or not

If the app's cron tasks are set to maintenance mode, then the task is set to maintenance mode regardless.
This commit is contained in:
Jose Diaz-Gonzalez
2025-09-21 20:14:50 -04:00
parent 4a357e9896
commit e42df29a79
5 changed files with 13 additions and 7 deletions

View File

@@ -21,6 +21,7 @@
(list, optional) A list of cron resources. Keys are the names of the process types. The values are an object containing one or more of the following properties:
- `command`: (string, required)
- `maintenance`: (boolean, optional)
- `schedule`: (string, required)
## Formation

View File

@@ -34,6 +34,7 @@ The `app.json` file for a given app can define a special `cron` key that contain
A cron task takes the following properties:
- `command`: A command to be run within the built app image. Specified commands can also be `Procfile` entries.
- `maintenance`: A boolean value that decides whether the cron task is in maintenance and therefore executable or not.
- `schedule`: A [cron-compatible](https://en.wikipedia.org/wiki/Cron#Overview) scheduling definition upon which to run the command. Seconds are generally not supported.
Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase.

View File

@@ -55,6 +55,9 @@ type CronTask struct {
// Command is the command to execute
Command string `json:"command"`
// Maintenance is whether or not the cron command is in maintenance mode
Maintenance bool `json:"maintenance"`
// Schedule is the cron schedule to execute the command on
Schedule string `json:"schedule"`
}

View File

@@ -77,7 +77,7 @@ type FetchCronTasksInput struct {
func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) {
appName := input.AppName
tasks := []CronTask{}
isMaintenance := reportComputedMaintenance(appName) == "true"
isAppCronInMaintenance := reportComputedMaintenance(appName) == "true"
if input.AppJSON == nil && input.AppName == "" {
return tasks, fmt.Errorf("Missing app name or app.json")
@@ -121,12 +121,13 @@ func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) {
return tasks, fmt.Errorf("Invalid cron schedule for app %s (schedule %s): %s", appName, c.Schedule, err.Error())
}
maintenance := isAppCronInMaintenance || c.Maintenance
tasks = append(tasks, CronTask{
App: appName,
Command: c.Command,
Schedule: c.Schedule,
ID: GenerateCommandID(appName, c),
Maintenance: isMaintenance,
Maintenance: maintenance,
})
}

View File

@@ -169,7 +169,7 @@ func TriggerSchedulerCronWrite(scheduler string, appName string) error {
return nil
}
allCronEntries, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName})
cronTasks, err := cron.FetchCronTasks(cron.FetchCronTasksInput{AppName: appName})
if err != nil {
return fmt.Errorf("Error fetching cron entries: %w", err)
}
@@ -193,13 +193,13 @@ func TriggerSchedulerCronWrite(scheduler string, appName string) error {
return fmt.Errorf("Error creating kubernetes client: %w", err)
}
for _, cronEntry := range allCronEntries {
for _, cronTask := range cronTasks {
labelSelector := []string{
fmt.Sprintf("app.kubernetes.io/part-of=%s", appName),
fmt.Sprintf("dokku.com/cron-id=%s", cronEntry.ID),
fmt.Sprintf("dokku.com/cron-id=%s", cronTask.ID),
}
if cronEntry.Maintenance {
if cronTask.Maintenance {
err = clientset.SuspendCronJobs(ctx, SuspendCronJobsInput{
Namespace: namespace,
LabelSelector: strings.Join(labelSelector, ","),
@@ -706,7 +706,7 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
ID: cronTask.ID,
Schedule: cronTask.Schedule,
Suffix: suffix,
Suspend: cronEntry.Maintenance,
Suspend: cronTask.Maintenance,
},
Labels: labels,
ProcessType: ProcessType_Cron,