get.porter.sh/porter@v1.3.0/pkg/porter/list_test.go (about) 1 package porter 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "get.porter.sh/porter/pkg/cnab" 9 "get.porter.sh/porter/pkg/printer" 10 "get.porter.sh/porter/pkg/secrets" 11 "get.porter.sh/porter/pkg/storage" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "reflect" 16 ) 17 18 func TestNewDisplayInstallation(t *testing.T) { 19 t.Run("installation has been installed", func(t *testing.T) { 20 cp := storage.NewTestInstallationProvider(t) 21 defer cp.Close() 22 23 install := cp.CreateInstallation(storage.NewInstallation("", "wordpress"), func(i *storage.Installation) { 24 i.Status.Action = cnab.ActionUpgrade 25 i.Status.ResultStatus = cnab.StatusRunning 26 }) 27 28 _, err := cp.GetInstallation(context.Background(), "", "wordpress") 29 require.NoError(t, err, "ReadInstallation failed") 30 31 displayInstall := NewDisplayInstallation(install) 32 33 require.Equal(t, displayInstall.Name, install.Name, "invalid installation name") 34 require.Equal(t, displayInstall.Status.Created, install.Status.Created, "invalid created time") 35 require.Equal(t, displayInstall.Status.Modified, install.Status.Modified, "invalid modified time") 36 require.Equal(t, cnab.ActionUpgrade, displayInstall.Status.Action, "invalid last action") 37 require.Equal(t, cnab.StatusRunning, displayInstall.Status.ResultStatus, "invalid last status") 38 }) 39 40 t.Run("installation has not been installed", func(t *testing.T) { 41 cp := storage.NewTestInstallationProvider(t) 42 defer cp.Close() 43 44 install := cp.CreateInstallation(storage.NewInstallation("", "wordpress")) 45 46 _, err := cp.GetInstallation(context.Background(), "", "wordpress") 47 require.NoError(t, err, "GetInst failed") 48 49 displayInstall := NewDisplayInstallation(install) 50 51 require.Equal(t, displayInstall.Name, install.Name, "invalid installation name") 52 require.Equal(t, install.Status.Created, displayInstall.Status.Created, "invalid created time") 53 require.Equal(t, install.Status.Modified, displayInstall.Status.Modified, "invalid modified time") 54 require.Empty(t, displayInstall.Status.Action, "invalid last action") 55 require.Empty(t, displayInstall.Status.ResultStatus, "invalid last status") 56 }) 57 } 58 59 func TestPorter_ListInstallations(t *testing.T) { 60 ctx := context.Background() 61 p := NewTestPorter(t) 62 defer p.Close() 63 64 i1 := storage.NewInstallation("", "shared-mysql") 65 i1.Parameters.Parameters = []secrets.SourceMap{ // Define a parameter that is stored in a secret, list should not retrieve it 66 {Name: "password", Source: secrets.Source{Strategy: "secret", Hint: "mypassword"}}, 67 } 68 i1.Status.RunID = "10" // Add a run but don't populate the data for it, list should not retrieve it 69 70 p.TestInstallations.CreateInstallation(i1) 71 p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "carolyn-wordpress")) 72 p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "vaughn-wordpress")) 73 p.TestInstallations.CreateInstallation(storage.NewInstallation("test", "staging-wordpress")) 74 p.TestInstallations.CreateInstallation(storage.NewInstallation("test", "iat-wordpress")) 75 p.TestInstallations.CreateInstallation(storage.NewInstallation("test", "shared-mysql")) 76 77 t.Run("all-namespaces", func(t *testing.T) { 78 opts := ListOptions{AllNamespaces: true} 79 results, err := p.ListInstallations(ctx, opts) 80 require.NoError(t, err) 81 assert.Len(t, results, 6) 82 83 // Check that porter didn't go off and retrieve extra data for each installation 84 for _, r := range results { 85 assert.Empty(t, r.ResolvedParameters, "ListInstallations should not resolve secrets used by the installations") 86 } 87 }) 88 89 t.Run("local namespace", func(t *testing.T) { 90 opts := ListOptions{Namespace: "dev"} 91 results, err := p.ListInstallations(ctx, opts) 92 require.NoError(t, err) 93 assert.Len(t, results, 2) 94 95 opts = ListOptions{Namespace: "test"} 96 results, err = p.ListInstallations(ctx, opts) 97 require.NoError(t, err) 98 assert.Len(t, results, 3) 99 }) 100 101 t.Run("global namespace", func(t *testing.T) { 102 opts := ListOptions{Namespace: ""} 103 results, err := p.ListInstallations(ctx, opts) 104 require.NoError(t, err) 105 assert.Len(t, results, 1) 106 }) 107 108 t.Run("empty namespace", func(t *testing.T) { 109 opts := ListOptions{Namespace: "nonexistent"} 110 results, err := p.ListInstallations(ctx, opts) 111 require.NoError(t, err) 112 assert.Len(t, results, 0) 113 assert.NotNil(t, results) 114 }) 115 } 116 117 func TestPorter_ListInstallationsWithFieldSelector(t *testing.T) { 118 ctx := context.Background() 119 p := NewTestPorter(t) 120 defer p.Close() 121 122 i1 := storage.NewInstallation("", "installation1") 123 i1.Bundle.Version = "0.2.0" 124 i1.Status.Action = "install" 125 i1.Status.ResultStatus = "succeeded" 126 127 i2 := storage.NewInstallation("", "installation2") 128 i2.Bundle.Version = "0.2.1" 129 i2.Status.Action = "install" 130 i2.Status.ResultStatus = "succeeded" 131 132 i3 := storage.NewInstallation("", "installation3") 133 i3.Bundle.Version = "0.2.0" 134 i3.Status.Action = "install" 135 i3.Status.ResultStatus = "failed" 136 137 i4 := storage.NewInstallation("", "installation4") 138 i4.Bundle.Version = "0.2.5" 139 i4.Status.Action = "install" 140 141 i5 := storage.NewInstallation("", "installation5") 142 i5.Bundle.Version = "0.1.0" 143 i5.Status.Action = "install" 144 i5.Status.ResultStatus = "succeeded" 145 146 p.TestInstallations.CreateInstallation(i1) 147 p.TestInstallations.CreateInstallation(i2) 148 p.TestInstallations.CreateInstallation(i3) 149 p.TestInstallations.CreateInstallation(i4) 150 p.TestInstallations.CreateInstallation(i5) 151 152 t.Run("blank field selector", func(t *testing.T) { 153 opts := ListOptions{FieldSelector: ""} 154 results, err := p.ListInstallations(ctx, opts) 155 require.NoError(t, err) 156 assert.Len(t, results, 5) 157 }) 158 159 t.Run("top level field selectors", func(t *testing.T) { 160 opts := ListOptions{FieldSelector: "name=installation1"} 161 results, err := p.ListInstallations(ctx, opts) 162 require.NoError(t, err) 163 assert.Len(t, results, 1) 164 }) 165 166 t.Run("multi level field selectors", func(t *testing.T) { 167 opts := ListOptions{FieldSelector: "name=installation1,bundle.version=0.2.0,status.action=install,status.resultStatus=succeeded"} 168 results, err := p.ListInstallations(ctx, opts) 169 require.NoError(t, err) 170 assert.Len(t, results, 1) 171 172 opts = ListOptions{FieldSelector: "status.action=install,status.resultStatus=succeeded"} 173 results, err = p.ListInstallations(ctx, opts) 174 require.NoError(t, err) 175 assert.Len(t, results, 3) 176 177 opts = ListOptions{FieldSelector: "status.resultStatus=failed"} 178 results, err = p.ListInstallations(ctx, opts) 179 require.NoError(t, err) 180 assert.Len(t, results, 1) 181 182 opts = ListOptions{FieldSelector: "status.resultStatus"} 183 results, err = p.ListInstallations(ctx, opts) 184 require.Error(t, err) 185 assert.Len(t, results, 0) 186 187 opts = ListOptions{FieldSelector: "status.resultStatus=xyz"} 188 results, err = p.ListInstallations(ctx, opts) 189 require.NoError(t, err) 190 assert.Len(t, results, 0) 191 192 }) 193 } 194 195 func TestDisplayInstallation_ConvertToInstallation(t *testing.T) { 196 cp := storage.NewTestInstallationProvider(t) 197 defer cp.Close() 198 199 install := cp.CreateInstallation(storage.NewInstallation("", "wordpress"), func(i *storage.Installation) { 200 i.Status.Action = cnab.ActionUpgrade 201 i.Status.ResultStatus = cnab.StatusRunning 202 }) 203 204 _, err := cp.GetInstallation(context.Background(), "", "wordpress") 205 require.NoError(t, err, "ReadInstallation failed") 206 207 displayInstall := NewDisplayInstallation(install) 208 209 convertedInstallation, err := displayInstall.ConvertToInstallation() 210 require.NoError(t, err, "failed to convert display installation to installation record") 211 212 require.Equal(t, install.SchemaVersion, convertedInstallation.SchemaVersion, "invalid schema version") 213 require.Equal(t, install.Name, convertedInstallation.Name, "invalid installation name") 214 require.Equal(t, install.Namespace, convertedInstallation.Namespace, "invalid installation namespace") 215 require.Equal(t, install.Uninstalled, convertedInstallation.Uninstalled, "invalid installation uninstalled status") 216 require.Equal(t, install.Bundle.Digest, convertedInstallation.Bundle.Digest, "invalid installation bundle") 217 218 require.Equal(t, len(install.Labels), len(convertedInstallation.Labels)) 219 for key := range displayInstall.Labels { 220 require.Equal(t, install.Labels[key], convertedInstallation.Labels[key], "invalid installation labels") 221 } 222 223 require.Equal(t, install.Custom, convertedInstallation.Custom, "invalid installation custom") 224 225 require.Equal(t, convertedInstallation.CredentialSets, install.CredentialSets, "invalid credential set") 226 require.Equal(t, convertedInstallation.ParameterSets, install.ParameterSets, "invalid parameter set") 227 228 require.Equal(t, install.Parameters.String(), convertedInstallation.Parameters.String(), "invalid parameters name") 229 require.Equal(t, len(install.Parameters.Parameters), len(convertedInstallation.Parameters.Parameters)) 230 231 parametersMap := make(map[string]secrets.SourceMap, len(install.Parameters.Parameters)) 232 for _, param := range install.Parameters.Parameters { 233 parametersMap[param.Name] = param 234 } 235 236 for _, param := range convertedInstallation.Parameters.Parameters { 237 expected := parametersMap[param.Name] 238 require.Equal(t, expected.ResolvedValue, param.ResolvedValue) 239 expectedSource, err := expected.Source.MarshalJSON() 240 require.NoError(t, err) 241 source, err := param.Source.MarshalJSON() 242 require.NoError(t, err) 243 require.Equal(t, expectedSource, source) 244 } 245 246 require.Equal(t, install.Status.Created, convertedInstallation.Status.Created, "invalid created time") 247 require.Equal(t, install.Status.Modified, convertedInstallation.Status.Modified, "invalid modified time") 248 require.Equal(t, cnab.ActionUpgrade, convertedInstallation.Status.Action, "invalid last action") 249 require.Equal(t, cnab.StatusRunning, convertedInstallation.Status.ResultStatus, "invalid last status") 250 251 } 252 253 func TestPorter_PrintInstallations(t *testing.T) { 254 t.Parallel() 255 256 testcases := []struct { 257 name string 258 format printer.Format 259 outputFile string 260 }{ 261 {name: "plain", format: printer.FormatPlaintext, outputFile: "testdata/list/expected-output.txt"}, 262 {name: "no reference, plain", format: printer.FormatPlaintext, outputFile: "testdata/list/no-reference-expected-output.txt"}, 263 {name: "json", format: printer.FormatJson, outputFile: "testdata/list/expected-output.json"}, 264 {name: "yaml", format: printer.FormatYaml, outputFile: "testdata/list/expected-output.yaml"}, 265 } 266 267 for _, tc := range testcases { 268 t.Run(tc.name, func(t *testing.T) { 269 p := NewTestPorter(t) 270 defer p.Close() 271 272 opts := ListOptions{ 273 Namespace: "dev", 274 Name: "mywordpress", 275 PrintOptions: printer.PrintOptions{ 276 Format: tc.format, 277 }, 278 } 279 280 p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues, func(i *storage.Installation) { 281 i.Status.BundleVersion = "v1.2.3" 282 i.Status.ResultStatus = cnab.StatusSucceeded 283 }) 284 285 ctx := context.Background() 286 287 err := p.PrintInstallations(ctx, opts) 288 require.NoError(t, err, "PrintInstallation failed") 289 p.CompareGoldenFile(tc.outputFile, p.TestConfig.TestContext.GetOutput()) 290 }) 291 } 292 } 293 294 func TestPorter_getDisplayInstallationState(t *testing.T) { 295 p := NewTestPorter(t) 296 defer p.Close() 297 298 installation := p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues) 299 displayInstallationState := getDisplayInstallationState(installation) 300 require.Equal(t, StateDefined, displayInstallationState) 301 302 bun := cnab.ExtendedBundle{} 303 run := p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall, bun), p.TestInstallations.SetMutableRunValues) 304 result := p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) 305 installation.ApplyResult(run, result) 306 installTime := now.Add(-time.Second * 5) 307 installation.Status.Installed = &installTime 308 displayInstallationState = getDisplayInstallationState(installation) 309 require.Equal(t, StateInstalled, displayInstallationState) 310 311 run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUninstall, bun), p.TestInstallations.SetMutableRunValues) 312 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) 313 installation.ApplyResult(run, result) 314 installation.Status.Uninstalled = &now 315 displayInstallationState = getDisplayInstallationState(installation) 316 require.Equal(t, StateUninstalled, displayInstallationState) 317 } 318 319 func TestPorter_getDisplayInstallationStatus(t *testing.T) { 320 p := NewTestPorter(t) 321 defer p.Close() 322 323 installation := p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues) 324 bun := cnab.ExtendedBundle{} 325 run := p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall, bun), p.TestInstallations.SetMutableRunValues) 326 result := p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) 327 installation.ApplyResult(run, result) 328 displayInstallationStatus := getDisplayInstallationStatus(installation) 329 require.Equal(t, cnab.StatusSucceeded, displayInstallationStatus) 330 331 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusFailed), p.TestInstallations.SetMutableResultValues) 332 installation.ApplyResult(run, result) 333 displayInstallationStatus = getDisplayInstallationStatus(installation) 334 require.Equal(t, cnab.StatusFailed, displayInstallationStatus) 335 336 run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall, bun), p.TestInstallations.SetMutableRunValues) 337 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) 338 installation.ApplyResult(run, result) 339 displayInstallationStatus = getDisplayInstallationStatus(installation) 340 require.Equal(t, StatusInstalling, displayInstallationStatus) 341 342 run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUninstall, bun), p.TestInstallations.SetMutableRunValues) 343 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) 344 installation.ApplyResult(run, result) 345 displayInstallationStatus = getDisplayInstallationStatus(installation) 346 require.Equal(t, StatusUninstalling, displayInstallationStatus) 347 348 run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUpgrade, bun), p.TestInstallations.SetMutableRunValues) 349 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) 350 installation.ApplyResult(run, result) 351 displayInstallationStatus = getDisplayInstallationStatus(installation) 352 require.Equal(t, StatusUpgrading, displayInstallationStatus) 353 354 run = p.TestInstallations.CreateRun(installation.NewRun("customaction", bun), p.TestInstallations.SetMutableRunValues) 355 result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) 356 installation.ApplyResult(run, result) 357 installation.Status.Action = "customaction" 358 displayInstallationStatus = getDisplayInstallationStatus(installation) 359 require.Equal(t, "running customaction", displayInstallationStatus) 360 } 361 362 func Test_parseFieldSelector(t *testing.T) { 363 tests := []struct { 364 name string 365 fieldSelector string 366 want map[string]string 367 wantErr bool 368 expectedErrMsg string 369 }{ 370 { 371 name: "valid field selector", 372 fieldSelector: "bundle.version=0.2.0,status.action=install", 373 want: map[string]string{ 374 "bundle.version": "0.2.0", 375 "status.action": "install", 376 }, 377 wantErr: false, 378 }, 379 { 380 name: "invalid field selector", 381 fieldSelector: "bundle.version=0.2.0,status.action", 382 want: nil, 383 wantErr: true, 384 expectedErrMsg: "invalid field selector: bundle.version=0.2.0,status.action", 385 }, 386 } 387 for _, tt := range tests { 388 t.Run(tt.name, func(t *testing.T) { 389 got, err := parseFieldSelector(tt.fieldSelector) 390 if (err != nil) != tt.wantErr { 391 t.Errorf("parseFieldSelector() error = %v, wantErr %v", err, tt.wantErr) 392 return 393 } 394 if !reflect.DeepEqual(got, tt.want) { 395 t.Errorf("parseFieldSelector() = %v, want %v", got, tt.want) 396 } 397 if err != nil && err.Error() != tt.expectedErrMsg { 398 t.Errorf("parseFieldSelector() error message = %v, expected error message %v", err.Error(), tt.expectedErrMsg) 399 } 400 }) 401 } 402 } 403 404 func Test_doesInstallationMatchFieldSelectors(t *testing.T) { 405 type args struct { 406 installation DisplayInstallation 407 fieldSelectorMap map[string]string 408 } 409 tests := []struct { 410 name string 411 args args 412 want bool 413 }{ 414 { 415 name: "installation matches field selectors", 416 args: args{ 417 installation: DisplayInstallation{ 418 Name: "wordpress", 419 Status: storage.InstallationStatus{ 420 Action: "install", 421 ResultStatus: "succeeded", 422 }, 423 Bundle: storage.OCIReferenceParts{ 424 Version: "0.2.0", 425 }, 426 }, 427 fieldSelectorMap: map[string]string{ 428 "name": "wordpress", 429 "status.action": "install", 430 "status.resultStatus": "succeeded", 431 "bundle.version": "0.2.0", 432 }, 433 }, 434 want: true, 435 }, 436 { 437 name: "installation matches field selectors", 438 args: args{ 439 installation: DisplayInstallation{ 440 Name: "wordpress", 441 Status: storage.InstallationStatus{ 442 Action: "install", 443 ResultStatus: "succeeded", 444 }, 445 Bundle: storage.OCIReferenceParts{ 446 Version: "0.2.1", 447 }, 448 }, 449 fieldSelectorMap: map[string]string{ 450 "name": "wordpress", 451 "status.action": "install", 452 "status.resultStatus": "succeeded", 453 "bundle.version": "0.2.0", 454 }, 455 }, 456 want: false, 457 }, 458 { 459 name: "installation matches field selectors", 460 args: args{ 461 installation: DisplayInstallation{ 462 Name: "wordpress", 463 Status: storage.InstallationStatus{ 464 Action: "install", 465 ResultStatus: "failed", 466 }, 467 Bundle: storage.OCIReferenceParts{ 468 Version: "0.2.0", 469 }, 470 }, 471 fieldSelectorMap: map[string]string{ 472 "name": "wordpress", 473 "status.action": "install", 474 "status.resultStatus": "succeeded", 475 "bundle.version": "0.2.0", 476 }, 477 }, 478 want: false, 479 }, 480 } 481 for _, tt := range tests { 482 t.Run(tt.name, func(t *testing.T) { 483 if got := doesInstallationMatchFieldSelectors(tt.args.installation, tt.args.fieldSelectorMap); got != tt.want { 484 t.Errorf("doesInstallationMatchFieldSelectors() = %v, want %v", got, tt.want) 485 } 486 }) 487 } 488 } 489 490 func Test_installationHasFieldWithValue(t *testing.T) { 491 type args struct { 492 installation DisplayInstallation 493 fieldJsonTagPath string 494 value string 495 } 496 tests := []struct { 497 name string 498 args args 499 want bool 500 }{ 501 { 502 name: "installation has field with value", 503 args: args{ 504 installation: DisplayInstallation{ 505 Name: "wordpress", 506 Status: storage.InstallationStatus{ 507 Action: "install", 508 ResultStatus: "succeeded", 509 }, 510 Bundle: storage.OCIReferenceParts{ 511 Version: "0.2.0", 512 }, 513 }, 514 fieldJsonTagPath: "name", 515 value: "wordpress", 516 }, 517 want: true, 518 }, 519 { 520 name: "installation has nested field with value", 521 args: args{ 522 installation: DisplayInstallation{ 523 Name: "wordpress", 524 Status: storage.InstallationStatus{ 525 Action: "install", 526 ResultStatus: "succeeded", 527 }, 528 Bundle: storage.OCIReferenceParts{ 529 Version: "0.2.0", 530 }, 531 }, 532 fieldJsonTagPath: "bundle.version", 533 value: "0.2.0", 534 }, 535 want: true, 536 }, 537 { 538 name: "installation does not have field with value", 539 args: args{ 540 installation: DisplayInstallation{ 541 Name: "wordpress", 542 Status: storage.InstallationStatus{ 543 Action: "install", 544 ResultStatus: "succeeded", 545 }, 546 Bundle: storage.OCIReferenceParts{ 547 Version: "0.2.0", 548 }, 549 }, 550 fieldJsonTagPath: "bundle.version", 551 value: "0.2.1", 552 }, 553 want: false, 554 }, 555 { 556 name: "installation does not have field with value", 557 args: args{ 558 installation: DisplayInstallation{ 559 Name: "wordpress", 560 Status: storage.InstallationStatus{ 561 Action: "install", 562 ResultStatus: "succeeded", 563 }, 564 Bundle: storage.OCIReferenceParts{ 565 Version: "0.2.0", 566 }, 567 }, 568 fieldJsonTagPath: "xyz", 569 value: "123", 570 }, 571 want: false, 572 }, 573 } 574 for _, tt := range tests { 575 t.Run(tt.name, func(t *testing.T) { 576 if got := installationHasFieldWithValue(tt.args.installation, tt.args.fieldJsonTagPath, tt.args.value); got != tt.want { 577 t.Errorf("installationHasFieldWithValue() = %v, want %v", got, tt.want) 578 } 579 }) 580 } 581 }