get.porter.sh/porter@v1.3.0/pkg/storage/installation_store.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"get.porter.sh/porter/pkg/tracing"
     8  	"go.mongodb.org/mongo-driver/bson"
     9  )
    10  
    11  const (
    12  	CollectionInstallations = "installations"
    13  	CollectionRuns          = "runs"
    14  	CollectionResults       = "results"
    15  	CollectionOutputs       = "outputs"
    16  )
    17  
    18  var _ InstallationProvider = InstallationStore{}
    19  
    20  // InstallationStore is a persistent store for installation documents.
    21  type InstallationStore struct {
    22  	store   Store
    23  	encrypt EncryptionHandler
    24  	decrypt EncryptionHandler
    25  }
    26  
    27  // NewInstallationStore creates a persistent store for installations using the specified
    28  // backing datastore.
    29  func NewInstallationStore(datastore Store) InstallationStore {
    30  	return InstallationStore{
    31  		store:   datastore,
    32  		encrypt: noOpEncryptionHandler,
    33  		decrypt: noOpEncryptionHandler,
    34  	}
    35  }
    36  
    37  // EnsureInstallationIndices created indices on the installations collection.
    38  func EnsureInstallationIndices(ctx context.Context, store Store) error {
    39  	ctx, span := tracing.StartSpan(ctx)
    40  	defer span.EndSpan()
    41  
    42  	span.Debug("Initializing installation collection indices")
    43  
    44  	opts := EnsureIndexOptions{
    45  		Indices: []Index{
    46  			// query installations by a namespace (list) or namespace + name (get)
    47  			{Collection: CollectionInstallations, Keys: []string{"namespace", "name"}, Unique: true},
    48  			// query runs by installation (list)
    49  			{Collection: CollectionRuns, Keys: []string{"namespace", "installation"}},
    50  			// query results by installation (delete or batch get)
    51  			{Collection: CollectionResults, Keys: []string{"namespace", "installation"}},
    52  			// query results by run (list)
    53  			{Collection: CollectionResults, Keys: []string{"runId"}},
    54  			// query most recent outputs by run (porter installation run show, when we list outputs)
    55  			{Collection: CollectionOutputs, Keys: []string{"namespace", "installation", "-resultId"}},
    56  			// query outputs by result (list)
    57  			{Collection: CollectionOutputs, Keys: []string{"resultId", "name"}, Unique: true},
    58  			// query most recent outputs by name for an installation
    59  			{Collection: CollectionOutputs, Keys: []string{"namespace", "installation", "name", "-resultId"}},
    60  		},
    61  	}
    62  
    63  	err := store.EnsureIndex(ctx, opts)
    64  	return span.Error(err)
    65  }
    66  
    67  func (s InstallationStore) ListInstallations(ctx context.Context, listOptions ListOptions) ([]Installation, error) {
    68  	_, log := tracing.StartSpan(ctx)
    69  	defer log.EndSpan()
    70  
    71  	var out []Installation
    72  	err := s.store.Find(ctx, CollectionInstallations, listOptions.ToFindOptions(), &out)
    73  	return out, err
    74  }
    75  
    76  func (s InstallationStore) ListRuns(ctx context.Context, namespace string, installation string) ([]Run, map[string][]Result, error) {
    77  	var runs []Run
    78  	var err error
    79  	var results []Result
    80  
    81  	opts := FindOptions{
    82  		Sort: []string{"_id"},
    83  		Filter: bson.M{
    84  			"namespace":    namespace,
    85  			"installation": installation,
    86  		},
    87  	}
    88  	err = s.store.Find(ctx, CollectionRuns, opts, &runs)
    89  	if err != nil {
    90  		return nil, nil, err
    91  	}
    92  
    93  	err = s.store.Find(ctx, CollectionResults, opts, &results)
    94  	if err != nil {
    95  		return runs, nil, err
    96  	}
    97  
    98  	resultsMap := make(map[string][]Result, len(runs))
    99  
   100  	for _, run := range runs {
   101  		resultsMap[run.ID] = []Result{}
   102  	}
   103  
   104  	for _, res := range results {
   105  		if _, ok := resultsMap[res.RunID]; ok {
   106  			resultsMap[res.RunID] = append(resultsMap[res.RunID], res)
   107  		}
   108  	}
   109  
   110  	return runs, resultsMap, err
   111  }
   112  
   113  func (s InstallationStore) ListResults(ctx context.Context, runID string) ([]Result, error) {
   114  	var out []Result
   115  	opts := FindOptions{
   116  		Sort: []string{"_id"},
   117  		Filter: bson.M{
   118  			"runId": runID,
   119  		},
   120  	}
   121  	err := s.store.Find(ctx, CollectionResults, opts, &out)
   122  	return out, err
   123  }
   124  
   125  func (s InstallationStore) ListOutputs(ctx context.Context, resultID string) ([]Output, error) {
   126  	var out []Output
   127  	opts := FindOptions{
   128  		Sort: []string{"resultId", "name"},
   129  		Filter: bson.M{
   130  			"resultId": resultID,
   131  		},
   132  	}
   133  	err := s.store.Find(ctx, CollectionOutputs, opts, &out)
   134  	return out, err
   135  }
   136  
   137  func (s InstallationStore) FindInstallations(ctx context.Context, findOpts FindOptions) ([]Installation, error) {
   138  	_, log := tracing.StartSpan(ctx)
   139  	defer log.EndSpan()
   140  
   141  	var out []Installation
   142  	err := s.store.Find(ctx, CollectionInstallations, findOpts, &out)
   143  	return out, err
   144  }
   145  
   146  func (s InstallationStore) GetInstallation(ctx context.Context, namespace string, name string) (Installation, error) {
   147  	var out Installation
   148  
   149  	opts := FindOptions{
   150  		Filter: bson.M{
   151  			"namespace": namespace,
   152  			"name":      name,
   153  		},
   154  	}
   155  
   156  	err := s.store.FindOne(ctx, CollectionInstallations, opts, &out)
   157  
   158  	return out, err
   159  }
   160  
   161  func (s InstallationStore) GetRun(ctx context.Context, id string) (Run, error) {
   162  	var out Run
   163  	opts := GetOptions{ID: id}
   164  	err := s.store.Get(ctx, CollectionRuns, opts, &out)
   165  	return out, err
   166  }
   167  
   168  func (s InstallationStore) GetResult(ctx context.Context, id string) (Result, error) {
   169  	var out Result
   170  	opts := GetOptions{ID: id}
   171  	err := s.store.Get(ctx, CollectionResults, opts, &out)
   172  	return out, err
   173  }
   174  
   175  func (s InstallationStore) GetLastRun(ctx context.Context, namespace string, installation string) (Run, error) {
   176  	var out []Run
   177  	opts := FindOptions{
   178  		Sort:  []string{"-_id"},
   179  		Limit: 1,
   180  		Filter: bson.M{
   181  			"namespace":    namespace,
   182  			"installation": installation,
   183  		},
   184  	}
   185  	err := s.store.Find(ctx, CollectionRuns, opts, &out)
   186  	if err != nil {
   187  		return Run{}, err
   188  	}
   189  	if len(out) == 0 {
   190  		return Run{}, ErrNotFound{Collection: CollectionRuns}
   191  	}
   192  	return out[0], err
   193  }
   194  
   195  func (s InstallationStore) GetLastOutput(ctx context.Context, namespace string, installation string, name string) (Output, error) {
   196  	var out Output
   197  	opts := FindOptions{
   198  		Sort:  []string{"-_id"},
   199  		Limit: 1,
   200  		Filter: bson.M{
   201  			"namespace":    namespace,
   202  			"installation": installation,
   203  			"name":         name,
   204  		},
   205  	}
   206  	err := s.store.FindOne(ctx, CollectionOutputs, opts, &out)
   207  	return out, err
   208  }
   209  
   210  func (s InstallationStore) GetLastOutputs(ctx context.Context, namespace string, installation string) (Outputs, error) {
   211  	var groupedOutputs []struct {
   212  		ID         string `json:"_id"`
   213  		LastOutput Output `json:"lastOutput"`
   214  	}
   215  	opts := AggregateOptions{
   216  		Pipeline: []bson.D{
   217  			// List outputs by installation
   218  			{{Key: "$match", Value: bson.M{
   219  				"namespace":    namespace,
   220  				"installation": installation,
   221  			}}},
   222  			// Reverse sort them (newest on top)
   223  			{{Key: "$sort", Value: bson.D{
   224  				{Key: "namespace", Value: 1},
   225  				{Key: "installation", Value: 1},
   226  				{Key: "name", Value: 1},
   227  				{Key: "resultId", Value: -1},
   228  			}}},
   229  			// Group them by output name and select the last value for each output
   230  			{{Key: "$group", Value: bson.D{
   231  				{Key: "_id", Value: "$name"},
   232  				{Key: "lastOutput", Value: bson.M{"$first": "$$ROOT"}},
   233  			}}},
   234  		},
   235  	}
   236  	err := s.store.Aggregate(ctx, CollectionOutputs, opts, &groupedOutputs)
   237  
   238  	lastOutputs := make([]Output, len(groupedOutputs))
   239  	for i, groupedOutput := range groupedOutputs {
   240  		lastOutputs[i] = groupedOutput.LastOutput
   241  	}
   242  
   243  	return NewOutputs(lastOutputs), err
   244  }
   245  
   246  func (s InstallationStore) GetLogs(ctx context.Context, runID string) (string, bool, error) {
   247  	var out Output
   248  	opts := FindOptions{
   249  		Sort: []string{"resultId"}, // get logs from the last result for a run
   250  		Filter: bson.M{
   251  			"runId": runID,
   252  			"name":  "io.cnab.outputs.invocationImageLogs",
   253  		},
   254  		Limit: 1,
   255  	}
   256  	err := s.store.FindOne(ctx, CollectionOutputs, opts, &out)
   257  	if errors.Is(err, ErrNotFound{}) {
   258  		return "", false, nil
   259  	}
   260  	return string(out.Value), err == nil, err
   261  }
   262  
   263  func (s InstallationStore) GetLastLogs(ctx context.Context, namespace string, installation string) (string, bool, error) {
   264  	var out Output
   265  	opts := FindOptions{
   266  		Sort: []string{"-resultId"}, // get logs from the last result for a run
   267  		Filter: bson.M{
   268  			"namespace":    namespace,
   269  			"installation": installation,
   270  			"name":         "io.cnab.outputs.invocationImageLogs",
   271  		},
   272  		Limit: 1,
   273  	}
   274  	err := s.store.FindOne(ctx, CollectionOutputs, opts, &out)
   275  	if errors.Is(err, ErrNotFound{}) {
   276  		return "", false, nil
   277  	}
   278  	return string(out.Value), err == nil, err
   279  }
   280  
   281  func (s InstallationStore) InsertInstallation(ctx context.Context, installation Installation) error {
   282  	installation.SchemaVersion = DefaultInstallationSchemaVersion
   283  	opts := InsertOptions{
   284  		Documents: []interface{}{installation},
   285  	}
   286  	return s.store.Insert(ctx, CollectionInstallations, opts)
   287  }
   288  
   289  func (s InstallationStore) InsertRun(ctx context.Context, run Run) error {
   290  	opts := InsertOptions{
   291  		Documents: []interface{}{run},
   292  	}
   293  	return s.store.Insert(ctx, CollectionRuns, opts)
   294  }
   295  
   296  func (s InstallationStore) InsertResult(ctx context.Context, result Result) error {
   297  	opts := InsertOptions{
   298  		Documents: []interface{}{result},
   299  	}
   300  	return s.store.Insert(ctx, CollectionResults, opts)
   301  }
   302  
   303  func (s InstallationStore) InsertOutput(ctx context.Context, output Output) error {
   304  	opts := InsertOptions{
   305  		Documents: []interface{}{output},
   306  	}
   307  	return s.store.Insert(ctx, CollectionOutputs, opts)
   308  }
   309  
   310  func (s InstallationStore) UpdateInstallation(ctx context.Context, installation Installation) error {
   311  	installation.SchemaVersion = DefaultInstallationSchemaVersion
   312  	opts := UpdateOptions{
   313  		Document: installation,
   314  	}
   315  	return s.store.Update(ctx, CollectionInstallations, opts)
   316  }
   317  
   318  func (s InstallationStore) UpsertRun(ctx context.Context, run Run) error {
   319  	opts := UpdateOptions{
   320  		Upsert:   true,
   321  		Document: run,
   322  	}
   323  	return s.store.Update(ctx, CollectionRuns, opts)
   324  }
   325  
   326  func (s InstallationStore) UpsertInstallation(ctx context.Context, installation Installation) error {
   327  	installation.SchemaVersion = DefaultInstallationSchemaVersion
   328  	opts := UpdateOptions{
   329  		Upsert:   true,
   330  		Document: installation,
   331  	}
   332  	return s.store.Update(ctx, CollectionInstallations, opts)
   333  }
   334  
   335  // RemoveInstallation and all associated data.
   336  func (s InstallationStore) RemoveInstallation(ctx context.Context, namespace string, name string) error {
   337  	removeInstallation := RemoveOptions{
   338  		Filter: bson.M{
   339  			"namespace": namespace,
   340  			"name":      name,
   341  		},
   342  	}
   343  	err := s.store.Remove(ctx, CollectionInstallations, removeInstallation)
   344  	if err != nil {
   345  		return err
   346  	}
   347  
   348  	// Find associated documents
   349  	removeChildDocs := RemoveOptions{
   350  		Filter: bson.M{
   351  			"namespace":    namespace,
   352  			"installation": name,
   353  		},
   354  		All: true,
   355  	}
   356  
   357  	// Delete runs
   358  	err = s.store.Remove(ctx, CollectionRuns, removeChildDocs)
   359  	if err != nil {
   360  		return err
   361  	}
   362  
   363  	// Delete results
   364  	err = s.store.Remove(ctx, CollectionResults, removeChildDocs)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	// Delete outputs
   370  	err = s.store.Remove(ctx, CollectionOutputs, removeChildDocs)
   371  	if err != nil {
   372  		return err
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  // EncryptionHandler is a function that transforms data by encrypting or decrypting it.
   379  type EncryptionHandler func([]byte) ([]byte, error)
   380  
   381  // noOpEncryptHandler is used when no handler is specified.
   382  var noOpEncryptionHandler = func(data []byte) ([]byte, error) {
   383  	return data, nil
   384  }