mirror of
https://github.com/dokku/dokku.git
synced 2025-12-29 00:25:08 +01:00
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 --rm run --cron-id %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))
|
|
}
|