github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/automation/run/run.go (about)

     1  // Package run defines metadata about transform script execution
     2  package run
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/google/uuid"
    12  	golog "github.com/ipfs/go-log"
    13  	"github.com/qri-io/dataset"
    14  	"github.com/qri-io/qri/automation/workflow"
    15  	"github.com/qri-io/qri/event"
    16  )
    17  
    18  var (
    19  	log = golog.Logger("run")
    20  	// ErrNoID indicates the run.State has no run ID
    21  	ErrNoID = fmt.Errorf("no run ID")
    22  	// ErrNoWorkflowID indicates the run.State has no workflow.ID
    23  	ErrNoWorkflowID = fmt.Errorf("no workflow ID")
    24  )
    25  
    26  // NewID creates a run identifier
    27  func NewID() string {
    28  	return uuid.New().String()
    29  }
    30  
    31  // SetIDRand sets the random reader that NewID uses as a source of random bytes
    32  // passing in nil will default to crypto.Rand. This can be used to make ID
    33  // generation deterministic for tests. eg:
    34  //    myString := "SomeRandomStringThatIsLong-SoYouCanCallItAsMuchAsNeeded..."
    35  //    run.SetIDRand(strings.NewReader(myString))
    36  //    a := NewID()
    37  //    run.SetIDRand(strings.NewReader(myString))
    38  //    b := NewID()
    39  func SetIDRand(r io.Reader) {
    40  	uuid.SetRand(r)
    41  }
    42  
    43  // Status enumerates all possible execution states of a transform script or
    44  // step within a script, in relation to the current time.
    45  // Scripts & steps that have completed are broken into categories based on exit
    46  // state
    47  type Status string
    48  
    49  const (
    50  	// RSWaiting indicates a script/step that has yet to start
    51  	RSWaiting = Status("waiting")
    52  	// RSRunning indicates a script/step is currently executing
    53  	RSRunning = Status("running")
    54  	// RSSucceeded indicates a script/step has completed without error
    55  	RSSucceeded = Status("succeeded")
    56  	// RSFailed indicates a script/step completed & exited when an unexpected error
    57  	// occured
    58  	RSFailed = Status("failed")
    59  	// RSUnchanged indicates a script completed but no changes were found
    60  	// since the last version of the script succeeded
    61  	RSUnchanged = Status("unchanged")
    62  	// RSSkipped indicates a script/step was not executed
    63  	RSSkipped = Status("skipped")
    64  )
    65  
    66  // State is a passable, cachable data structure that describes the execution of
    67  // a transform. State structs can act as a sink of transform events, collapsing
    68  // the state transition of multiple transform events into a single structure
    69  type State struct {
    70  	ID         string       `json:"id"`
    71  	WorkflowID workflow.ID  `json:"workflowID"`
    72  	Number     int          `json:"number"`
    73  	Status     Status       `json:"status"`
    74  	Message    string       `json:"message"`
    75  	StartTime  *time.Time   `json:"startTime"`
    76  	StopTime   *time.Time   `json:"stopTime"`
    77  	Duration   int64        `json:"duration"`
    78  	Steps      []*StepState `json:"steps"`
    79  }
    80  
    81  // NewState returns a new *State with the given runID
    82  func NewState(runID string) *State {
    83  	return &State{ID: runID}
    84  }
    85  
    86  // Validate errors if the run is not valid
    87  func (rs *State) Validate() error {
    88  	if rs.ID == "" {
    89  		return ErrNoID
    90  	}
    91  	if rs.WorkflowID.String() == "" {
    92  		return ErrNoWorkflowID
    93  	}
    94  	return nil
    95  }
    96  
    97  // Copy returns a shallow copy of the receiver
    98  func (rs *State) Copy() *State {
    99  	if rs == nil {
   100  		return nil
   101  	}
   102  	run := &State{
   103  		ID:         rs.ID,
   104  		WorkflowID: rs.WorkflowID,
   105  		Number:     rs.Number,
   106  		Status:     rs.Status,
   107  		Message:    rs.Message,
   108  		StartTime:  rs.StartTime,
   109  		StopTime:   rs.StopTime,
   110  		Duration:   rs.Duration,
   111  		Steps:      rs.Steps,
   112  	}
   113  	return run
   114  }
   115  
   116  // AddTransformEvent alters state based on a given event
   117  func (rs *State) AddTransformEvent(e event.Event) error {
   118  	if rs.ID != e.SessionID {
   119  		// silently ignore session ID mismatch
   120  		return nil
   121  	}
   122  
   123  	switch e.Type {
   124  	case event.ETTransformStart:
   125  		rs.Status = RSRunning
   126  		rs.StartTime = toTimePointer(e.Timestamp)
   127  		return nil
   128  	case event.ETTransformStop:
   129  		rs.StopTime = toTimePointer(e.Timestamp)
   130  		if tl, ok := e.Payload.(event.TransformLifecycle); ok {
   131  			rs.Status = Status(tl.Status)
   132  		}
   133  		if rs.StartTime != nil && rs.StopTime != nil {
   134  			rs.Duration = int64(rs.StopTime.Sub(*rs.StartTime))
   135  		}
   136  		return nil
   137  	case event.ETTransformStepStart:
   138  		s, err := NewStepStateFromEvent(e)
   139  		if err != nil {
   140  			return err
   141  		}
   142  		s.Status = RSRunning
   143  		s.StartTime = toTimePointer(e.Timestamp)
   144  		rs.Steps = append(rs.Steps, s)
   145  		return nil
   146  	case event.ETTransformStepStop:
   147  		step, err := rs.lastStep()
   148  		if err != nil {
   149  			return err
   150  		}
   151  		step.StopTime = toTimePointer(e.Timestamp)
   152  		if tsl, ok := e.Payload.(event.TransformStepLifecycle); ok {
   153  			step.Status = Status(tsl.Status)
   154  		} else {
   155  			step.Status = RSFailed
   156  		}
   157  		if step.StartTime != nil && step.StopTime != nil {
   158  			step.Duration = int64(step.StopTime.Sub(*step.StartTime))
   159  		}
   160  		return nil
   161  	case event.ETTransformStepSkip:
   162  		s, err := NewStepStateFromEvent(e)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		s.Status = RSSkipped
   167  		rs.Steps = append(rs.Steps, s)
   168  		return nil
   169  	case event.ETTransformPrint,
   170  		event.ETTransformError,
   171  		event.ETTransformDatasetPreview:
   172  		return rs.appendStepOutputLog(e)
   173  	case event.ETTransformCanceled:
   174  		return nil
   175  	}
   176  	return fmt.Errorf("unexpected event type: %q", e.Type)
   177  }
   178  
   179  func (rs *State) lastStep() (*StepState, error) {
   180  	if len(rs.Steps) > 0 {
   181  		return rs.Steps[len(rs.Steps)-1], nil
   182  	}
   183  	return nil, fmt.Errorf("expected step to exist")
   184  }
   185  
   186  func (rs *State) appendStepOutputLog(e event.Event) error {
   187  	step, err := rs.lastStep()
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	step.Output = append(step.Output, e)
   193  	return nil
   194  }
   195  
   196  // StepState describes the execution of a transform step
   197  type StepState struct {
   198  	Name      string        `json:"name"`
   199  	Category  string        `json:"category"`
   200  	Status    Status        `json:"status"`
   201  	StartTime *time.Time    `json:"startTime"`
   202  	StopTime  *time.Time    `json:"stopTime"`
   203  	Duration  int64         `json:"duration"`
   204  	Output    []event.Event `json:"output"`
   205  }
   206  
   207  // Copy returns a shallow copy of the receiver
   208  func (ss *StepState) Copy() *StepState {
   209  	return &StepState{
   210  		Name:      ss.Name,
   211  		Category:  ss.Category,
   212  		Status:    ss.Status,
   213  		StartTime: ss.StartTime,
   214  		StopTime:  ss.StopTime,
   215  		Duration:  ss.Duration,
   216  		Output:    ss.Output,
   217  	}
   218  }
   219  
   220  // NewStepStateFromEvent constructs StepState from an event
   221  func NewStepStateFromEvent(e event.Event) (*StepState, error) {
   222  	if tsl, ok := e.Payload.(event.TransformStepLifecycle); ok {
   223  		return &StepState{
   224  			Name:     tsl.Name,
   225  			Category: tsl.Category,
   226  			Status:   Status(tsl.Status),
   227  		}, nil
   228  	}
   229  	return nil, fmt.Errorf("run step event data must be a transform step lifecycle struct")
   230  }
   231  
   232  type _stepState struct {
   233  	Name      string     `json:"name"`
   234  	Category  string     `json:"category"`
   235  	Status    Status     `json:"status"`
   236  	StartTime *time.Time `json:"startTime"`
   237  	StopTime  *time.Time `json:"stopTime"`
   238  	Duration  int64      `json:"duration"`
   239  	Output    []_event   `json:"output"`
   240  }
   241  
   242  type _event struct {
   243  	Type      event.Type      `json:"type"`
   244  	Timestamp int64           `json:"timestamp"`
   245  	SessionID string          `json:"sessionID"`
   246  	Payload   json.RawMessage `json:"payload"`
   247  }
   248  
   249  // UnmarshalJSON satisfies the json.Unmarshaller interface
   250  func (ss *StepState) UnmarshalJSON(data []byte) error {
   251  	tmpSS := &StepState{}
   252  	rs := &_stepState{}
   253  	if err := json.Unmarshal(data, rs); err != nil {
   254  		return err
   255  	}
   256  	tmpSS.Name = rs.Name
   257  	tmpSS.Category = rs.Category
   258  	tmpSS.Status = rs.Status
   259  	tmpSS.StartTime = rs.StartTime
   260  	tmpSS.StopTime = rs.StopTime
   261  	tmpSS.Duration = rs.Duration
   262  	for _, re := range rs.Output {
   263  		e := event.Event{
   264  			Type:      re.Type,
   265  			Timestamp: re.Timestamp,
   266  			SessionID: re.SessionID,
   267  		}
   268  		switch e.Type {
   269  		case event.ETTransformStart, event.ETTransformStop:
   270  			p := event.TransformLifecycle{}
   271  			if err := json.Unmarshal(re.Payload, &p); err != nil {
   272  				return err
   273  			}
   274  			e.Payload = p
   275  		case event.ETTransformStepStart, event.ETTransformStepStop, event.ETTransformStepSkip:
   276  			p := event.TransformStepLifecycle{}
   277  			if err := json.Unmarshal(re.Payload, &p); err != nil {
   278  				return err
   279  			}
   280  			e.Payload = p
   281  		case event.ETTransformPrint, event.ETTransformError:
   282  			p := event.TransformMessage{}
   283  			if err := json.Unmarshal(re.Payload, &p); err != nil {
   284  				return err
   285  			}
   286  			e.Payload = p
   287  		case event.ETTransformDatasetPreview:
   288  			e.Payload = &dataset.Dataset{}
   289  			if err := json.Unmarshal(re.Payload, e.Payload); err != nil {
   290  				return err
   291  			}
   292  		default:
   293  			if err := json.Unmarshal(re.Payload, e.Payload); err != nil {
   294  				return err
   295  			}
   296  		}
   297  		tmpSS.Output = append(tmpSS.Output, e)
   298  	}
   299  	*ss = *tmpSS
   300  	return nil
   301  }
   302  
   303  func toTimePointer(unixnano int64) *time.Time {
   304  	t := time.Unix(0, unixnano)
   305  	return &t
   306  }
   307  
   308  // Set is a collection of run.States that implements the sort.Interface,
   309  // sorting a list of run.State in reverse-chronological order
   310  type Set struct {
   311  	set []*State
   312  }
   313  
   314  // NewSet constructs a run.State set
   315  func NewSet() *Set {
   316  	return &Set{}
   317  }
   318  
   319  // Len is part of the sort.Interface
   320  func (s Set) Len() int { return len(s.set) }
   321  
   322  // Less is part of the `sort.Interface`
   323  func (s Set) Less(i, j int) bool {
   324  	return lessNilTime(s.set[i].StartTime, s.set[j].StartTime)
   325  }
   326  
   327  // Swap is part of the `sort.Interface`
   328  func (s Set) Swap(i, j int) { s.set[i], s.set[j] = s.set[j], s.set[i] }
   329  
   330  // Add adds a run.State to a Set
   331  func (s *Set) Add(j *State) {
   332  	if s == nil {
   333  		*s = Set{set: []*State{j}}
   334  		return
   335  	}
   336  
   337  	for i, run := range s.set {
   338  		if run.ID == j.ID {
   339  			s.set[i] = j
   340  			return
   341  		}
   342  	}
   343  	s.set = append(s.set, j)
   344  	sort.Sort(s)
   345  }
   346  
   347  // Remove removes a run.State from a Set
   348  func (s *Set) Remove(id string) (removed bool) {
   349  	for i, run := range s.set {
   350  		if run.ID == id {
   351  			if i+1 == len(s.set) {
   352  				s.set = s.set[:i]
   353  				return true
   354  			}
   355  
   356  			s.set = append(s.set[:i], s.set[i+1:]...)
   357  			return true
   358  		}
   359  	}
   360  	return false
   361  }
   362  
   363  // Slice returns a slice of run.States from position `start` to position `end`
   364  func (s *Set) Slice(start, end int) []*State {
   365  	if start < 0 || end < 0 {
   366  		return []*State{}
   367  	}
   368  	if end > s.Len() {
   369  		end = s.Len()
   370  	}
   371  	return s.set[start:end]
   372  }
   373  
   374  // MarshalJSON satisfies the `json.Marshaller` interface
   375  func (s Set) MarshalJSON() ([]byte, error) {
   376  	return json.Marshal(s.set)
   377  }
   378  
   379  // UnmarshalJSON satisfies the `json.Unmarshaller` interface
   380  func (s *Set) UnmarshalJSON(data []byte) error {
   381  	set := []*State{}
   382  	if err := json.Unmarshal(data, &set); err != nil {
   383  		return err
   384  	}
   385  	s.set = set
   386  	return nil
   387  }
   388  
   389  func lessNilTime(a, b *time.Time) bool {
   390  	if a == nil && b != nil {
   391  		return true
   392  	} else if a != nil && b == nil {
   393  		return false
   394  	} else if a == nil && b == nil {
   395  		return false
   396  	}
   397  	return a.After(*b)
   398  }