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 }