github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/automation/workflow/store.go (about) 1 package workflow 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 "sync" 9 10 "github.com/qri-io/qri/base/params" 11 "github.com/qri-io/qri/profile" 12 ) 13 14 var ( 15 // ErrNotFound indicates that the workflow was not found in the store 16 ErrNotFound = fmt.Errorf("workflow not found") 17 // ErrWorkflowForDatasetExists indicates that a workflow associated 18 // with the given dataset already exists 19 ErrWorkflowForDatasetExists = fmt.Errorf("a workflow associated with the given dataset ID already exists") 20 ) 21 22 // Store manages & stores workflows, allowing listing and updating of workflows 23 type Store interface { 24 Lister 25 // Get fetches a Workflow from the Store using the workflow.ID 26 Get(ctx context.Context, wid ID) (*Workflow, error) 27 // GetByInitID fetches a Workflow from the Store using the dataset.ID 28 GetByInitID(ctx context.Context, initID string) (*Workflow, error) 29 // Remove removes a Workflow from the Store using the workflow.ID 30 Remove(ctx context.Context, id ID) error 31 // Put adds a Workflow to the Store. If there is no ID in the Workflow, 32 // Put will create a new ID, record the time in the `Created` field 33 // and put the workflow in the store, ensuring that the associated 34 // Workflow.InitID is unique. If there is an existing ID, Put will 35 // update the entry in the Store, if the given workflow is valid 36 Put(ctx context.Context, wf *Workflow) (*Workflow, error) 37 // Shutdown closes the store 38 Shutdown(ctx context.Context) error 39 } 40 41 // A Lister lists entries from a workflow store 42 type Lister interface { 43 // List lists the Workflows in the Store in reverse chronological order 44 // by Workflow.Created time 45 List(ctx context.Context, pid profile.ID, lp params.List) ([]*Workflow, error) 46 // ListDeployed lists the deployed Workflows in the Store in reverse 47 // chronological order by Workflow.Created time 48 ListDeployed(ctx context.Context, pid profile.ID, lp params.List) ([]*Workflow, error) 49 } 50 51 // MemStore is an in memory representation of a Store 52 type MemStore struct { 53 mu *sync.Mutex 54 workflows map[ID]*Workflow 55 } 56 57 var _ Store = (*MemStore)(nil) 58 59 // NewMemStore returns a MemStore 60 func NewMemStore() *MemStore { 61 return &MemStore{ 62 mu: &sync.Mutex{}, 63 workflows: map[ID]*Workflow{}, 64 } 65 } 66 67 // Put adds a Workflow to a MemStore 68 func (m *MemStore) Put(ctx context.Context, wf *Workflow) (*Workflow, error) { 69 if wf == nil { 70 return nil, ErrNilWorkflow 71 } 72 w := wf.Copy() 73 if w.ID == "" { 74 if _, err := m.GetByInitID(ctx, w.InitID); !errors.Is(err, ErrNotFound) { 75 return nil, ErrWorkflowForDatasetExists 76 } 77 w.ID = NewID() 78 } 79 if err := w.Validate(); err != nil { 80 return nil, err 81 } 82 m.mu.Lock() 83 m.workflows[w.ID] = w 84 m.mu.Unlock() 85 return w, nil 86 } 87 88 // Get fetches a Workflow using the associated ID 89 func (m *MemStore) Get(ctx context.Context, wid ID) (*Workflow, error) { 90 m.mu.Lock() 91 wf, ok := m.workflows[wid] 92 m.mu.Unlock() 93 if !ok { 94 return nil, ErrNotFound 95 } 96 return wf, nil 97 } 98 99 // GetByInitID fetches a Workflow using the dataset ID 100 func (m *MemStore) GetByInitID(ctx context.Context, initID string) (*Workflow, error) { 101 if initID == "" { 102 return nil, ErrNotFound 103 } 104 m.mu.Lock() 105 defer m.mu.Unlock() 106 for _, wf := range m.workflows { 107 if wf.InitID == initID { 108 return wf, nil 109 } 110 } 111 return nil, ErrNotFound 112 } 113 114 // Remove removes a Workflow from a Store 115 func (m *MemStore) Remove(ctx context.Context, id ID) error { 116 m.mu.Lock() 117 _, ok := m.workflows[id] 118 if !ok { 119 return ErrNotFound 120 } 121 delete(m.workflows, id) 122 m.mu.Unlock() 123 return nil 124 } 125 126 // List lists all the workflows in the store, by decending order from time of 127 // creation 128 func (m *MemStore) List(ctx context.Context, pid profile.ID, lp params.List) ([]*Workflow, error) { 129 wfs := NewSet() 130 fetchAll := false 131 switch { 132 case lp.Limit == -1 && lp.Offset == 0: 133 fetchAll = true 134 case lp.Limit < 0: 135 return nil, fmt.Errorf("limit of %d is out of bounds", lp.Limit) 136 case lp.Offset < 0: 137 return nil, fmt.Errorf("offset of %d is out of bounds", lp.Offset) 138 case lp.Limit == 0: 139 return []*Workflow{}, nil 140 } 141 m.mu.Lock() 142 defer m.mu.Unlock() 143 for _, wf := range m.workflows { 144 wfs.Add(wf) 145 } 146 147 if lp.Offset >= wfs.Len() { 148 return []*Workflow{}, nil 149 } 150 151 start := lp.Offset 152 end := lp.Offset + lp.Limit 153 if end > wfs.Len() || fetchAll { 154 end = wfs.Len() 155 } 156 157 sort.Sort(wfs) 158 return wfs.Slice(start, end), nil 159 } 160 161 // ListDeployed lists all the workflows in the store that are deployed, by 162 // decending order from time of creation 163 func (m *MemStore) ListDeployed(ctx context.Context, pid profile.ID, lp params.List) ([]*Workflow, error) { 164 wfs := NewSet() 165 fetchAll := false 166 switch { 167 case lp.Limit == -1 && lp.Offset == 0: 168 fetchAll = true 169 case lp.Limit < 0: 170 return nil, fmt.Errorf("limit of %d is out of bounds", lp.Limit) 171 case lp.Offset < 0: 172 return nil, fmt.Errorf("offset of %d is out of bounds", lp.Offset) 173 case lp.Limit == 0: 174 return []*Workflow{}, nil 175 } 176 m.mu.Lock() 177 defer m.mu.Unlock() 178 for _, wf := range m.workflows { 179 if wf.Active { 180 wfs.Add(wf) 181 } 182 } 183 184 if lp.Offset >= wfs.Len() { 185 return []*Workflow{}, nil 186 } 187 188 start := lp.Offset 189 end := lp.Offset + lp.Limit 190 if end > wfs.Len() || fetchAll { 191 end = wfs.Len() 192 } 193 194 sort.Sort(wfs) 195 return wfs.Slice(start, end), nil 196 } 197 198 // Shutdown closes the store 199 func (m *MemStore) Shutdown(ctx context.Context) error { 200 return nil 201 }