Merge pull request #7750 from dokku/0.36-release

Release 0.36.0
This commit is contained in:
Jose Diaz-Gonzalez
2025-07-23 22:02:58 -04:00
committed by GitHub
124 changed files with 2174 additions and 951 deletions

View File

@@ -2,7 +2,9 @@
/subcommands/*
/triggers/*
/triggers
/app-json-is-valid
/cron-*
/install
/post-*
/report
/scheduler-stop

View File

@@ -1,5 +1,5 @@
SUBCOMMANDS = subcommands/list subcommands/report subcommands/run subcommands/set
TRIGGERS = triggers/cron-get-property
TRIGGERS = triggers/app-json-is-valid 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

View File

@@ -4,6 +4,7 @@ import (
"fmt"
appjson "github.com/dokku/dokku/plugins/app-json"
"github.com/dokku/dokku/plugins/common"
"github.com/multiformats/go-base36"
cronparser "github.com/robfig/cron/v3"
@@ -12,14 +13,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 +45,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
@@ -56,30 +62,63 @@ func (t TemplateCommand) CronCommand() string {
return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command)
}
// FetchCronEntriesInput is the input for the FetchCronEntries function
type FetchCronEntriesInput struct {
AppName string
AppJSON *appjson.AppJSON
WarnToFailure bool
}
// FetchCronEntries returns a list of cron commands for a given app
func FetchCronEntries(appName string) ([]TemplateCommand, error) {
func FetchCronEntries(input FetchCronEntriesInput) ([]TemplateCommand, error) {
appName := input.AppName
commands := []TemplateCommand{}
appJSON, err := appjson.GetAppJSON(appName)
if err != nil {
return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error())
isMaintenance := reportComputedMaintenance(appName) == "true"
if input.AppJSON == 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())
}
input.AppJSON = &appJSON
}
if appJSON.Cron == nil {
if input.AppJSON.Cron == nil {
return commands, nil
}
for _, c := range appJSON.Cron {
for i, c := range input.AppJSON.Cron {
if c.Command == "" {
if input.WarnToFailure {
return commands, fmt.Errorf("Missing cron command for app %s (index %d)", appName, i)
}
common.LogWarn(fmt.Sprintf("Missing cron command for app %s (index %d)", appName, i))
continue
}
if c.Schedule == "" {
if input.WarnToFailure {
return commands, fmt.Errorf("Missing cron schedule for app %s (index %d)", appName, i)
}
common.LogWarn(fmt.Sprintf("Missing cron schedule for app %s (index %d)", appName, i))
continue
}
parser := cronparser.NewParser(cronparser.Minute | cronparser.Hour | cronparser.Dom | cronparser.Month | cronparser.Dow | cronparser.Descriptor)
_, err := parser.Parse(c.Schedule)
if err != nil {
return commands, fmt.Errorf("Invalid cron schedule %s: %s", c.Schedule, err.Error())
return commands, fmt.Errorf("Invalid cron schedule for app %s (schedule %s): %s", appName, c.Schedule, err.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,
})
}

View File

@@ -1,6 +1,6 @@
module github.com/dokku/dokku/plugins/cron
go 1.23.0
go 1.24.0
require (
github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000

View File

@@ -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{}
@@ -38,6 +41,23 @@ func reportMailto(_ string) string {
}
func reportTasks(appName string) string {
c, _ := FetchCronEntries(appName)
c, _ := FetchCronEntries(FetchCronEntriesInput{AppName: 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")
}

View File

@@ -19,6 +19,10 @@ func main() {
var err error
switch trigger {
case "app-json-is-valid":
appName := flag.Arg(0)
appJSONPath := flag.Arg(1)
err = cron.TriggerAppJSONIsValid(appName, appJSONPath)
case "cron-get-property":
appName := flag.Arg(0)
property := flag.Arg(1)
@@ -43,6 +47,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)
}

View File

@@ -26,15 +26,15 @@ func CommandList(appName string, format string) error {
return fmt.Errorf("Invalid format specified, supported formats: json, stdout")
}
entries, err := FetchCronEntries(appName)
entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName})
if err != nil {
return err
}
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)
@@ -79,7 +79,7 @@ func CommandRun(appName string, cronID string, detached bool) error {
return err
}
entries, err := FetchCronEntries(appName)
entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName})
if err != nil {
return err
}
@@ -130,8 +130,10 @@ func CommandSet(appName string, property string, value string) error {
}
common.CommandPropertySet("cron", appName, property, value, DefaultProperties, GlobalProperties)
scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "cron-write",
Trigger: "scheduler-cron-write",
Args: []string{scheduler, appName},
StreamStdio: true,
})
return err

View File

@@ -4,20 +4,46 @@ import (
"errors"
"fmt"
appjson "github.com/dokku/dokku/plugins/app-json"
"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 {
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
}
// TriggerAppJSONIsValid validates the cron entries for a given app
func TriggerAppJSONIsValid(appName string, appJSONPath string) error {
if !common.FileExists(appJSONPath) {
return nil
}
appJSON, err := appjson.ReadAppJSON(appJSONPath)
if err != nil {
return err
}
_, err = FetchCronEntries(FetchCronEntriesInput{
AppName: appName,
AppJSON: &appJSON,
WarnToFailure: true,
})
if err != nil {
return err
}
return nil
}
@@ -55,5 +81,28 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
// TriggerPostDelete destroys the cron property for a given app container
func TriggerPostDelete(appName string) error {
return common.PropertyDestroy("cron", appName)
scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-cron-write",
Args: []string{scheduler, appName},
StreamStdio: true,
})
if err != nil {
return err
}
if err := common.PropertyDestroy("cron", appName); err != nil {
return err
}
return nil
}
// 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
}