github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/automation/run/store.go (about) 1 package run 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "sort" 9 "sync" 10 11 "github.com/qri-io/qri/automation/workflow" 12 "github.com/qri-io/qri/base/params" 13 "github.com/qri-io/qri/event" 14 "github.com/qri-io/qri/profile" 15 ) 16 17 var ( 18 // ErrNotFound indicates that the run.State was not found in the Store 19 ErrNotFound = fmt.Errorf("run not found") 20 // ErrUnknownWorkflowID indicates that the given workflow.ID has no 21 // associated run.State in the Store 22 ErrUnknownWorkflowID = fmt.Errorf("unknown workflow ID") 23 ) 24 25 // Store stores and updates run.States. It is also responsible for tracking the 26 // number of times a workflow has run, as well as listening for transform 27 // events and updating the associated the run.State accordingly 28 type Store interface { 29 // Create adds a new run State to the Store 30 Create(ctx context.Context, r *State) (*State, error) 31 // Put puts a run State with an existing run ID into the Store 32 Put(ctx context.Context, r *State) (*State, error) 33 // Get gets the associated run.State 34 Get(ctx context.Context, id string) (*State, error) 35 // Count returns the number of runs for a given workflow.ID 36 Count(ctx context.Context, wid workflow.ID) (int, error) 37 // List lists all the runs associated with the workflow.ID in reverse 38 // chronological order 39 List(ctx context.Context, wid workflow.ID, lp params.List) ([]*State, error) 40 // GetLatest returns the most recent run associated with the workflow id 41 GetLatest(ctx context.Context, wid workflow.ID) (*State, error) 42 // GetStatus returns the status of the latest run based on the 43 // workflow.ID 44 GetStatus(ctx context.Context, wid workflow.ID) (Status, error) 45 // ListByStatus returns a list of run.State entries with a given status 46 // looking only at the most recent run of each Workflow 47 ListByStatus(ctx context.Context, owner profile.ID, s Status, lp params.List) ([]*State, error) 48 // Shutdown closes the store 49 Shutdown() error 50 } 51 52 // EventAdder is an extension interface that optimizes writing an event to a run 53 // Result should equal to calling: 54 // run := store.Get(id) 55 // run.AddTransformEvent(e) 56 // store.Put(run) 57 type EventAdder interface { 58 Store 59 // AddEvent writes an event to the store, attaching it to an existing stored 60 // run state 61 AddEvent(id string, e event.Event) error 62 } 63 64 // MemStore is an in memory representation of a Store 65 type MemStore struct { 66 mu sync.Mutex 67 workflows map[workflow.ID]*workflowMeta 68 runs map[string]*State 69 } 70 71 type workflowMeta struct { 72 Count int `json:"count"` 73 RunIDs []string `json:"runIDs"` 74 } 75 76 func newWorkflowMeta() *workflowMeta { 77 return &workflowMeta{ 78 Count: 0, 79 RunIDs: []string{}, 80 } 81 } 82 83 var ( 84 _ Store = (*MemStore)(nil) 85 _ EventAdder = (*MemStore)(nil) 86 ) 87 88 // NewMemStore returns a MemStore 89 func NewMemStore() *MemStore { 90 s := &MemStore{ 91 workflows: map[workflow.ID]*workflowMeta{}, 92 runs: map[string]*State{}, 93 } 94 return s 95 } 96 97 // Create adds a new run.State to a MemStore 98 func (s *MemStore) Create(ctx context.Context, r *State) (*State, error) { 99 if r == nil { 100 return nil, fmt.Errorf("run is nil") 101 } 102 run := r.Copy() 103 if run.ID == "" { 104 run.ID = NewID() 105 } 106 if _, err := s.Get(ctx, run.ID); !errors.Is(err, ErrNotFound) { 107 return nil, fmt.Errorf("run with this ID already exists") 108 } 109 if err := run.Validate(); err != nil { 110 return nil, err 111 } 112 s.mu.Lock() 113 defer s.mu.Unlock() 114 _, ok := s.workflows[run.WorkflowID] 115 if !ok { 116 wfm := newWorkflowMeta() 117 s.workflows[run.WorkflowID] = wfm 118 } 119 s.workflows[run.WorkflowID].Count++ 120 runIDs := s.workflows[run.WorkflowID].RunIDs 121 s.workflows[run.WorkflowID].RunIDs = append(runIDs, run.ID) 122 s.runs[run.ID] = run 123 return run, nil 124 } 125 126 // Put updates an existing run.State in a MemStore 127 func (s *MemStore) Put(ctx context.Context, r *State) (*State, error) { 128 if r == nil { 129 return nil, fmt.Errorf("run is nil") 130 } 131 run := r.Copy() 132 if run.ID == "" { 133 return nil, fmt.Errorf("run has empty ID") 134 } 135 fetchedR, err := s.Get(ctx, run.ID) 136 if err != nil { 137 return nil, ErrNotFound 138 } 139 if fetchedR.WorkflowID != run.WorkflowID { 140 return nil, fmt.Errorf("run.State's WorkflowID does not match the WorkflowID of the associated run.State currently in the store") 141 } 142 if err := run.Validate(); err != nil { 143 return nil, err 144 } 145 s.mu.Lock() 146 defer s.mu.Unlock() 147 s.runs[run.ID] = run 148 return run, nil 149 } 150 151 // Get fetches a run.State using the associated ID 152 func (s *MemStore) Get(ctx context.Context, id string) (*State, error) { 153 s.mu.Lock() 154 r, ok := s.runs[id] 155 s.mu.Unlock() 156 if !ok { 157 return nil, ErrNotFound 158 } 159 return r, nil 160 } 161 162 // Count returns the number of runs for a given workflow.ID 163 func (s *MemStore) Count(ctx context.Context, wid workflow.ID) (int, error) { 164 s.mu.Lock() 165 defer s.mu.Unlock() 166 wfm, ok := s.workflows[wid] 167 if !ok { 168 return 0, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid) 169 } 170 return wfm.Count, nil 171 } 172 173 // List lists all the runs associated with the workflow.ID in reverse 174 // chronological order 175 func (s *MemStore) List(ctx context.Context, wid workflow.ID, lp params.List) ([]*State, error) { 176 if err := lp.Validate(); err != nil { 177 return nil, err 178 } 179 180 s.mu.Lock() 181 defer s.mu.Unlock() 182 wfm, ok := s.workflows[wid] 183 if !ok { 184 return nil, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid) 185 } 186 runIDs := wfm.RunIDs 187 runs := []*State{} 188 for i := len(runIDs) - 1; i >= 0; i-- { 189 id := runIDs[i] 190 run, ok := s.runs[id] 191 if !ok { 192 return nil, fmt.Errorf("run %q missing from the store", id) 193 } 194 runs = append(runs, run) 195 } 196 197 if lp.Offset >= len(runs) { 198 return []*State{}, nil 199 } 200 201 start := lp.Offset 202 end := lp.Offset + lp.Limit 203 if end > len(runs) || lp.All() { 204 end = len(runs) 205 } 206 return runs[start:end], nil 207 } 208 209 // GetLatest returns the most recent run associated with the workflow id 210 func (s *MemStore) GetLatest(ctx context.Context, wid workflow.ID) (*State, error) { 211 s.mu.Lock() 212 defer s.mu.Unlock() 213 214 wfm, ok := s.workflows[wid] 215 if !ok { 216 return nil, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid) 217 } 218 runIDs := wfm.RunIDs 219 latestRunID := runIDs[len(runIDs)-1] 220 run, ok := s.runs[latestRunID] 221 if !ok { 222 return nil, fmt.Errorf("run %q missing from the store", latestRunID) 223 } 224 return run, nil 225 } 226 227 // GetStatus returns the status of the latest run based on the 228 // workflow.ID 229 func (s *MemStore) GetStatus(ctx context.Context, wid workflow.ID) (Status, error) { 230 run, err := s.GetLatest(ctx, wid) 231 if err != nil { 232 return "", err 233 } 234 return run.Status, nil 235 } 236 237 // ListByStatus returns a list of run.State entries with a given status 238 // looking only at the most recent run of each Workflow 239 func (s *MemStore) ListByStatus(ctx context.Context, owner profile.ID, status Status, lp params.List) ([]*State, error) { 240 if err := lp.Validate(); err != nil { 241 return nil, err 242 } 243 244 set := NewSet() 245 s.mu.Lock() 246 defer s.mu.Unlock() 247 for _, wfm := range s.workflows { 248 runIDs := wfm.RunIDs 249 rid := runIDs[len(runIDs)-1] 250 run, ok := s.runs[rid] 251 if !ok { 252 return nil, fmt.Errorf("run %q missing from the store", rid) 253 } 254 if run.Status == status { 255 set.Add(run) 256 } 257 } 258 259 if lp.Offset >= set.Len() { 260 return []*State{}, nil 261 } 262 263 start := lp.Offset 264 end := lp.Offset + lp.Limit 265 if end > set.Len() || lp.All() { 266 end = set.Len() 267 } 268 269 sort.Sort(set) 270 return set.Slice(start, end), nil 271 } 272 273 // Shutdown closes the store 274 func (s *MemStore) Shutdown() error { 275 return nil 276 } 277 278 // AddEvent writes an event to the store, attaching it to an existing stored 279 // run state 280 func (s *MemStore) AddEvent(id string, e event.Event) error { 281 s.mu.Lock() 282 defer s.mu.Unlock() 283 284 run, ok := s.runs[id] 285 if !ok { 286 return ErrNotFound 287 } 288 if err := run.AddTransformEvent(e); err != nil { 289 return fmt.Errorf("adding transform event to run: %w", err) 290 } 291 292 s.runs[id] = run 293 return nil 294 } 295 296 // MarshalJSON satisfies the json.Marshaller interface 297 func (s *MemStore) MarshalJSON() ([]byte, error) { 298 if s == nil { 299 s = NewMemStore() 300 } 301 return json.Marshal(struct { 302 Workflows map[workflow.ID]*workflowMeta `json:"workflows"` 303 Runs map[string]*State `json:"runs"` 304 }{Workflows: s.workflows, Runs: s.runs}) 305 } 306 307 // UnmarshalJSON satisfies the json.Unmarshaller interface 308 func (s *MemStore) UnmarshalJSON(p []byte) error { 309 v := struct { 310 Workflows map[workflow.ID]*workflowMeta `json:"workflows"` 311 Runs map[string]*State `json:"runs"` 312 }{} 313 314 if err := json.Unmarshal(p, &v); err != nil { 315 return err 316 } 317 s.workflows = v.Workflows 318 s.runs = v.Runs 319 return nil 320 }