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  }