package scheduler import ( "bytes" "encoding/gob" "fmt" "gitea.inhome.blapointe.com/local/firestormy/config" "gitea.inhome.blapointe.com/local/firestormy/config/ns" "gitea.inhome.blapointe.com/local/firestormy/logger" "gitea.inhome.blapointe.com/local/logb" "gitea.inhome.blapointe.com/local/storage" "os/exec" "strings" "time" "github.com/google/uuid" ) type Job struct { Title string Name string Schedule string Runner Runner Disabled bool foo func() LastStatus int LastOutput string LastRuntime time.Duration LastRun time.Time } func NewJob(runner Runner, schedule, raw string) (*Job, error) { switch runner { case Bash: return newBashJob(schedule, raw) default: return nil, ErrBadRunner } } func newBashJob(schedule, sh string, title ...string) (*Job, error) { if !validCron(schedule) { return nil, ErrBadCron } key := uuid.New().String() if err := config.Store.Set(key, []byte(sh), ns.JobsRaw...); err != nil { return nil, err } j := &Job{ Name: key, Schedule: schedule, Runner: Bash, } if len(title) == 0 || len(title[0]) == 0 { j.Title = j.Name } else { j.Title = title[0] } j.foo = func() { do := func() ([]byte, error) { logb.Debugf("[sched] run %s/%s? %v", j.Title, j.Name, j.Disabled) if j.Disabled { return nil, nil } sh, err := config.Store.Get(j.Name, ns.JobsRaw...) if err != nil { return nil, err } cmd := exec.Command("bash", "-c", string(sh)) j.LastRun = time.Now() start := time.Now() out, err := cmd.CombinedOutput() j.LastRuntime = time.Since(start) if cmd != nil && cmd.ProcessState != nil { j.LastStatus = cmd.ProcessState.ExitCode() } else { j.LastStatus = 1 } return out, err } b, err := do() logb.Debugf("[sched] run %s: (%v) %s", j.Name, err, b) if err != nil { b = []byte(fmt.Sprintf("err running command: %s: %s", err.Error(), b)) } j.LastOutput = strings.TrimSpace(string(b)) b2, err := j.Encode() if err == nil { err = config.Store.Set(j.Name, b2, ns.Jobs...) } logger.New().Info("result", fmt.Sprintf("(%v) %s", err, b)) } return j, nil } func (j *Job) Rename(name string) error { sh, err := config.Store.Get(j.Name, ns.JobsRaw...) if err != nil { return err } b, err := j.Encode() if err != nil { return err } if err := config.Store.Set(name, sh, ns.JobsRaw...); err != nil { return err } if err := config.Store.Set(name, b, ns.Jobs...); err != nil { return err } if err := config.Store.Set(j.Name, nil, ns.Jobs...); err != nil && err != storage.ErrNotFound { return err } if err := config.Store.Set(j.Name, nil, ns.JobsRaw...); err != nil && err != storage.ErrNotFound { return err } j.Name = name return nil } func (j *Job) Run() { j.foo() } func (j *Job) Encode() ([]byte, error) { buff := bytes.NewBuffer(nil) encoder := gob.NewEncoder(buff) err := encoder.Encode(*j) return buff.Bytes(), err } func (j *Job) Decode(b []byte) error { buff := bytes.NewReader(b) decoder := gob.NewDecoder(buff) err := decoder.Decode(j) if err != nil { return err } k, err := NewJob(j.Runner, j.Schedule, "") if err == nil { config.Store.Set(k.Name, nil, ns.JobsRaw...) config.Store.Set(k.Name, nil, ns.Jobs...) k.Name = j.Name k.Title = j.Title k.LastStatus = j.LastStatus k.LastOutput = j.LastOutput k.LastRuntime = j.LastRuntime k.LastRun = j.LastRun k.Disabled = j.Disabled *j = *k } return err }