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 }