package ticket import ( "fmt" "sort" "time" "github.com/pkg/errors" "github.com/robfig/cron" "github.com/strongdm/comply/internal/config" "github.com/strongdm/comply/internal/model" ) func byProcedureByTime(tickets []*model.Ticket) map[string][]*model.Ticket { result := make(map[string][]*model.Ticket) for _, t := range tickets { procedureID := t.ProcedureID() if procedureID == "" { // missing procedure metadata; skip continue } list, ok := result[procedureID] if !ok { list = make([]*model.Ticket, 0) } list = append(list, t) sort.Slice(list, func(i, j int) bool { if list[i].CreatedAt == nil || list[j].CreatedAt == nil { return false } return list[i].CreatedAt.Before(*list[j].CreatedAt) }) result[procedureID] = list } return result } func TriggerScheduled() error { rawTickets, err := model.ReadTickets() if err != nil { return err } tickets := byProcedureByTime(rawTickets) procedures, err := model.ReadProcedures() if err != nil { return err } for _, procedure := range procedures { if procedure.Cron == "" { continue } procedureID := procedure.ID schedule, err := cron.Parse(procedure.Cron) if err != nil { continue } ticketsForProc, ok := tickets[procedureID] if ok { // find most recent one mostRecent := ticketsForProc[len(ticketsForProc)-1] if mostRecent.CreatedAt == nil { continue } // would another have triggered since? nextTrigger := schedule.Next(*mostRecent.CreatedAt).UTC() if nextTrigger.After(time.Now().UTC()) { // in the future, nothing to do continue } err = trigger(procedure) if err != nil { return err } } else { // don't go back further than 13 months tooOld := time.Now().Add(-1 * time.Hour * 24 * (365 + 30)) // search back one day until triggers triggeredAt := time.Now().Add(-24 * time.Hour).UTC() SEARCH: for { if triggeredAt.Before(tooOld) { break SEARCH } candidate := schedule.Next(triggeredAt) // in the future? not far eonugh back yet. if candidate.After(time.Now().UTC()) { triggeredAt = triggeredAt.Add(-24 * time.Hour) continue } // is in the past? then trigger. err = trigger(procedure) if err != nil { return err } break SEARCH } } } return nil } func trigger(procedure *model.Procedure) error { fmt.Printf("triggering procedure %s (cron expression: %s)\n", procedure.Name, procedure.Cron) ts, err := config.Config().TicketSystem() if err != nil { return errors.Wrap(err, "error in ticket system configuration") } tp := model.GetPlugin(model.TicketSystem(ts)) err = tp.Create(&model.Ticket{ Name: procedure.Name, Body: fmt.Sprintf("%s\n\n\n---\nProcedure-ID: %s", procedure.Body, procedure.ID), }, []string{"comply", "comply-procedure"}) return err }