get.porter.sh/porter@v1.3.0/pkg/porter/parameters_test.go (about) 1 package porter 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "testing" 8 9 "get.porter.sh/porter/pkg/cnab" 10 "get.porter.sh/porter/pkg/config" 11 "get.porter.sh/porter/pkg/printer" 12 "get.porter.sh/porter/pkg/secrets" 13 "get.porter.sh/porter/pkg/storage" 14 "get.porter.sh/porter/pkg/test" 15 "get.porter.sh/porter/pkg/yaml" 16 "get.porter.sh/porter/tests" 17 "github.com/cnabio/cnab-go/bundle" 18 "github.com/cnabio/cnab-go/bundle/definition" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 ) 22 23 func TestDisplayValuesSort(t *testing.T) { 24 v := DisplayValues{ 25 {Name: "b"}, 26 {Name: "c"}, 27 {Name: "a"}, 28 } 29 30 sort.Sort(v) 31 32 assert.Equal(t, "a", v[0].Name) 33 assert.Equal(t, "b", v[1].Name) 34 assert.Equal(t, "c", v[2].Name) 35 } 36 37 func TestGenerateParameterSet(t *testing.T) { 38 p := NewTestPorter(t) 39 defer p.Close() 40 41 p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json") 42 43 opts := ParameterOptions{ 44 Silent: true, 45 } 46 opts.Namespace = "dev" 47 opts.Name = "kool-params" 48 opts.Labels = []string{"env=dev"} 49 opts.CNABFile = "/bundle.json" 50 ctx := context.Background() 51 52 err := opts.Validate(ctx, nil, p.Porter) 53 require.NoError(t, err, "Validate failed") 54 55 err = p.GenerateParameters(ctx, opts) 56 require.NoError(t, err, "no error should have existed") 57 creds, err := p.Parameters.GetParameterSet(ctx, opts.Namespace, "kool-params") 58 require.NoError(t, err, "expected parameter to have been generated") 59 assert.Equal(t, map[string]string{"env": "dev"}, creds.Labels) 60 } 61 62 func TestPorter_ListParameters(t *testing.T) { 63 p := NewTestPorter(t) 64 defer p.Close() 65 ctx := context.Background() 66 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("", "shared-mysql"))) 67 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("dev", "carolyn-wordpress"))) 68 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("dev", "vaughn-wordpress"))) 69 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("test", "staging-wordpress"))) 70 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("test", "iat-wordpress"))) 71 require.NoError(t, p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("test", "shared-mysql"))) 72 73 t.Run("all-namespaces", func(t *testing.T) { 74 opts := ListOptions{AllNamespaces: true} 75 results, err := p.ListParameters(ctx, opts) 76 require.NoError(t, err) 77 assert.Len(t, results, 6) 78 }) 79 80 t.Run("local namespace", func(t *testing.T) { 81 opts := ListOptions{Namespace: "dev"} 82 results, err := p.ListParameters(ctx, opts) 83 require.NoError(t, err) 84 assert.Len(t, results, 2) 85 86 opts = ListOptions{Namespace: "test"} 87 results, err = p.ListParameters(ctx, opts) 88 require.NoError(t, err) 89 assert.Len(t, results, 3) 90 }) 91 92 t.Run("global namespace", func(t *testing.T) { 93 opts := ListOptions{Namespace: ""} 94 results, err := p.ListParameters(ctx, opts) 95 require.NoError(t, err) 96 assert.Len(t, results, 1) 97 }) 98 } 99 100 func Test_loadParameters_paramNotDefined(t *testing.T) { 101 t.Parallel() 102 103 r := NewTestPorter(t) 104 defer r.Close() 105 106 b := cnab.NewBundle(bundle.Bundle{ 107 Parameters: map[string]bundle.Parameter{}, 108 }) 109 110 overrides := map[string]string{ 111 "foo": "bar", 112 } 113 114 i := storage.Installation{} 115 _, err := r.finalizeParameters(context.Background(), i, b, "action", overrides) 116 require.EqualError(t, err, "parameter foo not defined in bundle") 117 } 118 119 func Test_loadParameters_definitionNotDefined(t *testing.T) { 120 t.Parallel() 121 122 r := NewTestPorter(t) 123 defer r.Close() 124 125 b := cnab.NewBundle(bundle.Bundle{ 126 Parameters: map[string]bundle.Parameter{ 127 "foo": { 128 Definition: "foo", 129 }, 130 }, 131 }) 132 133 overrides := map[string]string{ 134 "foo": "bar", 135 } 136 137 i := storage.Installation{} 138 _, err := r.finalizeParameters(context.Background(), i, b, "action", overrides) 139 require.EqualError(t, err, "definition foo not defined in bundle") 140 } 141 142 func Test_loadParameters_applyTo(t *testing.T) { 143 t.Parallel() 144 145 r := NewTestPorter(t) 146 defer r.Close() 147 148 // Here we set default values, but expect nil/empty 149 // values for parameters that do not apply to a given action 150 b := cnab.NewBundle(bundle.Bundle{ 151 Definitions: definition.Definitions{ 152 "foo": &definition.Schema{ 153 Type: "string", 154 Default: "default-foo-value", 155 }, 156 "bar": &definition.Schema{ 157 Type: "integer", 158 Default: "default-bar-value", 159 }, 160 "true": &definition.Schema{ 161 Type: "boolean", 162 Default: "default-true-value", 163 }, 164 }, 165 Parameters: map[string]bundle.Parameter{ 166 "foo": { 167 Definition: "foo", 168 ApplyTo: []string{ 169 "action", 170 }, 171 }, 172 "bar": { 173 Definition: "bar", 174 }, 175 "true": { 176 Definition: "true", 177 ApplyTo: []string{ 178 "different-action", 179 }, 180 }, 181 }, 182 }) 183 184 overrides := map[string]string{ 185 "foo": "FOO", 186 "bar": "456", 187 "true": "false", 188 } 189 190 i := storage.Installation{} 191 params, err := r.finalizeParameters(context.Background(), i, b, "action", overrides) 192 require.NoError(t, err) 193 194 require.Equal(t, "FOO", params["foo"], "expected param 'foo' to be updated") 195 require.EqualValues(t, 456, params["bar"], "expected param 'bar' to be updated") 196 require.Equal(t, nil, params["true"], "expected param 'true' to be nil as it does not apply") 197 } 198 199 func Test_loadParameters_applyToBundleDefaults(t *testing.T) { 200 t.Parallel() 201 202 r := NewTestPorter(t) 203 defer r.Close() 204 205 b := cnab.NewBundle(bundle.Bundle{ 206 Definitions: definition.Definitions{ 207 "foo": &definition.Schema{ 208 Type: "string", 209 Default: "foo-default", 210 }, 211 }, 212 Parameters: map[string]bundle.Parameter{ 213 "foo": { 214 Definition: "foo", 215 ApplyTo: []string{ 216 "different-action", 217 }, 218 }, 219 }, 220 }) 221 222 i := storage.Installation{} 223 params, err := r.finalizeParameters(context.Background(), i, b, "action", nil) 224 require.NoError(t, err) 225 226 require.Equal(t, nil, params["foo"], "expected param 'foo' to be nil, regardless of the bundle default, as it does not apply") 227 } 228 229 func Test_loadParameters_requiredButDoesNotApply(t *testing.T) { 230 t.Parallel() 231 232 r := NewTestPorter(t) 233 defer r.Close() 234 235 b := cnab.NewBundle(bundle.Bundle{ 236 Definitions: definition.Definitions{ 237 "foo": &definition.Schema{ 238 Type: "string", 239 }, 240 }, 241 Parameters: map[string]bundle.Parameter{ 242 "foo": { 243 Definition: "foo", 244 ApplyTo: []string{ 245 "different-action", 246 }, 247 Required: true, 248 }, 249 }, 250 }) 251 252 i := storage.Installation{} 253 params, err := r.finalizeParameters(context.Background(), i, b, "action", nil) 254 require.NoError(t, err) 255 256 require.Equal(t, nil, params["foo"], "expected param 'foo' to be nil, regardless of claim value, as it does not apply") 257 } 258 259 func Test_loadParameters_fileParameter(t *testing.T) { 260 t.Parallel() 261 262 r := NewTestPorter(t) 263 defer r.Close() 264 265 r.TestConfig.TestContext.AddTestFile("testdata/file-param", "/path/to/file") 266 267 b := cnab.NewBundle(bundle.Bundle{ 268 RequiredExtensions: []string{ 269 cnab.FileParameterExtensionKey, 270 }, 271 Definitions: definition.Definitions{ 272 "foo": &definition.Schema{ 273 Type: "string", 274 ContentEncoding: "base64", 275 }, 276 }, 277 Parameters: map[string]bundle.Parameter{ 278 "foo": { 279 Definition: "foo", 280 Required: true, 281 Destination: &bundle.Location{ 282 Path: "/tmp/foo", 283 }, 284 }, 285 }, 286 }) 287 288 overrides := map[string]string{ 289 "foo": "/path/to/file", 290 } 291 292 i := storage.Installation{} 293 params, err := r.finalizeParameters(context.Background(), i, b, "action", overrides) 294 require.NoError(t, err) 295 296 require.Equal(t, "SGVsbG8gV29ybGQh", params["foo"], "expected param 'foo' to be the base64-encoded file contents") 297 } 298 299 func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) { 300 t.Parallel() 301 302 t.Run("nothing present, use default", func(t *testing.T) { 303 t.Parallel() 304 305 r := NewTestPorter(t) 306 defer r.Close() 307 308 r.TestParameters.AddTestParameters("testdata/paramset.json") 309 r.TestParameters.AddSecret("foo_secret", "foo_set") 310 311 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 312 b, err := cnab.LoadBundle(r.Context, "bundle.json") 313 require.NoError(t, err, "ProcessBundle failed") 314 315 i := storage.Installation{InstallationSpec: storage.InstallationSpec{Name: "mybun"}} 316 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, nil) 317 require.NoError(t, err) 318 assert.Equal(t, "foo_default", params["foo"], 319 "expected param 'foo' to have default value") 320 }) 321 322 t.Run("only override present", func(t *testing.T) { 323 t.Parallel() 324 325 r := NewTestPorter(t) 326 defer r.Close() 327 328 r.TestParameters.AddTestParameters("testdata/paramset.json") 329 r.TestParameters.AddSecret("foo_secret", "foo_set") 330 331 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 332 b, err := cnab.LoadBundle(r.Context, "bundle.json") 333 require.NoError(t, err, "ProcessBundle failed") 334 335 overrides := map[string]string{ 336 "foo": "foo_override", 337 } 338 339 i := storage.Installation{InstallationSpec: storage.InstallationSpec{Name: "mybun"}} 340 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, overrides) 341 require.NoError(t, err) 342 assert.Equal(t, "foo_override", params["foo"], 343 "expected param 'foo' to have override value") 344 }) 345 346 t.Run("only parameter source present", func(t *testing.T) { 347 t.Parallel() 348 349 r := NewTestPorter(t) 350 defer r.Close() 351 352 r.TestParameters.AddTestParameters("testdata/paramset.json") 353 r.TestParameters.AddSecret("foo_secret", "foo_set") 354 355 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 356 b, err := cnab.LoadBundle(r.Context, "bundle.json") 357 require.NoError(t, err, "ProcessBundle failed") 358 359 i := r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun")) 360 c := r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, b), func(r *storage.Run) { r.Bundle = b.Bundle }) 361 cr := r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 362 r.TestInstallations.CreateOutput(cr.NewOutput("foo", []byte("foo_source"))) 363 364 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, nil) 365 require.NoError(t, err) 366 assert.Equal(t, "foo_source", params["foo"], 367 "expected param 'foo' to have parameter source value") 368 }) 369 370 t.Run("override > parameter source", func(t *testing.T) { 371 t.Parallel() 372 373 r := NewTestPorter(t) 374 defer r.Close() 375 376 r.TestParameters.AddTestParameters("testdata/paramset.json") 377 r.TestParameters.AddSecret("foo_secret", "foo_set") 378 379 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 380 b, err := cnab.LoadBundle(r.Context, "bundle.json") 381 require.NoError(t, err, "ProcessBundle failed") 382 383 overrides := map[string]string{ 384 "foo": "foo_override", 385 } 386 387 i := r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun")) 388 c := r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, b)) 389 cr := r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 390 r.TestInstallations.CreateOutput(cr.NewOutput("foo", []byte("foo_source"))) 391 392 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, overrides) 393 require.NoError(t, err) 394 assert.Equal(t, "foo_override", params["foo"], 395 "expected param 'foo' to have parameter override value") 396 }) 397 398 t.Run("dependency output without type", func(t *testing.T) { 399 t.Parallel() 400 401 r := NewTestPorter(t) 402 defer r.Close() 403 404 r.TestParameters.AddTestParameters("testdata/paramset.json") 405 r.TestParameters.AddSecret("foo_secret", "foo_set") 406 407 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 408 b, err := cnab.LoadBundle(r.Context, "bundle.json") 409 require.NoError(t, err, "ProcessBundle failed") 410 411 i := r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun-mysql")) 412 c := r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, b), func(r *storage.Run) { r.Bundle = b.Bundle }) 413 cr := r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 414 r.TestInstallations.CreateOutput(cr.NewOutput("connstr", []byte("connstr value"))) 415 416 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, nil) 417 require.NoError(t, err) 418 assert.Equal(t, "connstr value", params["connstr"], 419 "expected param 'connstr' to have parameter value from the untyped dependency output") 420 }) 421 422 t.Run("merge parameter values", func(t *testing.T) { 423 t.Parallel() 424 425 r := NewTestPorter(t) 426 defer r.Close() 427 428 r.TestParameters.AddTestParameters("testdata/paramset.json") 429 r.TestParameters.AddSecret("foo_secret", "foo_set") 430 431 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 432 b, err := cnab.LoadBundle(r.Context, "bundle.json") 433 require.NoError(t, err, "ProcessBundle failed") 434 435 // foo is set by a user 436 // bar is set by a parameter source 437 // baz is set by the bundle default 438 i := r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun")) 439 c := r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, b)) 440 cr := r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 441 r.TestInstallations.CreateOutput(cr.NewOutput("foo", []byte("foo_source"))) 442 r.TestInstallations.CreateOutput(cr.NewOutput("bar", []byte("bar_source"))) 443 r.TestInstallations.CreateOutput(cr.NewOutput("baz", []byte("baz_source"))) 444 445 overrides := map[string]string{"foo": "foo_override"} 446 params, err := r.finalizeParameters(context.Background(), i, b, cnab.ActionUpgrade, overrides) 447 require.NoError(t, err) 448 assert.Equal(t, "foo_override", params["foo"], 449 "expected param 'foo' to have parameter override value") 450 assert.Equal(t, "bar_source", params["bar"], 451 "expected param 'bar' to have parameter source value") 452 assert.Equal(t, "baz_default", params["baz"], 453 "expected param 'baz' to have bundle default value") 454 }) 455 } 456 457 func Test_ParameterTypeConversion(t *testing.T) { 458 t.Parallel() 459 setup := func() (InstallOptions, storage.Installation) { 460 bun := cnab.NewBundle(bundle.Bundle{ 461 Name: "mybuns", 462 Version: "1.0.0", 463 SchemaVersion: "v1.0.0", 464 InvocationImages: []bundle.InvocationImage{ 465 {BaseImage: bundle.BaseImage{Image: "mybuns:latest", ImageType: "docker"}}, 466 }, 467 Definitions: definition.Definitions{ 468 "string": &definition.Schema{Type: "string"}, 469 "number": &definition.Schema{Type: "number"}, 470 "integer": &definition.Schema{Type: "integer"}, 471 "boolean": &definition.Schema{Type: "boolean"}, 472 "array": &definition.Schema{Type: "array"}, 473 "object": &definition.Schema{Type: "object"}, 474 }, 475 Parameters: map[string]bundle.Parameter{ 476 "string": {Definition: "string", Required: true, Destination: &bundle.Location{Path: "str"}}, 477 "number": {Definition: "number", Required: true, Destination: &bundle.Location{Path: "num"}}, 478 "integer": {Definition: "integer", Required: true, Destination: &bundle.Location{Path: "int"}}, 479 "boolean": {Definition: "boolean", Required: true, Destination: &bundle.Location{Path: "bool"}}, 480 "array": {Definition: "array", Required: true, Destination: &bundle.Location{Path: "arr"}}, 481 "object": {Definition: "object", Required: true, Destination: &bundle.Location{Path: "obj"}}, 482 }, 483 }) 484 485 opts := NewInstallOptions() 486 opts.bundleRef = &cnab.BundleReference{Definition: bun} 487 i := storage.NewInstallation(opts.Namespace, opts.Name) 488 return opts, i 489 } 490 491 testcases := []struct { 492 name string 493 parameterSet string 494 params []string 495 }{ 496 { 497 name: "command line parameters", 498 params: []string{ 499 "string=abc123", 500 "number=3.1415", 501 "integer=3", 502 "boolean=true", 503 `array=[1, "a", 3.1415, [2, "b"], {"c": 3}, false]`, 504 `object={"items": ["a", "b", "c", 1, 2, 3], "valid": true }`, 505 }, 506 }, 507 { 508 name: "native parameter set", 509 parameterSet: "testdata/paramset-native.json", 510 }, 511 { 512 name: "stringified parameter set", 513 parameterSet: "testdata/paramset-stringified.json", 514 }, 515 } 516 517 for _, tc := range testcases { 518 tc := tc 519 t.Run(tc.name, func(t *testing.T) { 520 t.Parallel() 521 522 p := NewTestPorter(t) 523 defer p.Close() 524 525 opts, i := setup() 526 opts.Params = tc.params 527 if tc.parameterSet != "" { 528 p.TestParameters.AddTestParameters(tc.parameterSet) 529 opts.ParameterSets = []string{"mypset"} 530 } 531 532 err := p.applyActionOptionsToInstallation(context.Background(), opts, &i) 533 require.NoError(t, err) 534 finalParams := opts.GetParameters() 535 536 expectedParams := map[string]interface{}{ 537 "string": "abc123", 538 "number": 3.1415, 539 "integer": 3, 540 "boolean": true, 541 "array": []interface{}{ 542 1.0, "a", 3.1415, []interface{}{2.0, "b"}, map[string]interface{}{"c": 3.0}, false, 543 }, 544 "object": map[string]interface{}{ 545 "items": []interface{}{"a", "b", "c", 1.0, 2.0, 3.0}, 546 "valid": true, 547 }, 548 } 549 550 for name, expected := range expectedParams { 551 actual, ok := finalParams[name] 552 require.True(t, ok, "Parameter %s not found", name) 553 assert.Equal(t, expected, actual) 554 } 555 }) 556 } 557 } 558 559 // This is intended to cover the matrix of cases around parameter value resolution. 560 // It exercises the matrix for all supported actions. 561 func Test_Paramapalooza(t *testing.T) { 562 testcases := []struct { 563 Name string 564 Required bool 565 Provided bool 566 DefaultExists bool 567 AppliesToAction bool 568 ExpectedVal interface{} 569 ExpectedErr string 570 }{ 571 // Are you ready to enter the Matrix? 572 {Name: "required, provided, default exists, applies to action", 573 Required: true, Provided: true, DefaultExists: true, AppliesToAction: true, ExpectedVal: "my-param-value", 574 }, 575 {"required, provided, default exists, does not apply to action", 576 true, true, true, false, nil, "", 577 }, 578 {"required, provided, default does not exist, applies to action", 579 true, true, false, true, "my-param-value", "", 580 }, 581 {"required, provided, default does not exist, does not apply to action", 582 true, true, false, false, nil, "", 583 }, 584 // As of writing, bundle.ValuesOrDefaults in cnab-go requires a specific override 585 // be provided if applicable to an action. 586 // Otherwise, it errors out and does not look up/use default in this case. 587 {"required, not provided, default exists, applies to action", 588 true, false, true, true, nil, "parameter \"my-param\" is required", 589 }, 590 {"required, not provided, default exists, does not apply to action", 591 true, false, true, false, nil, "", 592 }, 593 {"required, not provided, default does not exist, applies to action", 594 true, false, false, true, nil, "parameter \"my-param\" is required", 595 }, 596 {"required, not provided, default does not exist, does not apply to action", 597 true, false, false, false, nil, "", 598 }, 599 {"not required, provided, default exists, applies to action", 600 false, true, true, true, "my-param-value", "", 601 }, 602 {"not required, provided, default exists, does not apply to action", 603 false, true, true, false, nil, "", 604 }, 605 {"not required, provided, default does not exist, applies to action", 606 false, true, false, true, "my-param-value", "", 607 }, 608 {"not required, provided, default does not exist, does not apply to action", 609 false, true, false, false, nil, "", 610 }, 611 {"not required, not provided, default exists, applies to action", 612 false, false, true, true, "my-param-default", "", 613 }, 614 {"not required, not provided, default exists, does not apply to action", 615 false, false, true, false, nil, "", 616 }, 617 {"not required, not provided, default does not exist, applies to action", 618 false, false, false, true, nil, "", 619 }, 620 {"not required, not provided, default does not exist, does not apply to action", 621 false, false, false, false, nil, "", 622 }, 623 } 624 625 for _, tc := range testcases { 626 tc := tc 627 t.Run(tc.Name, func(t *testing.T) { 628 t.Parallel() 629 actions := []string{"install", "upgrade", "uninstall", "zombies"} 630 for _, action := range actions { 631 t.Run(action, func(t *testing.T) { 632 633 r := NewTestPorter(t) 634 defer r.Close() 635 636 bun := cnab.NewBundle(bundle.Bundle{ 637 Name: "mybuns", 638 Version: "1.0.0", 639 SchemaVersion: "v1.0.0", 640 Actions: map[string]bundle.Action{ 641 "zombies": { 642 Modifies: true, 643 }, 644 }, 645 InvocationImages: []bundle.InvocationImage{ 646 { 647 BaseImage: bundle.BaseImage{ 648 Image: "mybuns:latest", 649 ImageType: "docker", 650 }, 651 }, 652 }, 653 Definitions: definition.Definitions{ 654 "my-param": &definition.Schema{ 655 Type: "string", 656 }, 657 }, 658 Parameters: map[string]bundle.Parameter{ 659 "my-param": { 660 Definition: "my-param", 661 Required: tc.Required, 662 Destination: &bundle.Location{ 663 EnvironmentVariable: "MY_PARAM", 664 }, 665 }, 666 }, 667 }) 668 669 if tc.DefaultExists { 670 bun.Definitions["my-param"].Default = "my-param-default" 671 } 672 673 if !tc.AppliesToAction { 674 param := bun.Parameters["my-param"] 675 param.ApplyTo = []string{"non-applicable-action"} 676 bun.Parameters["my-param"] = param 677 } 678 679 i := storage.Installation{InstallationSpec: storage.InstallationSpec{Name: "test"}} 680 overrides := map[string]string{} 681 // If param is provided (via --param/--param-file) 682 // it will be attached to args 683 if tc.Provided { 684 overrides["my-param"] = "my-param-value" 685 } 686 687 resolvedParams, err := r.finalizeParameters(context.Background(), i, bun, action, overrides) 688 if tc.ExpectedErr != "" { 689 require.EqualError(t, err, tc.ExpectedErr) 690 } else { 691 require.NoError(t, err) 692 assert.Equal(t, tc.ExpectedVal, resolvedParams["my-param"]) 693 } 694 }) 695 } 696 }) 697 } 698 } 699 700 func TestRuntime_ResolveParameterSources(t *testing.T) { 701 t.Parallel() 702 703 r := NewTestPorter(t) 704 defer r.Close() 705 706 r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json") 707 bun, err := cnab.LoadBundle(r.Context, "bundle.json") 708 require.NoError(t, err, "ProcessBundle failed") 709 710 i := r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun-mysql")) 711 c := r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, bun), func(r *storage.Run) { r.Bundle = bun.Bundle }) 712 cr := r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 713 r.TestInstallations.CreateOutput(cr.NewOutput("connstr", []byte("connstr value"))) 714 715 i = r.TestInstallations.CreateInstallation(storage.NewInstallation("", "mybun")) 716 c = r.TestInstallations.CreateRun(i.NewRun(cnab.ActionInstall, bun), func(r *storage.Run) { r.Bundle = bun.Bundle }) 717 cr = r.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded)) 718 r.TestInstallations.CreateOutput(cr.NewOutput("bar", []byte("bar value"))) 719 720 got, err := r.resolveParameterSources(context.Background(), bun, i) 721 require.NoError(t, err, "resolveParameterSources failed") 722 723 want := secrets.Set{ 724 "bar": "bar value", 725 "connstr": "connstr value", 726 } 727 assert.Equal(t, want, got, "resolved incorrect parameter values") 728 } 729 730 func TestShowParameters_NotFound(t *testing.T) { 731 p := NewTestPorter(t) 732 defer p.Close() 733 734 opts := ParameterShowOptions{ 735 PrintOptions: printer.PrintOptions{ 736 Format: printer.FormatPlaintext, 737 }, 738 Name: "non-existent-param", 739 } 740 741 err := p.ShowParameter(context.Background(), opts) 742 assert.ErrorIs(t, err, storage.ErrNotFound{}) 743 } 744 745 func TestShowParameters_Found(t *testing.T) { 746 type ParameterShowTest struct { 747 name string 748 format printer.Format 749 expectedOutputFile string 750 } 751 752 testcases := []ParameterShowTest{ 753 { 754 name: "json", 755 format: printer.FormatJson, 756 expectedOutputFile: "testdata/parameters/mypset.json", 757 }, 758 { 759 name: "yaml", 760 format: printer.FormatYaml, 761 expectedOutputFile: "testdata/parameters/mypset.yaml", 762 }, 763 { 764 name: "plaintext", 765 format: printer.FormatPlaintext, 766 expectedOutputFile: "testdata/parameters/mypset.txt", 767 }, 768 } 769 770 for _, tc := range testcases { 771 t.Run(tc.name, func(t *testing.T) { 772 p := NewTestPorter(t) 773 defer p.Close() 774 775 opts := ParameterShowOptions{ 776 PrintOptions: printer.PrintOptions{ 777 Format: tc.format, 778 }, 779 Name: "mypset", 780 } 781 782 p.TestParameters.AddTestParameters("testdata/paramset.json") 783 784 err := p.ShowParameter(context.Background(), opts) 785 require.NoError(t, err, "an error should not have occurred") 786 gotOutput := p.TestConfig.TestContext.GetOutput() 787 test.CompareGoldenFile(t, tc.expectedOutputFile, gotOutput) 788 }) 789 } 790 } 791 792 func TestPrintParameters(t *testing.T) { 793 p := NewTestPorter(t) 794 defer p.Close() 795 796 opts := ListOptions{ 797 PrintOptions: printer.PrintOptions{ 798 Format: printer.FormatPlaintext, 799 }, 800 Name: "mypset", 801 } 802 803 p.TestParameters.AddTestParameters("testdata/paramset.json") 804 805 err := p.PrintParameters(context.Background(), opts) 806 require.NoError(t, err, "an error should not have occurred") 807 gotOutput := p.TestConfig.TestContext.GetOutput() 808 test.CompareGoldenFile(t, "testdata/parameters/mypsettable.txt", gotOutput) 809 810 } 811 812 func TestParametersCreateOptions_Validate(t *testing.T) { 813 testcases := []struct { 814 name string 815 args []string 816 outputType string 817 wantErr string 818 }{ 819 { 820 name: "no fileName defined", 821 args: []string{}, 822 outputType: "", 823 wantErr: "", 824 }, 825 { 826 name: "two positional arguments", 827 args: []string{"parameter-set1", "parameter-set2"}, 828 outputType: "", 829 wantErr: "only one positional argument may be specified", 830 }, 831 { 832 name: "no file format defined from file extension or output flag", 833 args: []string{"parameter-set"}, 834 outputType: "", 835 wantErr: "could not detect the file format from the file extension (.txt). Specify the format with --output", 836 }, 837 { 838 name: "different file format", 839 args: []string{"parameter-set.json"}, 840 outputType: "yaml", 841 wantErr: "", 842 }, 843 { 844 name: "format from output flag", 845 args: []string{"parameters"}, 846 outputType: "json", 847 wantErr: "", 848 }, 849 { 850 name: "format from file extension", 851 args: []string{"parameter-set.yml"}, 852 outputType: "", 853 wantErr: "", 854 }, 855 } 856 857 for _, tc := range testcases { 858 t.Run(tc.name, func(t *testing.T) { 859 opts := ParameterCreateOptions{OutputType: tc.outputType} 860 err := opts.Validate(tc.args) 861 if tc.wantErr == "" { 862 require.NoError(t, err, "no error should have existed") 863 return 864 } 865 assert.Contains(t, err.Error(), tc.wantErr) 866 }) 867 } 868 } 869 870 func TestParametersCreate(t *testing.T) { 871 testcases := []struct { 872 name string 873 fileName string 874 outputType string 875 wantErr string 876 }{ 877 { 878 name: "valid input: no input defined, will output yaml format to stdout", 879 fileName: "", 880 outputType: "", 881 wantErr: "", 882 }, 883 { 884 name: "valid input: output to stdout with format json", 885 fileName: "", 886 outputType: "json", 887 wantErr: "", 888 }, 889 { 890 name: "valid input: file format from fileName", 891 fileName: "fileName.json", 892 outputType: "", 893 wantErr: "", 894 }, 895 { 896 name: "valid input: file format from outputType", 897 fileName: "fileName", 898 outputType: "json", 899 wantErr: "", 900 }, 901 { 902 name: "valid input: different file format from fileName and outputType", 903 fileName: "fileName.yaml", 904 outputType: "json", 905 wantErr: "", 906 }, 907 { 908 name: "valid input: same file format in fileName and outputType", 909 fileName: "fileName.json", 910 outputType: "json", 911 wantErr: "", 912 }, 913 { 914 name: "invalid input: invalid file format from fileName", 915 fileName: "fileName.txt", 916 outputType: "", 917 wantErr: fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json", "txt"), 918 }, 919 { 920 name: "invalid input: invalid file format from outputType", 921 fileName: "fileName", 922 outputType: "txt", 923 wantErr: fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json", "txt"), 924 }, 925 } 926 927 for _, tc := range testcases { 928 t.Run(tc.name, func(t *testing.T) { 929 p := NewTestPorter(t) 930 defer p.Close() 931 932 opts := ParameterCreateOptions{FileName: tc.fileName, OutputType: tc.outputType} 933 err := p.CreateParameter(opts) 934 if tc.wantErr == "" { 935 require.NoError(t, err, "no error should have existed") 936 return 937 } 938 assert.Contains(t, err.Error(), tc.wantErr) 939 }) 940 } 941 } 942 943 func TestPorter_ParametersApply(t *testing.T) { 944 t.Run("invalid schemaType", func(t *testing.T) { 945 // Make sure that we are validating the display parameter set values, and not just the underlying stored parameter set 946 ctx := context.Background() 947 p := NewTestPorter(t) 948 defer p.Close() 949 950 p.AddTestFile("testdata/parameters/mypset.yaml", "mypset.yaml") 951 p.TestConfig.TestContext.EditYaml("mypset.yaml", func(yq *yaml.Editor) error { 952 return yq.SetValue("schemaType", "invalidthing") 953 }) 954 955 opts := ApplyOptions{ 956 Namespace: "altns", 957 File: "mypset.yaml", 958 } 959 err := p.ParametersApply(ctx, opts) 960 tests.RequireErrorContains(t, err, "invalid schemaType") 961 }) 962 963 t.Run("valid parameter set", func(t *testing.T) { 964 ctx := context.Background() 965 p := NewTestPorter(t) 966 defer p.Close() 967 968 p.AddTestFile("testdata/parameters/mypset.yaml", "mypset.yaml") 969 p.TestConfig.TestContext.EditYaml("mypset.yaml", func(yq *yaml.Editor) error { 970 return yq.DeleteNode("namespace") 971 }) 972 973 opts := ApplyOptions{ 974 Namespace: "altns", // Import into this namespace since one isn't set in the file (we removed it above) 975 File: "mypset.yaml", 976 } 977 err := p.ParametersApply(ctx, opts) 978 require.NoError(t, err, "ParametersApply failed") 979 980 ps, err := p.Parameters.GetParameterSet(ctx, "altns", "mypset") 981 require.NoError(t, err, "Failed to retrieve applied parameter set") 982 983 assert.Equal(t, "mypset", ps.Name, "unexpected parameter set name") 984 require.Len(t, ps.Parameters, 1, "expected 1 parameter in the set") 985 assert.Equal(t, "foo", ps.Parameters[0].Name, "expected the foo parameter mapping defined") 986 assert.Equal(t, "secret", ps.Parameters[0].Source.Strategy, "expected the foo parameter mapping to come from a secret") 987 assert.Equal(t, "foo_secret", ps.Parameters[0].Source.Hint, "expected the foo parameter mapping to use foo_secret") 988 }) 989 } 990 991 func TestParameterRemovedFromBundle(t *testing.T) { 992 ctx := context.Background() 993 p := NewTestPorter(t) 994 p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml") 995 opts := InstallOptions{ 996 BundleExecutionOptions: &BundleExecutionOptions{ 997 Driver: "docker", 998 BundleReferenceOptions: &BundleReferenceOptions{ 999 installationOptions: installationOptions{ 1000 BundleDefinitionOptions: BundleDefinitionOptions{ 1001 File: config.Name, 1002 }, 1003 Name: "MyInstallation", 1004 }, 1005 }, 1006 }, 1007 } 1008 1009 installation := storage.NewInstallation(opts.Namespace, opts.Name) 1010 installation.Parameters.Parameters = make([]secrets.SourceMap, 1) 1011 installation.Parameters.Parameters[0] = secrets.SourceMap{ 1012 Name: "removedParam", 1013 Source: secrets.Source{ 1014 Strategy: "value", 1015 Hint: "1", 1016 }, 1017 } 1018 1019 err := p.applyActionOptionsToInstallation(ctx, opts, &installation) 1020 require.NoError(t, err) 1021 } 1022 1023 func Test_DependencyParameterOverride(t *testing.T) { 1024 ctx := context.Background() 1025 p := NewTestPorter(t) 1026 p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml") 1027 opts := InstallOptions{ 1028 BundleExecutionOptions: &BundleExecutionOptions{ 1029 Params: []string{ 1030 "dep#first-param=1", 1031 }, 1032 Driver: "docker", 1033 BundleReferenceOptions: &BundleReferenceOptions{ 1034 installationOptions: installationOptions{ 1035 BundleDefinitionOptions: BundleDefinitionOptions{ 1036 File: config.Name, 1037 }, 1038 Name: "MyInstallation", 1039 }, 1040 }, 1041 }, 1042 } 1043 1044 installation := storage.NewInstallation(opts.Namespace, opts.Name) 1045 err := p.applyActionOptionsToInstallation(ctx, opts, &installation) 1046 require.NoError(t, err) 1047 assert.Equal(t, opts.depParams["dep#first-param"], "1") 1048 }