Fix job encode decode

master
bel 2020-03-15 16:13:44 +00:00
parent dbf023e61b
commit e17e266712
111 changed files with 232 additions and 41 deletions

23
TODO.md Normal file → Executable file
View File

@ -2,18 +2,22 @@
1. UI to view
1. running job
1. jobs
x jobs
1. job definition
1. next runtime
1. last runtime
1. last output
1. add titles to jobs
1. job title includes last pass/fail icon
1. button to modify (copies to upsert form)
1. button to delete
1. UI to mutate
1. submit job
1. delete job
1. pause jobs
1. interrupt job
1. JS
1. ajax for json calls
x ajax for json calls
# Backend
@ -21,5 +25,18 @@ x load from file
1. interrupt running jobs
1. temporarily disable jobs
1. json API
1. list
1. last run output
1. last run pass/fail bool
1. last run timestamp
1. next run
1. upsert
1. delete job
1. pause/disable job
1. running job
1. interrupt job
1. change cron to load the full job from storage so not holding big queued jobs in ram
1. add optional second for test main
x add optional second for test main
1. test main
1. add titles to jobs
1. namespace for jobs, output, lastrun, laststatus

0
config/config_test.go Normal file → Executable file
View File

6
config/ns/ns.go Executable file
View File

@ -0,0 +1,6 @@
package ns
var (
Jobs = []string{"jobs"}
Output = []string{"jobs", "output"}
)

0
logger/logger.go Normal file → Executable file
View File

0
logger/logger_test.go Normal file → Executable file
View File

26
main.go
View File

@ -3,8 +3,10 @@ package main
import (
"local/firestormy/config"
"local/firestormy/scheduler"
"local/firestormy/server"
"local/lastn/lastn"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
@ -26,21 +28,19 @@ func main() {
panic(err)
}
/*
server := server.New()
if err := server.Routes(); err != nil {
server := server.New()
if err := server.Routes(); err != nil {
panic(err)
}
//go EnqueueBackups()
go func() {
log.Printf("Serving on %q", config.Port)
if err := http.ListenAndServe(config.Port, server); err != nil {
panic(err)
}
go EnqueueBackups()
go func() {
log.Printf("Serving on %q", config.Port)
if err := http.ListenAndServe(config.Port, server); err != nil {
panic(err)
}
}()
*/
}()
// catch stop
stop := make(chan os.Signal)

0
scheduler/errors.go Normal file → Executable file
View File

0
scheduler/errors_test.go Normal file → Executable file
View File

55
scheduler/job.go Normal file → Executable file
View File

