func (r Run) start(id int) { for { cexpr, err := cronexpr.Parse(r.Cron) if err != nil { panic(err) } // sleep until the next run is scheduled to happen nrun := cexpr.Next(time.Now()) waitduration := nrun.Sub(time.Now()) log.Printf("[info] run %d will start at %v (in %v)", id, nrun, waitduration) time.Sleep(waitduration) notifchan := make(chan Notification) done := make(chan bool) go processNotifications(notifchan, done) var wg sync.WaitGroup for _, target := range r.Targets { log.Printf("[info] run %d starting scan of target %q", id, target) id, err := r.scan(target) debugprint("got scan id %d", id) if err != nil { log.Printf("[error] failed to launch against %q: %v", target, err) continue } wg.Add(1) go r.evaluate(id, notifchan, &wg) } wg.Wait() close(notifchan) <-done } }
// Start a scheduler entity. This is normally run in it's own go-routine // and will wait until the configured time to execute. func (e *entity) start() { xr := func(s string, args ...interface{}) { mlog(s, args...) e.deadChan <- true } e.abortRun = make(chan bool, 1) for { cexpr, err := cronexpr.Parse(e.cfg.Configuration.Schedule) if err != nil { xr("%v: bad cron expression: %v", e.name, err) return } nrun := cexpr.Next(time.Now()) waitduration := nrun.Sub(time.Now()) mlog("%v: will run at %v (in %v)", e.name, nrun, waitduration) select { case <-e.abortRun: mlog("%v: asked to terminate, stopping", e.name) return case <-time.After(waitduration): } mlog("%v: running", e.name) err = e.launchAction() if err != nil { xr("%v: %v", e.name, err) return } } mlog("%v: job exiting", e.name) e.deadChan <- true }
func getVersions(request models.CheckRequest) (models.CheckResponse, error) { expr, err := cronexpr.Parse(request.Source.Expression) if err != nil { return nil, err } versions := []models.Version{} shouldFire := false loc, err := time.LoadLocation(request.Source.Location) if err != nil { return nil, err } previouslyFiredAt := request.Version.Time if previouslyFiredAt.IsZero() { previouslyFiredAt = time.Now().In(loc).Add(-1 * time.Hour) } shouldFireAt := expr.Next(previouslyFiredAt) if previouslyFiredAt.Before(shouldFireAt) && time.Now().After(shouldFireAt) { shouldFire = true } if shouldFire { versions = append(versions, models.Version{ Time: time.Now().In(loc), }) } return versions, nil }
// Parse returns a new Ticker containing a channel that will send the time // with a period specified by the spec argument. Stop the ticker to release // associated resources. func Parse(spec string) (*Ticker, error) { expr, err := cronexpr.Parse(spec) if err != nil { return nil, err } tickerC := make(chan time.Time, 1) ticker := &Ticker{ C: tickerC, done: make(chan struct{}, 1), expr: expr, } go func() { for { next := ticker.expr.Next(c.Now()) select { case <-time.After(next.Sub(c.Now())): tickerC <- c.Now() case <-ticker.done: break } } }() return ticker, nil }
// NewConfig parses the relevant environment variables, applying defaults if unspecified. func NewConfig() (Config, error) { c := Config{} c.ErrInfo = os.Getenv("ERR_INFO") == "true" ibGw := os.Getenv("IB_GW") if ibGw == "" { ibGw = "127.0.0.1:4002" } c.IbGws = strings.Split(ibGw, ",") ibClientId := os.Getenv("IB_CID") if ibClientId == "" { ibClientId = "5555" } var err error c.IbClientId, err = strconv.Atoi(ibClientId) if err != nil { return c, fmt.Errorf("IB_CID '%s' not an integer") } c.DbUrl = os.Getenv("DB_URL") if c.DbUrl == "" { c.DbUrl = "postgres://ibc_dev@localhost/ibc_dev?sslmode=disable" } if !strings.HasPrefix(c.DbUrl, "postgres://") { return c, fmt.Errorf("DB_URL '%s' did not being with postgres://", c.DbUrl) } portString := os.Getenv("PORT") if portString == "" { portString = "3000" } c.Port, err = strconv.Atoi(portString) if err != nil { return c, fmt.Errorf("PORT '%s' not an integer") } c.Host = os.Getenv("HOST") if c.Host == "" { c.Host = "localhost" } acctRefresh := os.Getenv("ACCT_REF") if acctRefresh == "" { acctRefresh = "@hourly" } c.AccountRefresh, err = cronexpr.Parse(acctRefresh) if err != nil { return c, err } return c, nil }
// New returns a new cron expression. // We use github.com/gorhill/cronexpr for parsing cron express inside. func New(expr string) (schedule.Expression, error) { cronexpr, err := cronexpr.Parse(expr) if err != nil { return nil, err } return &expression{ Expression: cronexpr, }, nil }
func (e *entityConfig) validate() error { if e.Configuration.Schedule == "" { return fmt.Errorf("missing schedule") } _, err := cronexpr.Parse(e.Configuration.Schedule) if err != nil { return fmt.Errorf("bad cron expression: %v", err) } return nil }
func (c *Channel) IsTimeToNotifyValid() bool { if c.TimeToNotify == ChannelSendImmediately { return true } _, err := cronexpr.Parse(c.TimeToNotify) if err != nil { logger.WithField("time_to_notify", c.TimeToNotify).Warn("failed to parse time to notify") } return err == nil }
func newCronTicker(cronExpr string) (*cronTicker, error) { expr, err := cronexpr.Parse(cronExpr) if err != nil { return nil, err } return &cronTicker{ expr: expr, ticker: make(chan time.Time), closing: make(chan struct{}), }, nil }
func TestExpressions(t *testing.T) { for _, test := range crontests { for _, times := range test.times { from, _ := time.Parse("2006-01-02 15:04:05", times.from) expr, err := cronexpr.Parse(test.expr) if err != nil { t.Errorf(`cronexpr.Parse("%s") returned "%s"`, test.expr, err.Error()) } next := expr.Next(from) nextstr := next.Format(test.layout) if nextstr != times.next { t.Errorf(`("%s").Next("%s") = "%s", got "%s"`, test.expr, times.from, times.next, nextstr) } } } }
func (e *Emissary) ShouldRun(t time.Time) (bool, error) { t = t.Truncate(time.Minute) compare := t.Add(-1 * time.Nanosecond) for _, s := range e.Schedules { parsed, err := cronexpr.Parse(s) if err != nil { return false, err } next := parsed.Next(compare) if next == t { return true, nil } } return false, nil }
// Init initializes some internal data inside the Alert, and must be called // after the Alert is unmarshaled from yaml (or otherwise created) func (a *Alert) Init() error { var err error a.searchIndexTPL, err = templatizeHelper(a.SearchIndex, err) a.searchTypeTPL, err = templatizeHelper(a.SearchType, err) a.searchTPL, err = templatizeHelper(&a.Search, err) if err != nil { return err } cron, err := cronexpr.Parse(a.Interval) if err != nil { return fmt.Errorf("parsing interval: %s", err) } a.cron = cron return nil }
func main() { var err error flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s - Manage users in various SaaS based on a LDAP source\n"+ "Usage: %s -c config.yaml\n", os.Args[0], os.Args[0]) flag.PrintDefaults() } flag.Parse() if *showVersion { fmt.Println(version) os.Exit(0) } // load the local configuration file conf, err := loadConf(*config) if err != nil { log.Fatalf("failed to load configuration: %v", err) } // just run once if *once || conf.Cron == "disabled" { run(conf) return } // infinite loop, wake up at the cron period for { cexpr, err := cronexpr.Parse(conf.Cron) if err != nil { log.Fatalf("failed to parse cron string %q: %v", conf.Cron, err) } // sleep until the next run is scheduled to happen nrun := cexpr.Next(time.Now()) waitduration := nrun.Sub(time.Now()) log.Printf("[info] next run will start at %v (in %v)", nrun, waitduration) time.Sleep(waitduration) run(conf) } }
// goroutine run this function periodly to check if this job is needed to auto start func (j Job) NeedAutoStart() bool { if len(j.Schedule) > 0 { expr, err := cronexpr.Parse(j.Schedule) if err != nil { log.Debug(err.Error()) return false } last_run_ts := j.GetLastRunTime() if last_run_ts >= 0 { last_run_time := time.Unix(last_run_ts, 0) next_time := expr.Next(last_run_time) log.Debug("next_time", next_time) // > 20s if time.Now().Unix()-next_time.Unix() > 20 { return true } } } return false }
// NewTaskRouteHandler creates a new task with the POST'd data func NewTaskRouteHandler(ren *render.Render, dispatcher *dispatcher.Dispatcher) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var nt database.Task decoder := json.NewDecoder(r.Body) err := decoder.Decode(&nt) if err != nil { ren.JSON(w, 400, map[string]error{"error": err}) return } defer r.Body.Close() switch { case nt.Name == "": ren.JSON(w, 400, map[string]error{"error": ErrMissingNameField}) return case nt.CMD == "": ren.JSON(w, 400, map[string]error{"error": ErrMissingCMDField}) return case nt.Args == "": ren.JSON(w, 400, map[string]error{"error": ErrMissingArgsField}) return case nt.Schedule == "": ren.JSON(w, 400, map[string]error{"error": ErrMissingScheduleField}) return } // validate that the entered cron string is valid. Error if not. _, err = cronexpr.Parse(nt.Schedule) if err != nil { ren.JSON(w, 400, map[string]error{"error": err}) return } dispatcher.SenderChan <- &nt dispatcher.TaskProcChan <- nt ren.JSON(w, http.StatusOK, map[string]database.Task{"task": nt}) } }
// must check crontab valid first func (cj *CronJob) IsValid() bool { log.Info("check crontab if avalid :", cj.Id, cj.Name) if _, err := cronexpr.Parse(cj.Schedule); err != nil { log.Warning("cron job parse crontab format failed: ", err) return false } if cj.Runner == "root" { log.Warning("cron job must run under non-root user, current is: ", cj.Runner) return false } now := time.Now().Unix() if cj.StartAt == 0 && cj.EndAt == 0 { return !cj.Disabled } if cj.StartAt == 1 && cj.EndAt == 2 { // delete job by user return false } if cj.StartAt > now && (cj.EndAt > cj.StartAt || cj.EndAt == 0) { log.Infof("crontab %d %s unstart, start: %d, end: %d", cj.Id, cj.Name, cj.StartAt, cj.EndAt) return false } if cj.StartAt < cj.EndAt && cj.EndAt < now && !cj.Disabled { log.Infof("crontab %d %s exprie, start: %d, end: %d", cj.Id, cj.Name, cj.StartAt, cj.EndAt) return false } if cj.StartAt < now && (cj.EndAt > now || cj.EndAt == 0) { log.Infof("crontab %d %s start, start: %d, end: %d", cj.Id, cj.Name, cj.StartAt, cj.EndAt) return !cj.Disabled } return !cj.Disabled }
// check crontab need func (cj *CronJob) NeedSchedule() bool { expression, err := cronexpr.Parse(cj.Schedule) if err != nil { log.Warning("crontab parse failed: ", cj.Id, cj.Name) cj.Disabled = true } cj.expression = expression if cj.LastExecAt == 0 { cj.LastExecAt = cj.CreateAt } last_run_time := time.Unix(cj.LastExecAt, 0) nt := cj.expression.Next(last_run_time) // log.Info("cron next run is: ", cj.Id, cj.Name, nt) if time.Now().Unix()-nt.Unix() > 20 { // log.Info("needschedule true: ", cj.Id, cj.Name, nt) return true } // log.Info("needschedule false: ", cj.Id, cj.Name, nt) return false }
// Add creates a new cron job. // // Use the ID to Rem() that job later if you want. F is the work to // be performed. The first argument is the scheduled time for that // invocation, and the second argument is true if the job is a // one-shot job. // // The schedule syntax can have three forms: // // 1. A cron schedule string (supposedly in syntax at // https://en.wikipedia.org/wiki/Cron). // // 2. "!TIME", where TIME is according to RFC3339. // // 3. "+DURATION", where DURATION is a Go Duration // (http://golang.org/pkg/time/#ParseDuration). Examples: "5s" means // "5 seconds", "2m" means "2 minutes", and "1h" means "1 hour". func (c *Cron) Add(ctx *core.Context, id string, schedule string, f func(t time.Time) error) error { core.Log(core.INFO|CRON, ctx, "Cron.Add", "id", id, "schedule", schedule, "name", c.Name) job := CronJob{} job.Id = id job.Schedule = schedule job.Fn = f if core.OneShotSchedule(schedule) { switch schedule[0:1] { case "!": t, err := time.Parse(time.RFC3339, schedule[1:]) if err != nil { return err } job.Next = t case "+": d, err := time.ParseDuration(schedule[1:]) if err != nil { return err } job.Next = time.Now().Add(d) default: return fmt.Errorf("bad one-shot schedule '%s'", schedule) } } else { expr, err := cronexpr.Parse(schedule) if err != nil { return err } job.Expression = expr } future := job.Next.Sub(time.Now()) core.Log(core.DEBUG|CRON, ctx, "Cron.Add", "id", id, "in", future) return c.schedule(ctx, &job, true) }
func (p Parser) ParseLine(line string) JobList { var jobList JobList = []Job{} re := regexp.MustCompile("^([^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+) +(.+)$") if !re.MatchString(line) { return jobList } matched := re.FindStringSubmatch(line) time := matched[1] script := matched[2] expr, err := cronexpr.Parse(time) if err != nil { return jobList } for nextTime := expr.Next(p.FromTime); !nextTime.After(p.ToTime); nextTime = expr.Next(nextTime) { job := NewJob(nextTime, script) jobList = append(jobList, *job) } return jobList }
func main() { var err error flag.Usage = usage flag.StringVar(&inTimeStr, "t", "", `whole or partial RFC3339 time value (i.e. "2006-01-02T15:04:05Z07:00") against which the cron expression is evaluated, now if not present`) flag.UintVar(&outTimeCount, "n", 1, `number of resulting time values to output`) flag.StringVar(&outTimeLayout, "l", "Mon, 02 Jan 2006 15:04:05 MST", `Go-compliant time layout to use for outputting time value(s), see <http://golang.org/pkg/time/#pkg-constants>`) flag.Parse() cronStr := flag.Arg(0) if len(cronStr) == 0 { flag.Usage() return } inTime := time.Now() inTimeLayout := "" timeStrLen := len(inTimeStr) if timeStrLen == 2 { inTimeLayout = "06" } else if timeStrLen >= 4 { inTimeLayout += "2006" if timeStrLen >= 7 { inTimeLayout += "-01" if timeStrLen >= 10 { inTimeLayout += "-02" if timeStrLen >= 13 { inTimeLayout += "T15" if timeStrLen >= 16 { inTimeLayout += ":04" if timeStrLen >= 19 { inTimeLayout += ":05" if timeStrLen >= 20 { inTimeLayout += "Z07:00" } } } } } } } if len(inTimeLayout) > 0 { // default to local time zone if timeStrLen < 20 { inTime, err = time.ParseInLocation(inTimeLayout, inTimeStr, time.Local) } else { inTime, err = time.Parse(inTimeLayout, inTimeStr) } if err != nil { fmt.Fprintf(os.Stderr, "# error: unparseable time value: \"%s\"\n", inTimeStr) os.Exit(1) } } expr, err := cronexpr.Parse(cronStr) if err != nil { fmt.Fprintf(os.Stderr, "# %s: %s\n", os.Args[0], err) os.Exit(1) } // Anything on the output which starts with '#' can be ignored if the caller // is interested only in the time values. There is only one time // value per line, and they are always in chronological ascending order. fmt.Printf("# \"%s\" + \"%s\" =\n", cronStr, inTime.Format(time.RFC3339)) if outTimeCount < 1 { outTimeCount = 1 } outTimes := expr.NextN(inTime, outTimeCount) for _, outTime := range outTimes { fmt.Println(outTime.Format(outTimeLayout)) } }
func Parse(in string) (*Schedule, error) { expr, err := cronexpr.Parse(in) return (*Schedule)(expr), err }
func main() { var ( err error conf conf cli mozldap.Client ) flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s - Manage users in various SaaS based on a LDAP source\n"+ "Usage: %s -c config.yaml\n", os.Args[0], os.Args[0]) flag.PrintDefaults() } flag.Parse() if *drynotif { *dryrun = true } // safeguard, remove in prod *dryrun = true *drynotif = true // load the local configuration file fd, err := ioutil.ReadFile(*config) if err != nil { log.Fatal(err) } err = yaml.Unmarshal(fd, &conf) if err != nil { log.Fatalf("error: %v", err) } // infinite loop, wake up at the cron period for { if !*once { cexpr, err := cronexpr.Parse(conf.Cron) if err != nil { log.Fatal("failed to parse cron string %q: %v", conf.Cron, err) } // sleep until the next run is scheduled to happen nrun := cexpr.Next(time.Now()) waitduration := nrun.Sub(time.Now()) log.Printf("[info] next run will start at %v (in %v)", nrun, waitduration) time.Sleep(waitduration) } // instanciate an ldap client if conf.Ldap.TLSCert != "" && conf.Ldap.TLSKey != "" { cli, err = mozldap.NewTLSClient( conf.Ldap.Uri, conf.Ldap.Username, conf.Ldap.Password, conf.Ldap.TLSCert, conf.Ldap.TLSKey, conf.Ldap.CACert, &tls.Config{InsecureSkipVerify: conf.Ldap.Insecure}) } else { cli, err = mozldap.NewClient( conf.Ldap.Uri, conf.Ldap.Username, conf.Ldap.Password, conf.Ldap.CACert, &tls.Config{InsecureSkipVerify: conf.Ldap.Insecure}, conf.Ldap.Starttls) } if err != nil { log.Fatal(err) } defer cli.Close() log.Printf("connected %s on %s:%d, tls:%v starttls:%v\n", cli.BaseDN, cli.Host, cli.Port, cli.UseTLS, cli.UseStartTLS) conf.Ldap.cli = cli // Channel where modules publish their notifications // which are aggregated and sent by the main program notifchan := make(chan modules.Notification) notifdone := make(chan bool) go processNotifications(conf, notifchan, notifdone) // store the value of dryrun and the ldap client // in the configuration of each module for i := range conf.Modules { conf.Modules[i].DryRun = *dryrun conf.Modules[i].LdapCli = cli conf.Modules[i].Notify.Channel = notifchan } // run each module in the order it appears in the configuration for _, modconf := range conf.Modules { if *runmod != "all" && *runmod != modconf.Name { continue } if _, ok := modules.Available[modconf.Name]; !ok { log.Printf("[warning] %s module not registered, skipping it", modconf.Name) continue } log.Println("[info] invoking module", modconf.Name) run := modules.Available[modconf.Name].NewRun(modconf) err = run.Run() if err != nil { log.Printf("[error] %s module failed with error: %v", modconf.Name, err) } } // Modules are done, close the notification channel to tell the goroutine // that it can aggregate and send them, and wait for notifdone to come back close(notifchan) <-notifdone if *once { break } } }