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

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"get.porter.sh/porter/pkg/cnab"
     8  	"github.com/cnabio/cnab-go/bundle"
     9  	"github.com/cnabio/cnab-go/bundle/definition"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"go.mongodb.org/mongo-driver/bson"
    13  )
    14  
    15  var _ InstallationProvider = InstallationStore{}
    16  
    17  var exampleBundle = bundle.Bundle{
    18  	SchemaVersion:    "schemaVersion",
    19  	Name:             "mybun",
    20  	Version:          "v0.1.0",
    21  	Description:      "this is my bundle",
    22  	InvocationImages: []bundle.InvocationImage{},
    23  	Actions: map[string]bundle.Action{
    24  		"test": {Modifies: true},
    25  		"logs": {Modifies: false},
    26  	},
    27  }
    28  
    29  // generateInstallationData creates test installations, runs, results and outputs
    30  // it returns a InstallationStorageProvider, and a test cleanup function.
    31  //
    32  // installations/
    33  //   foo/
    34  //     CLAIM_ID_1 (install)
    35  //     CLAIM_ID_2 (upgrade)
    36  //     CLAIM_ID_3 (invoke - test)
    37  //     CLAIM_ID_4 (uninstall)
    38  //   bar/
    39  //     CLAIM_ID_10 (install)
    40  //   baz/
    41  //     CLAIM_ID_20 (install)
    42  //     CLAIM_ID_21 (install)
    43  // results/
    44  //   CLAIM_ID_1/
    45  //     RESULT_ID_1 (success)
    46  //   CLAIM_ID_2/
    47  //     RESULT_ID 2 (success)
    48  //   CLAIM_ID_3/
    49  //     RESULT_ID_3 (failed)
    50  //   CLAIM_ID_4/
    51  //     RESULT_ID_4 (success)
    52  //   CLAIM_ID_10/
    53  //     RESULT_ID_10 (running)
    54  //     RESULT_ID_11 (success)
    55  //   CLAIM_ID_20/
    56  //     RESULT_ID_20 (failed)
    57  //   CLAIM_ID_21/
    58  //     NO RESULT YET
    59  // outputs/
    60  //   RESULT_ID_1/
    61  //     RESULT_ID_1_OUTPUT_1
    62  //   RESULT_ID_2/
    63  //     RESULT_ID_2_OUTPUT_1
    64  //     RESULT_ID_2_OUTPUT_2
    65  func generateInstallationData(t *testing.T) *TestInstallationProvider {
    66  	cp := NewTestInstallationProvider(t)
    67  
    68  	bun := cnab.ExtendedBundle{Bundle: bundle.Bundle{
    69  		Definitions: map[string]*definition.Schema{
    70  			"output1": {
    71  				Type: "string",
    72  			},
    73  			"output2": {
    74  				Type: "string",
    75  			},
    76  		},
    77  		Outputs: map[string]bundle.Output{
    78  			"output1": {
    79  				Definition: "output1",
    80  			},
    81  			"output2": {
    82  				Definition: "output2",
    83  				ApplyTo:    []string{"upgrade"},
    84  			},
    85  		},
    86  	}}
    87  
    88  	setBun := func(r *Run) { r.Bundle = bun.Bundle }
    89  
    90  	// Create the foo installation data
    91  	foo := cp.CreateInstallation(NewInstallation("dev", "foo"), func(i *Installation) {
    92  		i.ID = "1"
    93  		i.Labels = map[string]string{
    94  			"team":  "red",
    95  			"owner": "marie",
    96  		}
    97  	})
    98  	run := cp.CreateRun(foo.NewRun(cnab.ActionInstall, bun), setBun)
    99  	result := cp.CreateResult(run.NewResult(cnab.StatusSucceeded))
   100  	cp.CreateOutput(result.NewOutput(cnab.OutputInvocationImageLogs, []byte("install logs")))
   101  	cp.CreateOutput(result.NewOutput("output1", []byte("install output1")))
   102  
   103  	run = cp.CreateRun(foo.NewRun(cnab.ActionUpgrade, bun), setBun)
   104  	result = cp.CreateResult(run.NewResult(cnab.StatusSucceeded))
   105  	cp.CreateOutput(result.NewOutput(cnab.OutputInvocationImageLogs, []byte("upgrade logs")))
   106  	cp.CreateOutput(result.NewOutput("output1", []byte("upgrade output1")))
   107  	cp.CreateOutput(result.NewOutput("output2", []byte("upgrade output2")))
   108  	// Test bug in how we read output names by having the name include characters from the result id
   109  	cp.CreateOutput(result.NewOutput(result.ID+"-output3", []byte("upgrade output3")))
   110  
   111  	run = cp.CreateRun(foo.NewRun("test", bun), setBun)
   112  	result = cp.CreateResult(run.NewResult(cnab.StatusFailed))
   113  
   114  	run = cp.CreateRun(foo.NewRun(cnab.ActionUninstall, bun), setBun)
   115  	result = cp.CreateResult(run.NewResult(cnab.StatusSucceeded))
   116  
   117  	// Record the status of the foo installation
   118  	foo.ApplyResult(run, result)
   119  	require.NoError(t, cp.UpdateInstallation(context.Background(), foo))
   120  
   121  	// Create the bar installation data
   122  	bar := cp.CreateInstallation(NewInstallation("dev", "bar"), func(i *Installation) {
   123  		i.ID = "2"
   124  		i.Labels = map[string]string{
   125  			"team": "red",
   126  		}
   127  	})
   128  	run = cp.CreateRun(bar.NewRun(cnab.ActionInstall, bun), setBun)
   129  	cp.CreateResult(run.NewResult(cnab.StatusRunning))
   130  	result = cp.CreateResult(run.NewResult(cnab.StatusSucceeded))
   131  
   132  	// Record the status of the bar installation
   133  	bar.ApplyResult(run, result)
   134  	require.NoError(t, cp.UpdateInstallation(context.Background(), bar))
   135  
   136  	// Create the baz installation data
   137  	baz := cp.CreateInstallation(NewInstallation("dev", "baz"))
   138  	run = cp.CreateRun(baz.NewRun(cnab.ActionInstall, bun), setBun)
   139  	cp.CreateResult(run.NewResult(cnab.StatusFailed))
   140  	run = cp.CreateRun(baz.NewRun(cnab.ActionInstall, bun), setBun)
   141  	result = cp.CreateResult(run.NewResult(cnab.StatusRunning))
   142  
   143  	// Record the status of the baz installation
   144  	baz.ApplyResult(run, result)
   145  	require.NoError(t, cp.UpdateInstallation(context.Background(), baz))
   146  
   147  	return cp
   148  }
   149  
   150  func TestInstallationStorageProvider_Installations(t *testing.T) {
   151  	cp := generateInstallationData(t)
   152  	defer cp.Close()
   153  
   154  	t.Run("ListInstallations", func(t *testing.T) {
   155  		installations, err := cp.ListInstallations(context.Background(), ListOptions{Namespace: "dev"})
   156  		require.NoError(t, err, "ListInstallations failed")
   157  
   158  		require.Len(t, installations, 3, "Expected 3 installations")
   159  
   160  		bar := installations[0]
   161  		assert.Equal(t, "bar", bar.Name)
   162  		assert.Equal(t, cnab.StatusSucceeded, bar.Status.ResultStatus)
   163  
   164  		baz := installations[1]
   165  		assert.Equal(t, "baz", baz.Name)
   166  		assert.Equal(t, cnab.StatusRunning, baz.Status.ResultStatus)
   167  
   168  		foo := installations[2]
   169  		assert.Equal(t, "foo", foo.Name)
   170  		assert.Equal(t, cnab.StatusSucceeded, foo.Status.ResultStatus)
   171  	})
   172  
   173  	t.Run("ListInstallations with skip option", func(t *testing.T) {
   174  		installations, err := cp.ListInstallations(context.Background(), ListOptions{Namespace: "dev", Skip: 1})
   175  		require.NoError(t, err, "ListInstallations failed")
   176  
   177  		require.Len(t, installations, 2, "Expected 2 installations")
   178  
   179  		bar := installations[0]
   180  		assert.Equal(t, "baz", bar.Name)
   181  		assert.Equal(t, cnab.StatusRunning, bar.Status.ResultStatus)
   182  
   183  		baz := installations[1]
   184  		assert.Equal(t, "foo", baz.Name)
   185  		assert.Equal(t, cnab.StatusSucceeded, baz.Status.ResultStatus)
   186  	})
   187  
   188  	t.Run("ListInstallations with limit option", func(t *testing.T) {
   189  		installations, err := cp.ListInstallations(context.Background(), ListOptions{Namespace: "dev", Limit: 2})
   190  		require.NoError(t, err, "ListInstallations failed")
   191  
   192  		require.Len(t, installations, 2, "Expected 2 installations")
   193  
   194  		bar := installations[0]
   195  		assert.Equal(t, "bar", bar.Name)
   196  		assert.Equal(t, cnab.StatusSucceeded, bar.Status.ResultStatus)
   197  
   198  		baz := installations[1]
   199  		assert.Equal(t, "baz", baz.Name)
   200  		assert.Equal(t, cnab.StatusRunning, baz.Status.ResultStatus)
   201  	})
   202  
   203  	t.Run("FindInstallations by label", func(t *testing.T) {
   204  		opts := FindOptions{
   205  			Filter: map[string]interface{}{
   206  				"labels.team":  "red",
   207  				"labels.owner": "marie",
   208  			},
   209  		}
   210  		installations, err := cp.FindInstallations(context.Background(), opts)
   211  		require.NoError(t, err, "FindInstallations failed")
   212  
   213  		require.Len(t, installations, 1)
   214  		assert.Equal(t, "foo", installations[0].Name)
   215  	})
   216  
   217  	t.Run("FindInstallations - project results", func(t *testing.T) {
   218  		opts := FindOptions{
   219  			Select: bson.D{{Key: "labels", Value: false}},
   220  			Sort:   []string{"-id"},
   221  			Filter: bson.M{
   222  				"labels.team": "red",
   223  			},
   224  		}
   225  		installations, err := cp.FindInstallations(context.Background(), opts)
   226  		require.NoError(t, err, "FindInstallations failed")
   227  
   228  		require.Len(t, installations, 2)
   229  		assert.Equal(t, "bar", installations[0].Name)
   230  		assert.Equal(t, "2", installations[0].ID)
   231  		assert.Empty(t, installations[0].Labels, "the select projection should have excluded the labels field")
   232  		assert.Equal(t, "foo", installations[1].Name)
   233  		assert.Equal(t, "1", installations[1].ID)
   234  		assert.Empty(t, installations[1].Labels, "the select projection should have excluded the labels field")
   235  	})
   236  
   237  	t.Run("GetInstallation", func(t *testing.T) {
   238  		foo, err := cp.GetInstallation(context.Background(), "dev", "foo")
   239  		require.NoError(t, err, "GetInstallation failed")
   240  
   241  		assert.Equal(t, "foo", foo.Name)
   242  		assert.Equal(t, cnab.StatusSucceeded, foo.Status.ResultStatus)
   243  	})
   244  
   245  	t.Run("GetInstallation - not found", func(t *testing.T) {
   246  		_, err := cp.GetInstallation(context.Background(), "", "missing")
   247  		require.ErrorIs(t, err, ErrNotFound{})
   248  	})
   249  
   250  }
   251  
   252  func TestInstallationStorageProvider_DeleteInstallation(t *testing.T) {
   253  	cp := generateInstallationData(t)
   254  	defer cp.Close()
   255  
   256  	installations, err := cp.ListInstallations(context.Background(), ListOptions{Namespace: "dev"})
   257  	require.NoError(t, err, "ListInstallations failed")
   258  	assert.Len(t, installations, 3, "expected 3 installations")
   259  
   260  	err = cp.RemoveInstallation(context.Background(), "dev", "foo")
   261  	require.NoError(t, err, "RemoveInstallation failed")
   262  
   263  	installations, err = cp.ListInstallations(context.Background(), ListOptions{Namespace: "dev"})
   264  	require.NoError(t, err, "ListInstallations failed")
   265  	assert.Len(t, installations, 2, "expected foo to be deleted")
   266  
   267  	_, err = cp.GetLastRun(context.Background(), "dev", "foo")
   268  	require.ErrorIs(t, err, ErrNotFound{})
   269  }
   270  
   271  func TestInstallationStorageProvider_Run(t *testing.T) {
   272  	cp := generateInstallationData(t)
   273  
   274  	t.Run("ListRuns", func(t *testing.T) {
   275  		runs, resultsMap, err := cp.ListRuns(context.Background(), "dev", "foo")
   276  		require.NoError(t, err, "Failed to read bundle runs: %s", err)
   277  
   278  		require.Len(t, runs, 4, "Expected 4 runs")
   279  		require.Len(t, resultsMap, 4, "Results expected to have 4 runs")
   280  		assert.Equal(t, cnab.ActionInstall, runs[0].Action)
   281  		assert.Equal(t, cnab.ActionUpgrade, runs[1].Action)
   282  		assert.Equal(t, "test", runs[2].Action)
   283  		assert.Equal(t, cnab.ActionUninstall, runs[3].Action)
   284  	})
   285  
   286  	t.Run("ListRuns - bundle not yet run", func(t *testing.T) {
   287  		// It's now possible for someone to create an installation and not immediately have any runs.
   288  		runs, resultsMap, err := cp.ListRuns(context.Background(), "dev", "missing")
   289  		require.NoError(t, err)
   290  		assert.Empty(t, runs)
   291  		assert.Empty(t, resultsMap)
   292  	})
   293  
   294  	t.Run("GetRun", func(t *testing.T) {
   295  		runs, _, err := cp.ListRuns(context.Background(), "dev", "foo")
   296  		require.NoError(t, err, "ListRuns failed")
   297  
   298  		assert.NotEmpty(t, runs, "no runs were found")
   299  		runID := runs[0].ID
   300  
   301  		c, err := cp.GetRun(context.Background(), runID)
   302  		require.NoError(t, err, "GetRun failed")
   303  
   304  		assert.Equal(t, "foo", c.Installation)
   305  		assert.Equal(t, cnab.ActionInstall, c.Action)
   306  	})
   307  
   308  	t.Run("GetRun - invalid claim", func(t *testing.T) {
   309  		_, err := cp.GetRun(context.Background(), "missing")
   310  		require.ErrorIs(t, err, ErrNotFound{})
   311  	})
   312  
   313  	t.Run("GetLastRun", func(t *testing.T) {
   314  		c, err := cp.GetLastRun(context.Background(), "dev", "bar")
   315  		require.NoError(t, err, "GetLastRun failed")
   316  
   317  		assert.Equal(t, "bar", c.Installation)
   318  		assert.Equal(t, cnab.ActionInstall, c.Action)
   319  	})
   320  
   321  	t.Run("GetLastRun - invalid installation", func(t *testing.T) {
   322  		_, err := cp.GetLastRun(context.Background(), "dev", "missing")
   323  		require.ErrorIs(t, err, ErrNotFound{})
   324  	})
   325  }
   326  
   327  func TestInstallationStorageProvider_Results(t *testing.T) {
   328  	cp := generateInstallationData(t)
   329  	defer cp.Close()
   330  
   331  	barRuns, resultsMap, err := cp.ListRuns(context.Background(), "dev", "bar")
   332  	require.NoError(t, err, "ListRuns failed")
   333  	require.Len(t, barRuns, 1, "expected 1 claim")
   334  	require.Len(t, resultsMap, 1, "expected 1 claim")
   335  	runID := barRuns[0].ID // this claim has multiple results
   336  
   337  	t.Run("ListResults", func(t *testing.T) {
   338  		results, err := cp.ListResults(context.Background(), runID)
   339  		require.NoError(t, err, "ListResults failed")
   340  		assert.Len(t, results, 2, "expected 2 results")
   341  		assert.Len(t, resultsMap[runID], 2, "expected 2 results for runID in results map")
   342  	})
   343  
   344  	t.Run("GetResult", func(t *testing.T) {
   345  		results, err := cp.ListResults(context.Background(), runID)
   346  		require.NoError(t, err, "ListResults failed")
   347  
   348  		resultID := results[0].ID
   349  
   350  		r, err := cp.GetResult(context.Background(), resultID)
   351  		require.NoError(t, err, "GetResult failed")
   352  
   353  		assert.Equal(t, cnab.StatusRunning, r.Status)
   354  	})
   355  
   356  	t.Run("ReadResult - invalid result", func(t *testing.T) {
   357  		_, err := cp.GetResult(context.Background(), "missing")
   358  		require.ErrorIs(t, err, ErrNotFound{})
   359  	})
   360  }
   361  
   362  func TestInstallationStorageProvider_Outputs(t *testing.T) {
   363  	cp := generateInstallationData(t)
   364  	defer cp.Close()
   365  
   366  	fooRuns, _, err := cp.ListRuns(context.Background(), "dev", "foo")
   367  	require.NoError(t, err, "ListRuns failed")
   368  	require.NotEmpty(t, fooRuns, "expected foo to have a run")
   369  	foo := fooRuns[1]
   370  	fooResults, err := cp.ListResults(context.Background(), foo.ID) // Use foo's upgrade claim that has two outputs
   371  	require.NoError(t, err, "ListResults failed")
   372  	require.NotEmpty(t, fooResults, "expected foo to have a result")
   373  	fooResult := fooResults[0]
   374  	resultID := fooResult.ID // this result has an output
   375  
   376  	barRuns, _, err := cp.ListRuns(context.Background(), "dev", "bar")
   377  	require.NoError(t, err, "ListRuns failed")
   378  	require.Len(t, barRuns, 1, "expected bar to have a run")
   379  	barRun := barRuns[0]
   380  	barResults, err := cp.ListResults(context.Background(), barRun.ID)
   381  	require.NoError(t, err, "ReadAllResults failed")
   382  	require.NotEmpty(t, barResults, "expected bar to have a result")
   383  	barResult := barResults[0]
   384  	resultIDWithoutOutputs := barResult.ID
   385  
   386  	t.Run("ListOutputs", func(t *testing.T) {
   387  		outputs, err := cp.ListOutputs(context.Background(), resultID)
   388  		require.NoError(t, err, "ListResults failed")
   389  		assert.Len(t, outputs, 4, "expected 2 outputs")
   390  
   391  		assert.Equal(t, outputs[0].Name, resultID+"-output3")
   392  		assert.Equal(t, outputs[1].Name, cnab.OutputInvocationImageLogs)
   393  		assert.Equal(t, outputs[2].Name, "output1")
   394  		assert.Equal(t, outputs[3].Name, "output2")
   395  	})
   396  
   397  	t.Run("ListOutputs - no outputs", func(t *testing.T) {
   398  		outputs, err := cp.ListResults(context.Background(), resultIDWithoutOutputs)
   399  		require.NoError(t, err, "listing outputs for a result that doesn't have any should not result in an error")
   400  		assert.Empty(t, outputs)
   401  	})
   402  
   403  	t.Run("GetLastOutputs", func(t *testing.T) {
   404  		outputs, err := cp.GetLastOutputs(context.Background(), "dev", "foo")
   405  
   406  		require.NoError(t, err, "GetLastOutputs failed")
   407  		assert.Equal(t, 4, outputs.Len(), "wrong number of outputs identified")
   408  
   409  		gotOutput1, hasOutput1 := outputs.GetByName("output1")
   410  		assert.True(t, hasOutput1, "should have found output1")
   411  		assert.Equal(t, "upgrade output1", string(gotOutput1.Value), "did not find the most recent value for output1")
   412  
   413  		gotOutput2, hasOutput2 := outputs.GetByName("output2")
   414  		assert.True(t, hasOutput2, "should have found output2")
   415  		assert.Equal(t, "upgrade output2", string(gotOutput2.Value), "did not find the most recent value for output2")
   416  	})
   417  
   418  	t.Run("ReadLastOutputs - invalid installation", func(t *testing.T) {
   419  		outputs, err := cp.GetLastOutputs(context.Background(), "dev", "missing")
   420  		require.NoError(t, err)
   421  		assert.Equal(t, outputs.Len(), 0)
   422  	})
   423  
   424  	t.Run("GetLastOutput", func(t *testing.T) {
   425  		o, err := cp.GetLastOutput(context.Background(), "dev", "foo", "output1")
   426  
   427  		require.NoError(t, err, "GetLastOutputs failed")
   428  		assert.Equal(t, "upgrade output1", string(o.Value), "did not find the most recent value for output1")
   429  
   430  	})
   431  
   432  	t.Run("GetLastOutput - invalid installation", func(t *testing.T) {
   433  		o, err := cp.GetLastOutput(context.Background(), "dev", "missing", "output1")
   434  		require.ErrorIs(t, err, ErrNotFound{})
   435  		assert.Empty(t, o)
   436  	})
   437  
   438  	t.Run("GetLastLogs", func(t *testing.T) {
   439  		logs, hasLogs, err := cp.GetLastLogs(context.Background(), "dev", "foo")
   440  
   441  		require.NoError(t, err, "GetLastLogs failed")
   442  		assert.True(t, hasLogs, "expected logs to be found")
   443  		assert.Equal(t, "upgrade logs", logs, "did not find the most recent logs for foo")
   444  	})
   445  }