- schedule:run runs a worker on the scheduled queue with every plugin's periodic jobs - schedule:run --once runs entries due in the current app.timezone minute without River, warns and skips unregistered commands, and returns the first command error - summer schedule:run delegate forwards --once - tests for --once minute matching, forged-entry skipping (T-11-09) and ByPeriod dedupe
140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/summercms/internal/build"
|
|
"git.golem15.com/golem15/summercms/modules/bonfire"
|
|
)
|
|
|
|
func delegateCommand(name, description string) bonfire.Command {
|
|
return bonfire.Command{
|
|
Name: name,
|
|
Description: description,
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
argv := []string{name}
|
|
argv = append(argv, in.Args()...)
|
|
return runAppBinary(ctx, argv, out)
|
|
},
|
|
}
|
|
}
|
|
|
|
func delegateRollbackCommand() bonfire.Command {
|
|
return bonfire.Command{
|
|
Name: "migrate:rollback",
|
|
Description: "Roll back the last migration of a plugin in the app binary",
|
|
Flags: []bonfire.Flag{{
|
|
Name: "plugin",
|
|
Description: "Plugin ID whose last migration to roll back",
|
|
}},
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
argv := []string{"migrate:rollback"}
|
|
if plugin, ok := in.Flag("plugin"); ok && plugin != "" {
|
|
argv = append(argv, "--plugin", plugin)
|
|
}
|
|
argv = append(argv, in.Args()...)
|
|
return runAppBinary(ctx, argv, out)
|
|
},
|
|
}
|
|
}
|
|
|
|
func delegateQueueWorkCommand() bonfire.Command {
|
|
return bonfire.Command{
|
|
Name: "queue:work",
|
|
Description: "Run background job workers in the app binary",
|
|
Flags: []bonfire.Flag{{
|
|
Name: "queue",
|
|
Description: "Queue to work (repeatable; default all known queues)",
|
|
Repeatable: true,
|
|
}},
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
argv := []string{"queue:work"}
|
|
for _, q := range in.Flags("queue") {
|
|
argv = append(argv, "--queue", q)
|
|
}
|
|
argv = append(argv, in.Args()...)
|
|
return runAppBinary(ctx, argv, out)
|
|
},
|
|
}
|
|
}
|
|
|
|
func delegateScheduleRunCommand() bonfire.Command {
|
|
return bonfire.Command{
|
|
Name: "schedule:run",
|
|
Description: "Run the scheduler in the app binary (or once for system cron)",
|
|
Flags: []bonfire.Flag{{
|
|
Name: "once",
|
|
Description: "Run the entries due this minute and exit (for system cron)",
|
|
Bare: true,
|
|
}},
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
argv := []string{"schedule:run"}
|
|
if v, ok := in.Flag("once"); ok {
|
|
if once, _ := strconv.ParseBool(v); once {
|
|
argv = append(argv, "--once")
|
|
}
|
|
}
|
|
argv = append(argv, in.Args()...)
|
|
return runAppBinary(ctx, argv, out)
|
|
},
|
|
}
|
|
}
|
|
|
|
func runAppBinary(ctx context.Context, argv []string, out bonfire.Output) error {
|
|
if len(argv) == 0 {
|
|
return fmt.Errorf("summer: missing app command")
|
|
}
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
appDir, err := build.FindAppDir(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m, err := build.LoadManifest(filepath.Join(appDir, "summer.yaml"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bin := filepath.Join(appDir, "bin", m.Binary)
|
|
if _, err := os.Stat(bin); err != nil {
|
|
if err := build.App(ctx, appDir, out); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
self, err := os.Executable()
|
|
if err == nil {
|
|
if same, _ := sameFile(self, bin); same {
|
|
return fmt.Errorf("summer: refusing to recurse into %s", bin)
|
|
}
|
|
}
|
|
cmd := exec.CommandContext(ctx, bin, argv...)
|
|
cmd.Dir = appDir
|
|
cmd.Stdout = out
|
|
cmd.Stderr = out
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Env = os.Environ()
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("summer: %s: %w", strings.Join(argv, " "), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sameFile(a, b string) (bool, error) {
|
|
ai, err := os.Stat(a)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
bi, err := os.Stat(b)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return os.SameFile(ai, bi), nil
|
|
}
|