From eb7455da8d055d943113e6e88ce7262f42d133d4 Mon Sep 17 00:00:00 2001 From: Matthieu Martin Date: Fri, 18 Nov 2016 11:04:31 +0100 Subject: [PATCH] Parsing config file and scheduling jobs --- .gitignore | 1 + README.md | 4 +- config.go | 15 +++++++ cron.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++ cronr.example.yml | 12 ++++++ glide.lock | 10 +++++ glide.yaml | 5 +++ main.go | 32 ++++++++++++++ 8 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 config.go create mode 100644 cron.go create mode 100644 cronr.example.yml create mode 100644 glide.lock create mode 100644 glide.yaml create mode 100644 main.go diff --git a/.gitignore b/.gitignore index daf913b..d8ac91b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +vendor # Architecture specific extensions/prefixes *.[568vq] diff --git a/README.md b/README.md index 7200e9d..33810d8 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# cronr \ No newline at end of file +# cronr + +cronr is a daemon utility intended to be an alternative to classic cron. The crontab is replaced by a custom config file, containing the jobs list. When cronr is started, each job is scheduled according to the config. \ No newline at end of file diff --git a/config.go b/config.go new file mode 100644 index 0000000..9313423 --- /dev/null +++ b/config.go @@ -0,0 +1,15 @@ +package main + +// ConfigFile represents a YAML config file +type ConfigFile struct { + Jobs []Job +} + +// Job represents a unit to schedule +type Job struct { + Cron string `yaml:"cron"` + Command []string `yaml:"command"` + // User uint32 `yaml:"user"` + // Group uint32 `yaml:"group"` + WorkingDir string `yaml:"working_dir"` +} diff --git a/cron.go b/cron.go new file mode 100644 index 0000000..60be734 --- /dev/null +++ b/cron.go @@ -0,0 +1,105 @@ +package main + +import ( + "io/ioutil" + "log" + "os" + "os/exec" + "os/signal" + + "github.com/robfig/cron" + yaml "gopkg.in/yaml.v2" +) + +type command struct { + name string + args []string +} + +// CronAction parses and schedules jobs, waiting for SIGINT signal to stop +func CronAction(configFilepath string, verbose bool) error { + + // get config file content + fileContent, err := ioutil.ReadFile(configFilepath) + if err != nil { + return err + } + + // parse config file + config := ConfigFile{} + err = yaml.Unmarshal(fileContent, &config) + if err != nil { + return err + } + + // prepare cron + c := cron.New() + + // creating jobs + for i := range config.Jobs { + job := config.Jobs[i] + + c.AddFunc(job.Cron, func() { + + if verbose { + log.Println(job) + } + + cmdArgs := newCommand(job.Command) + cmd := exec.Command(cmdArgs.name, cmdArgs.args...) + + if job.WorkingDir != "" { + cmd.Dir = job.WorkingDir + } + + // if job.User != 0 && job.Group != 0 { + // fmt.Println(job.User, ":", job.Group) + // cmd.SysProcAttr = &syscall.SysProcAttr{} + // cmd.SysProcAttr.Credential = &syscall.Credential{Uid: job.User, Gid: job.Group} + // } + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + log.Printf("[ERROR] cmd: %v: %v\n", job.Command, err) + } + + }) + } + + log.Printf("%v job(s) scheduled\n", len(c.Entries())) + + // start cron + c.Start() + + // prepare chan for catching signals + done := make(chan os.Signal, 1) + signal.Notify(done, os.Interrupt, os.Kill) + + // waiting for signal to stop + <-done + + c.Stop() + + return nil +} + +func newCommand(rawCommand []string) command { + + var ( + name string + args []string + ) + + if len(rawCommand) > 1 { + name = rawCommand[0] + args = rawCommand[1:] + } else { + name = rawCommand[0] + args = []string{} + } + + return command{name: name, args: args} +} diff --git a/cronr.example.yml b/cronr.example.yml new file mode 100644 index 0000000..bb45150 --- /dev/null +++ b/cronr.example.yml @@ -0,0 +1,12 @@ + +jobs: + - cron: "0/5 * * * * *" # e.g: 0 30 * * * * WARNING: first one is seconds (instead of minutes for classic cron, https://godoc.org/github.com/robfig/cron) + command: + - sh + - -c + - /bin/echo foo >> output_1 + working_dir: tests # optional, cronr working dir by default + - cron: "0/2 * * * * *" + command: + - /bin/echo + - bar diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..374e512 --- /dev/null +++ b/glide.lock @@ -0,0 +1,10 @@ +hash: dfd7d105bde48982e85d2c1ecc276bfa3b1176993a0129183729214c2fae0d01 +updated: 2016-11-17T16:04:56.427152285+01:00 +imports: +- name: github.com/jawher/mow.cli + version: 772320464101e904cd51198160eb4d489be9cc49 +- name: github.com/robfig/cron + version: 9585fd555638e77bba25f25db5c44b41f264aeb7 +- name: gopkg.in/yaml.v2 + version: a83829b6f1293c91addabc89d0571c246397bbf4 +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..c33b13d --- /dev/null +++ b/glide.yaml @@ -0,0 +1,5 @@ +package: webup/cronr +import: +- package: github.com/robfig/cron +- package: github.com/jawher/mow.cli +- package: gopkg.in/yaml.v2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..65d8154 --- /dev/null +++ b/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os" + + cli "github.com/jawher/mow.cli" +) + +func main() { + + app := cli.App("cronr", "Executes scheduled jobs defined in a config file") + + // version + app.Version("v version", "cronr 1 (build 1)") + + // options & args + app.Spec = "-c [--verbose]" + configFile := app.StringOpt("c config-file", "", "Path of the cronr config file") + verbose := app.BoolOpt("verbose", false, "Display more informations on scheduled jobs") + + // action + app.Action = func() { + err := CronAction(*configFile, *verbose) + if err != nil { + fmt.Println(err) + cli.Exit(1) + } + } + + app.Run(os.Args) +}