mirror of
https://github.com/dokku/dokku.git
synced 2025-12-25 16:29:30 +01:00
These flags are not commonly invoked by users, causing issues when cleaning up old containers. Rather than instruct users to use some random flag, just change the default to what is likely to be more common.
194 lines
4.5 KiB
Go
194 lines
4.5 KiB
Go
package cron
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
appjson "github.com/dokku/dokku/plugins/app-json"
|
|
"github.com/dokku/dokku/plugins/common"
|
|
|
|
cronparser "github.com/robfig/cron/v3"
|
|
)
|
|
|
|
type templateCommand struct {
|
|
ID string
|
|
App string
|
|
Command string
|
|
Schedule string
|
|
AltCommand string
|
|
LogFile string
|
|
}
|
|
|
|
func (t templateCommand) CronCommand() string {
|
|
if t.AltCommand != "" {
|
|
if t.LogFile != "" {
|
|
return fmt.Sprintf("%s &>> %s", t.AltCommand, t.LogFile)
|
|
}
|
|
return t.AltCommand
|
|
}
|
|
|
|
return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command)
|
|
}
|
|
|
|
func fetchCronEntries(appName string) ([]templateCommand, error) {
|
|
commands := []templateCommand{}
|
|
scheduler := common.GetAppScheduler(appName)
|
|
if scheduler != "docker-local" {
|
|
return commands, nil
|
|
}
|
|
|
|
appjsonPath := appjson.GetAppjsonPath(appName)
|
|
if !common.FileExists(appjsonPath) {
|
|
return commands, nil
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(appjsonPath)
|
|
if err != nil {
|
|
return commands, fmt.Errorf("Cannot read app.json file for %s: %v", appName, err)
|
|
}
|
|
|
|
if strings.TrimSpace(string(b)) == "" {
|
|
return commands, nil
|
|
}
|
|
|
|
var appJSON appjson.AppJSON
|
|
if err = json.Unmarshal(b, &appJSON); err != nil {
|
|
return commands, fmt.Errorf("Cannot parse app.json for %s: %v", appName, err)
|
|
}
|
|
|
|
for _, c := range appJSON.Cron {
|
|
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, err
|
|
}
|
|
|
|
commands = append(commands, templateCommand{
|
|
App: appName,
|
|
Command: c.Command,
|
|
Schedule: c.Schedule,
|
|
ID: generateCommandID(appName, c),
|
|
})
|
|
}
|
|
|
|
return commands, nil
|
|
}
|
|
|
|
func deleteCrontab() error {
|
|
command := common.NewShellCmd("crontab -l -u dokku")
|
|
command.ShowOutput = false
|
|
if !command.Execute() {
|
|
return nil
|
|
}
|
|
|
|
command = common.NewShellCmd("crontab -r -u dokku")
|
|
command.ShowOutput = false
|
|
out, err := command.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to remove schedule file: %v", string(out))
|
|
}
|
|
|
|
common.LogInfo1("Removed")
|
|
return nil
|
|
}
|
|
|
|
func writeCronEntries() error {
|
|
apps, _ := common.DokkuApps()
|
|
commands := []templateCommand{}
|
|
for _, appName := range apps {
|
|
scheduler := common.GetAppScheduler(appName)
|
|
if scheduler != "docker-local" {
|
|
continue
|
|
}
|
|
|
|
c, err := fetchCronEntries(appName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
commands = append(commands, c...)
|
|
}
|
|
|
|
b, _ := common.PlugnTriggerOutput("cron-entries", "docker-local")
|
|
for _, line := range strings.Split(strings.TrimSpace(string(b[:])), "\n") {
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(line, ";")
|
|
if len(parts) != 2 && len(parts) != 3 {
|
|
return fmt.Errorf("Invalid injected cron task: %v", line)
|
|
}
|
|
|
|
id := base64.StdEncoding.EncodeToString([]byte(strings.Join(parts, ";;;")))
|
|
command := templateCommand{
|
|
ID: id,
|
|
Schedule: parts[0],
|
|
AltCommand: parts[1],
|
|
}
|
|
if len(parts) == 3 {
|
|
command.LogFile = parts[2]
|
|
}
|
|
commands = append(commands, command)
|
|
}
|
|
|
|
if len(commands) == 0 {
|
|
return deleteCrontab()
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"Commands": commands,
|
|
}
|
|
|
|
t, err := getCronTemplate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpFile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "WriteCronEntries"))
|
|
if err != nil {
|
|
return fmt.Errorf("Cannot create temporary schedule file: %v", err)
|
|
}
|
|
|
|
defer tmpFile.Close()
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
if err := t.Execute(tmpFile, data); err != nil {
|
|
return fmt.Errorf("Unable to template out schedule file: %v", err)
|
|
}
|
|
|
|
command := common.NewShellCmd(fmt.Sprintf("crontab -u dokku %s", tmpFile.Name()))
|
|
command.ShowOutput = false
|
|
out, err := command.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to update schedule file: %v", out)
|
|
}
|
|
|
|
common.LogInfo1("Updated schedule file")
|
|
|
|
return nil
|
|
}
|
|
|
|
func getCronTemplate() (*template.Template, error) {
|
|
t := template.New("cron")
|
|
|
|
templatePath := filepath.Join(common.MustGetEnv("PLUGIN_ENABLED_PATH"), "cron", "templates", "cron.tmpl")
|
|
b, err := ioutil.ReadFile(templatePath)
|
|
if err != nil {
|
|
return t, fmt.Errorf("Cannot read template file: %v", err)
|
|
}
|
|
|
|
s := strings.TrimSpace(string(b))
|
|
return t.Parse(s)
|
|
}
|
|
|
|
func generateCommandID(appName string, c appjson.CronCommand) string {
|
|
return base64.StdEncoding.EncodeToString([]byte(appName + "===" + c.Command + "===" + c.Schedule))
|
|
}
|