github.com/leg100/ots@v0.0.7-0.20210919080622-034055ced4bd/agent/spooler.go (about) 1 package agent 2 3 import ( 4 "context" 5 6 "github.com/go-logr/logr" 7 "github.com/leg100/go-tfe" 8 "github.com/leg100/ots" 9 ) 10 11 var _ Spooler = (*SpoolerDaemon)(nil) 12 13 // Spooler is a daemon from which jobs can be retrieved 14 type Spooler interface { 15 // Start the daemon 16 Start(context.Context) 17 18 // GetJob receives spooled job 19 GetJob() <-chan ots.Job 20 21 // GetCancelation receives cancelation request for a job 22 GetCancelation() <-chan ots.Job 23 } 24 25 // SpoolerDaemon implements Spooler, receiving runs with either a queued plan or 26 // apply, and converting them into spooled jobs. 27 type SpoolerDaemon struct { 28 // Queue of queued jobs 29 queue chan ots.Job 30 31 // Queue of cancelation requests 32 cancelations chan ots.Job 33 34 // EventService allows subscribing to stream of events 35 ots.EventService 36 37 // Logger for logging various events 38 logr.Logger 39 } 40 41 type RunLister interface { 42 List(ots.RunListOptions) (*ots.RunList, error) 43 } 44 45 const ( 46 // SpoolerCapacity is the max number of queued runs the spooler can store 47 SpoolerCapacity = 100 48 ) 49 50 var ( 51 // QueuedStatuses are the list of run statuses that indicate it is in a 52 // queued state 53 QueuedStatuses = []tfe.RunStatus{tfe.RunPlanQueued, tfe.RunApplyQueued} 54 ) 55 56 // NewSpooler is a constructor for a Spooler pre-populated with queued runs 57 func NewSpooler(rl RunLister, es ots.EventService, logger logr.Logger) (*SpoolerDaemon, error) { 58 // TODO: order runs by created_at date 59 runs, err := rl.List(ots.RunListOptions{Statuses: QueuedStatuses}) 60 if err != nil { 61 return nil, err 62 } 63 64 // Populate queue 65 queue := make(chan ots.Job, SpoolerCapacity) 66 for _, r := range runs.Items { 67 queue <- r 68 } 69 70 return &SpoolerDaemon{ 71 queue: queue, 72 cancelations: make(chan ots.Job, SpoolerCapacity), 73 EventService: es, 74 Logger: logger, 75 }, nil 76 } 77 78 // Start starts the spooler 79 func (s *SpoolerDaemon) Start(ctx context.Context) { 80 sub := s.Subscribe(DefaultID) 81 defer sub.Close() 82 83 for { 84 select { 85 case <-ctx.Done(): 86 return 87 case event := <-sub.C(): 88 s.handleEvent(event) 89 } 90 } 91 } 92 93 // GetJob returns a channel of queued jobs 94 func (s *SpoolerDaemon) GetJob() <-chan ots.Job { 95 return s.queue 96 } 97 98 // GetCancelation returns a channel of cancelation requests 99 func (s *SpoolerDaemon) GetCancelation() <-chan ots.Job { 100 return s.cancelations 101 } 102 103 func (s *SpoolerDaemon) handleEvent(ev ots.Event) { 104 switch obj := ev.Payload.(type) { 105 case *ots.Run: 106 s.Info("run event received", "run", obj.ID, "type", ev.Type, "status", obj.Status) 107 108 switch ev.Type { 109 case ots.PlanQueued, ots.ApplyQueued: 110 s.queue <- obj 111 case ots.RunCanceled: 112 s.cancelations <- obj 113 } 114 } 115 }