@ -4,18 +4,26 @@ import (
"bytes"
"encoding/gob"
"fmt"
"local/firestormy/config"
"local/firestormy/config/ns"
"local/firestormy/logger"
"os/exec"
"strings"
"time"
"github.com/google/uuid"
)
type Job struct {
Name string
Schedule string
Raw string
Runner Runner
foo func()
Name string
Schedule string
Raw string
Runner Runner
foo func()
LastStatus int
LastOutput string
LastRuntime time.Duration
LastRun time.Time
}
func NewJob(runner Runner, schedule, raw string) (*Job, error) {
@ -31,20 +39,33 @@ func newBashJob(schedule, sh string) (*Job, error) {
if !validCron(schedule) {
return nil, ErrBadCron
}
return &Job{
j := &Job{
Name: uuid.New().String(),
Schedule: schedule,
Raw: sh,
Runner: Bash,
foo: func() {
cmd := exec.Command("bash", "-c", sh)
out, err := cmd.CombinedOutput()
if err != nil {
panic(err)
}
logger.New().Info(fmt.Sprintf("executed %s: %s", sh, out))
},
}, nil
}
j.foo = func() {
cmd := exec.Command("bash", "-c", sh)
j.LastRun = time.Now()
start := time.Now()
out, err := cmd.CombinedOutput()
j.LastRuntime = time.Since(start)
if err != nil {
out = []byte(fmt.Sprintf("error running command: %v: %v", err, out))
}
j.LastOutput = strings.TrimSpace(string(out))
if cmd != nil && cmd.ProcessState != nil {
j.LastStatus = cmd.ProcessState.ExitCode()
}
logger.New().Info(fmt.Sprintf("%+v", j))
b, err := j.Encode()
if err == nil {
// TODO webpage doenst load post SET despite this returning nil
config.Store.Set(j.Name, b, ns.Jobs...)
}
}
return j, nil
}
func (j *Job) Run() {
@ -68,6 +89,10 @@ func (j *Job) Decode(b []byte) error {
k, err := NewJob(j.Runner, j.Schedule, j.Raw)
if err == nil {
k.Name = j.Name
k.LastStatus = j.LastStatus
k.LastOutput = j.LastOutput
k.LastRuntime = j.LastRuntime
k.LastRun = j.LastRun
*j = *k
}
return err

24
scheduler/job_test.go Normal file → Executable file
View File

@ -3,9 +3,12 @@ package scheduler
import (
"bytes"
"io/ioutil"
"local/firestormy/config"
"local/logb"
"local/storage"
"os"
"testing"
"time"
)
func TestNewBashJobBadCron(t *testing.T) {
@ -16,6 +19,7 @@ func TestNewBashJobBadCron(t *testing.T) {
}
func TestNewBashJobAndRun(t *testing.T) {
config.Store = storage.NewMap()
cases := []struct {
sched string
cmd string
@ -45,12 +49,7 @@ func TestNewBashJobAndRun(t *testing.T) {
t.Error(err)
continue
}
func() {
defer func() {
recover()
}()
j.Run()
}()
j.Run()
if !bytes.Contains(b.Bytes(), []byte(c.out)) {
t.Errorf("(%s, %s) => %s", c.sched, c.cmd, b.Bytes())
}
@ -58,6 +57,7 @@ func TestNewBashJobAndRun(t *testing.T) {
}
func TestJobEncodeDecode(t *testing.T) {
config.Store = storage.NewMap()
buff, clean := captureLog()
defer clean()
@ -89,6 +89,18 @@ func TestJobEncodeDecode(t *testing.T) {
if k.Name != j.Name {
t.Error(k.Name, "vs", j.Name)
}
if diff := k.LastRun.Unix() - j.LastRun.Unix(); (diff > 0 && diff < int64(time.Hour)) || (diff < 0 && -1*diff < int64(time.Hour)) {
t.Error(j.LastRun, "vs", k.LastRun)
}
if k.LastStatus != j.LastStatus {
t.Error(j.LastStatus, "vs", k.LastStatus)
}
if k.LastRuntime != j.LastRuntime {
t.Error(j.LastRuntime, "vs", k.LastRuntime)
}
if string(k.LastOutput) != string(j.LastOutput) {
t.Error(j.LastOutput, "vs", k.LastOutput)
}
k.foo()
if !bytes.Contains(buff.Bytes(), []byte(os.Getenv("HOSTNAME"))) {

0
scheduler/parser.go Normal file → Executable file
View File

0
scheduler/parser_test.go Normal file → Executable file
View File

20
scheduler/runner.go Normal file → Executable file
View File

@ -5,3 +5,23 @@ type Runner int
const (
Bash Runner = iota + 1
)
func (r Runner) String() string {
switch r {
case Bash:
return "bash"
default:
return ""
}
}
func NewRunner(s string) Runner {
for _, r := range []Runner{
Bash,
} {
if r.String() == s {
return r
}
}
return 0
}

9
scheduler/scheduler.go Normal file → Executable file
View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"local/firestormy/config"
"local/firestormy/config/ns"
"local/firestormy/logger"
"regexp"
"strings"
@ -117,7 +118,7 @@ func (s *Scheduler) Stop() error {
}
func (s *Scheduler) List() ([]*Job, error) {
entries, err := config.Store.List(nil)
entries, err := config.Store.List(ns.Jobs)
if err != nil {
return nil, err
}
@ -139,7 +140,7 @@ func (s *Scheduler) List() ([]*Job, error) {
}
func (s *Scheduler) loadJobFromStore(k string) (*Job, error) {
b, err := config.Store.Get(k)
b, err := config.Store.Get(k, ns.Jobs...)
if err != nil {
return nil, err
}
@ -160,7 +161,7 @@ func (s *Scheduler) Add(j *Job) error {
if err != nil {
return err
}
if err := config.Store.Set(j.Name, b); err != nil {
if err := config.Store.Set(j.Name, b, ns.Jobs...); err != nil {
return err
}
s.running[j.Name] = entryID
@ -178,7 +179,7 @@ func (s *Scheduler) Remove(j *Job) error {
if was == is {
return ErrJobNotFound
}
return config.Store.Set(j.Name, nil)
return config.Store.Set(j.Name, nil, ns.Jobs...)
}
func (s *Scheduler) getEntry(j *Job) (cron.EntryID, bool) {

0
scheduler/scheduler_test.go Normal file → Executable file
View File

View File

@ -1,9 +1,15 @@
package server
import (
"encoding/json"
"fmt"
"local/firestormy/config"
"local/firestormy/config/ns"
"local/firestormy/scheduler"
"local/router"
"log"
"net/http"
"sort"
)
func (s *Server) Routes() error {
@ -12,6 +18,14 @@ func (s *Server) Routes() error {
path string
handler http.HandlerFunc
}{
{
path: fmt.Sprintf("/upserts"),
handler: s.gzip(s.authenticate(s.upserts)),
},
{
path: fmt.Sprintf("/list"),
handler: s.gzip(s.authenticate(s.list)),
},
{
path: fmt.Sprintf("%s%s", wildcard, wildcard),
handler: s.gzip(s.authenticate(s.static)),
@ -29,3 +43,47 @@ func (s *Server) Routes() error {
func (s *Server) static(w http.ResponseWriter, r *http.Request) {
s.fileServer.ServeHTTP(w, r)
}
func (s *Server) upserts(w http.ResponseWriter, r *http.Request) {
upsert, err := newUpsertRequest(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Println("received", upsert)
http.Error(w, "not impl", http.StatusNotImplemented)
}
func (s *Server) list(w http.ResponseWriter, r *http.Request) {
jobs, err := config.Store.List(ns.Jobs)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sort.Strings(jobs)
out := make([]map[string]interface{}, len(jobs))
for i, job := range jobs {
out[i] = make(map[string]interface{})
b, err := config.Store.Get(job, ns.Jobs...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
j := &scheduler.Job{}
if err := j.Decode(b); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
out[i]["id"] = j.Name
out[i]["cron"] = j.Schedule
out[i]["language"] = j.Runner.String()
out[i]["script"] = j.Raw
out[i]["last"] = map[string]interface{}{
"run": j.LastRun,
"runtime": j.LastRuntime,
"output": j.LastOutput,
"status": j.LastStatus,
}
}
json.NewEncoder(w).Encode(out)
}

46
server/upserts.go Executable file
View File

@ -0,0 +1,46 @@
package server
import (
"encoding/json"
"errors"
"fmt"
"io"
"local/firestormy/config"
"local/firestormy/config/ns"
"github.com/google/uuid"
)
type upsertRequest struct {
ID string `json:"id"`
Language string `json:"language"`
Cron string `json:"cron"`
Script string `json:"script"`
}
func newUpsertRequest(r io.Reader) (upsertRequest, error) {
u := upsertRequest{}
if err := json.NewDecoder(r).Decode(&u); err != nil {
return u, err
}
err := u.validate()
return u, err
}
func (u *upsertRequest) validate() error {
if u.ID == "" {
u.ID = uuid.New().String()
} else if _, err := config.Store.Get(u.ID, ns.Jobs...); err != nil {
return fmt.Errorf("ID provided but not accessible: %v", err)
}
if u.Language == "" {
return errors.New("language required")
}
if u.Cron == "" {
return errors.New("cron required")
}
if u.Script == "" {
return errors.New("script required")
}
return nil
}

5
testdata/5_jobs.cron vendored Executable file
View File

@ -0,0 +1,5 @@
0 */5 * * * * echo first job
0 */15 * * * * echo second job
0 */25 * * * * echo third job
0 */35 * * * * echo fourth job
0 */45 * * * * true

0
testdata/comment_only.cron vendored Normal file → Executable file
View File

1
testdata/hostname_per_5m.cron vendored Executable file
View File

@ -0,0 +1 @@
0 */5 * * * * hostname

0
testdata/hostname_per_second.cron vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/CONTRIBUTING.md generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/LICENSE generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/MAINTAINERS.md generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/Makefile generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/NOTICE generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/README.md generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/README_zh_CN.md generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-compose-object.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-datatypes.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-error-response.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-bucket-encryption.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-lifecycle.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-object-acl-context.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-object-acl.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-object-context.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-object-file.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-object.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-options.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-get-policy.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-list.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-notification.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-object-legal-hold.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-object-lock.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-object-retention.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-object-tagging.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-presigned.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-bucket.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-common.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-context.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-copy.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-file-context.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-file.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-multipart.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object-streaming.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-put-object.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-remove.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-s3-datatypes.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-select.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api-stat.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/api.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/appveyor.yml generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/bucket-cache.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/bucket-notification.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/constants.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/core.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/go.mod generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/go.sum generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/hook-reader.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/chain.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/config.json.sample generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.sample generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/doc.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/env_aws.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/env_minio.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/file_aws_credentials.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/file_minio_client.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/iam_aws.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/signature-type.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/static.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_client_grants.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_ldap_identity.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_web_identity.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/encrypt/server-side.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-streaming.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v2.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v4.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/s3signer/utils.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/s3utils/utils.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/pkg/set/stringset.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/post-policy.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/retry-continous.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/retry.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/s3-endpoints.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/s3-error.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/staticcheck.conf generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/transport.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/minio/minio-go/v6/utils.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/robfig/cron/v3/LICENSE generated vendored Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More