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  }