Files

198 lines
4.5 KiB
Go
Raw Permalink Normal View History

package schedulerdockerlocal
import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/cron"
"golang.org/x/sync/errgroup"
base36 "github.com/multiformats/go-base36"
)
func deleteCrontab() error {
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: "crontab",
Args: []string{"-l", "-u", "dokku"},
})
if err != nil || result.ExitCode != 0 {
return nil
}
result, err = common.CallExecCommand(common.ExecCommandInput{
Command: "crontab",
Args: []string{"-r", "-u", "dokku"},
})
if err != nil {
return fmt.Errorf("Unable to remove schedule file: %w", err)
}
if result.ExitCode != 0 {
return fmt.Errorf("Unable to remove schedule file: %s", result.StderrContents())
}
common.LogInfo1("Removed")
return nil
}
func generateCronTasks() ([]cron.CronTask, error) {
apps, _ := common.UnfilteredDokkuApps()
g := new(errgroup.Group)
results := make(chan []cron.CronTask, len(apps)+1)
for _, appName := range apps {
appName := appName
g.Go(func() error {
scheduler := common.GetAppScheduler(appName)
if scheduler != "docker-local" {
results <- []cron.CronTask{}
return nil
}
c, err := cron.FetchCronTasks(cron.FetchCronTasksInput{AppName: appName})
if err != nil {
results <- []cron.CronTask{}
common.LogWarn(err.Error())
return nil
}
results <- c
return nil
})
}
g.Go(func() error {
tasks := []cron.CronTask{}
response, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "cron-entries",
Args: []string{"docker-local"},
})
for _, line := range strings.Split(response.StdoutContents(), "\n") {
if strings.TrimSpace(line) == "" {
results <- []cron.CronTask{}
return nil
}
parts := strings.Split(line, ";")
if len(parts) != 2 && len(parts) != 3 {
results <- []cron.CronTask{}
return fmt.Errorf("Invalid injected cron task: %v", line)
}
id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;")))
task := cron.CronTask{
ID: id,
Schedule: parts[0],
AltCommand: parts[1],
Maintenance: false,
}
if len(parts) == 3 {
task.LogFile = parts[2]
}
tasks = append(tasks, task)
}
results <- tasks
return nil
})
err := g.Wait()
close(results)
tasks := []cron.CronTask{}
if err != nil {
return tasks, err
}
for result := range results {
for _, task := range result {
if !task.Maintenance {
tasks = append(tasks, task)
}
}
}
return tasks, nil
}
func writeCronTab(scheduler string) error {
// allow empty scheduler, which means all apps (used by letsencrypt)
if scheduler != "docker-local" && scheduler != "" {
return nil
}
tasks, err := generateCronTasks()
if err != nil {
return err
}
if len(tasks) == 0 {
return deleteCrontab()
}
resultfromResults, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "cron-get-property",
Args: []string{"--global", "mailfrom"},
})
mailfrom := resultfromResults.StdoutContents()
mailtoResults, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "cron-get-property",
Args: []string{"--global", "mailto"},
})
mailto := mailtoResults.StdoutContents()
data := map[string]interface{}{
"Tasks": tasks,
"Mailfrom": mailfrom,
"Mailto": mailto,
}
t, err := getCronTemplate()
if err != nil {
return err
}
tmpFile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf("dokku-%s-%s", common.MustGetEnv("DOKKU_PID"), "WriteCronTab"))
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)
}
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: "crontab",
Args: []string{"-u", "dokku", tmpFile.Name()},
})
if err != nil {
return fmt.Errorf("Unable to update schedule file: %w", err)
}
if result.ExitCode != 0 {
return fmt.Errorf("Unable to update schedule file: %s", result.StderrContents())
}
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 := os.ReadFile(templatePath)
if err != nil {
return t, fmt.Errorf("Cannot read template file: %v", err)
}
s := strings.TrimSpace(string(b))
return t.Parse(s)
}