2
plugins/cron/.gitignore
vendored
2
plugins/cron/.gitignore
vendored
@@ -2,7 +2,9 @@
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/app-json-is-valid
|
||||
/cron-*
|
||||
/install
|
||||
/post-*
|
||||
/report
|
||||
/scheduler-stop
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user