get.porter.sh/porter@v1.3.0/pkg/cnab/config-adapter/adapter_test.go (about) 1 package configadapter 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "testing" 9 10 "get.porter.sh/porter/pkg/cnab" 11 depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1" 12 depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2" 13 "get.porter.sh/porter/pkg/config" 14 "get.porter.sh/porter/pkg/experimental" 15 "get.porter.sh/porter/pkg/manifest" 16 "get.porter.sh/porter/pkg/mixin" 17 "get.porter.sh/porter/pkg/pkgmgmt" 18 "github.com/cnabio/cnab-go/bundle" 19 "github.com/cnabio/cnab-go/bundle/definition" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestManifestConverter(t *testing.T) { 25 26 testcases := []struct { 27 name string 28 configHandler func(c *config.Config) 29 manifestPath string 30 goldenFile string 31 preserveTags bool 32 }{ 33 {name: "mybuns depsv1", 34 configHandler: func(c *config.Config) {}, 35 manifestPath: "tests/testdata/mybuns/porter.yaml", 36 goldenFile: "testdata/mybuns-depsv1.bundle.json", 37 preserveTags: false}, 38 {name: "mybuns depsv1 preserveTags", 39 configHandler: func(c *config.Config) {}, 40 manifestPath: "tests/testdata/mybuns/porter.yaml", 41 goldenFile: "testdata/mybuns-depsv1.bundle.preserveTags.json", 42 preserveTags: true}, 43 {name: "mybuns depsv2", 44 configHandler: func(c *config.Config) { 45 c.SetExperimentalFlags(experimental.FlagDependenciesV2) 46 }, 47 manifestPath: "tests/testdata/mybuns/porter.yaml", 48 goldenFile: "testdata/mybuns-depsv2.bundle.json", 49 preserveTags: false}, 50 {name: "mybuns depsv2 preserveTags", 51 configHandler: func(c *config.Config) { 52 c.SetExperimentalFlags(experimental.FlagDependenciesV2) 53 }, 54 manifestPath: "tests/testdata/mybuns/porter.yaml", 55 goldenFile: "testdata/mybuns-depsv2.bundle.preserveTags.json", 56 preserveTags: true}, 57 {name: "myenv depsv2", 58 configHandler: func(c *config.Config) { 59 c.SetExperimentalFlags(experimental.FlagDependenciesV2) 60 }, 61 manifestPath: "tests/testdata/myenv/porter.yaml", 62 goldenFile: "testdata/myenv-depsv2.bundle.json", 63 preserveTags: false}, 64 } 65 66 for _, tc := range testcases { 67 tc := tc 68 t.Run(tc.name, func(t *testing.T) { 69 t.Parallel() 70 71 c := config.NewTestConfig(t) 72 tc.configHandler(c.Config) 73 c.TestContext.AddTestFileFromRoot(tc.manifestPath, config.Name) 74 75 ctx := context.Background() 76 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 77 require.NoError(t, err, "could not load manifest") 78 79 installedMixins := []mixin.Metadata{ 80 {Name: "exec", VersionInfo: pkgmgmt.VersionInfo{Version: "v1.2.3"}}, 81 } 82 83 a := NewManifestConverter(c.Config, m, nil, installedMixins, tc.preserveTags) 84 85 bun, err := a.ToBundle(ctx) 86 require.NoError(t, err, "ToBundle failed") 87 88 // Compare the regular json, not the canonical, because that's hard to diff 89 prepBundleForDiff(&bun.Bundle) 90 bunD, err := json.MarshalIndent(bun, "", " ") 91 require.NoError(t, err) 92 c.TestContext.CompareGoldenFile(tc.goldenFile, string(bunD)) 93 }) 94 } 95 } 96 97 func prepBundleForDiff(b *bundle.Bundle) { 98 // Unset the digest when we are comparing test bundle files because 99 // otherwise the digest changes based on the version of the porter binary + 100 // mixins that generated it, which makes the file change a lot 101 // unnecessarily. 102 stamp := b.Custom[cnab.PorterExtension].(Stamp) 103 stamp.ManifestDigest = "" 104 b.Custom[cnab.PorterExtension] = stamp 105 } 106 107 func TestManifestConverter_ToBundle(t *testing.T) { 108 testcases := []struct { 109 name string 110 preserveTags bool 111 }{ 112 {name: "not preserving tags", preserveTags: false}, 113 {name: "preserving tags", preserveTags: true}, 114 } 115 116 for _, tc := range testcases { 117 tc := tc 118 t.Run(tc.name, func(t *testing.T) { 119 t.Parallel() 120 121 c := config.NewTestConfig(t) 122 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 123 124 ctx := context.Background() 125 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 126 require.NoError(t, err, "could not load manifest") 127 128 a := NewManifestConverter(c.Config, m, nil, nil, tc.preserveTags) 129 130 bun, err := a.ToBundle(ctx) 131 require.NoError(t, err, "ToBundle failed") 132 133 assert.Equal(t, cnab.BundleSchemaVersion(), bun.SchemaVersion) 134 assert.Equal(t, "mybuns", bun.Name) 135 assert.Equal(t, "0.1.2", bun.Version) 136 assert.Equal(t, "A very thorough test bundle", bun.Description) 137 138 stamp, err := LoadStamp(bun) 139 require.NoError(t, err, "could not load porter's stamp") 140 assert.NotNil(t, stamp) 141 142 assert.Equal(t, tc.preserveTags, stamp.PreserveTags) 143 144 assert.Contains(t, bun.Actions, "status", "custom action 'status' was not populated") 145 146 require.Len(t, bun.Credentials, 2, "expected two credentials") 147 assert.Contains(t, bun.Parameters, "porter-debug", "porter-debug parameter was not defined") 148 assert.Contains(t, bun.Definitions, "porter-debug-parameter", "porter-debug definition was not defined") 149 150 assert.True(t, bun.HasDependenciesV1(), "DependenciesV1 was not populated") 151 assert.Contains(t, bun.RequiredExtensions, "io.cnab.dependencies") 152 153 assert.NotEmpty(t, bun.Outputs, "expected multiple outputs generated") 154 }) 155 } 156 } 157 158 func TestManifestConverter_generateBundleCredentials(t *testing.T) { 159 t.Parallel() 160 161 c := config.NewTestConfig(t) 162 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 163 164 ctx := context.Background() 165 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 166 require.NoError(t, err, "could not load manifest") 167 168 a := NewManifestConverter(c.Config, m, nil, nil, false) 169 170 bun, err := a.ToBundle(ctx) 171 require.NoError(t, err, "ToBundle failed") 172 173 assert.Contains(t, bun.Credentials, "username", "credential 'username' was not populated") 174 username := bun.Credentials["username"] 175 assert.Equal(t, "The name you want on the audit log", username.Description, "credential.Description was not populated") 176 assert.False(t, username.Required, "credential.Required was not populated correctly") 177 assert.Equal(t, "ROOT_USERNAME", username.EnvironmentVariable, "credential.EnvironmentVariable was not populated") 178 179 assert.Contains(t, bun.Credentials, "password", "credential 'password' was not populated") 180 password := bun.Credentials["password"] 181 assert.True(t, password.Required, "credential.Required was not populated correctly") 182 assert.Equal(t, []string{"boom"}, password.ApplyTo, "credential.ApplyTo was not populated") 183 assert.Equal(t, "/tmp/password", password.Path, "credential.Path was not populated") 184 } 185 186 func TestManifestConverter_generateBundleParametersSchema(t *testing.T) { 187 testcases := []struct { 188 propname string 189 wantParam bundle.Parameter 190 wantDef definition.Schema 191 }{ 192 {"ainteger", 193 bundle.Parameter{ 194 Definition: "ainteger-parameter", 195 Destination: &bundle.Location{ 196 EnvironmentVariable: "AINTEGER", 197 }, 198 }, 199 definition.Schema{ 200 Type: "integer", 201 Default: 1, 202 Minimum: toFloat(0), 203 Maximum: toFloat(10), 204 }, 205 }, 206 {"anumber", 207 bundle.Parameter{ 208 Definition: "anumber-parameter", 209 Destination: &bundle.Location{ 210 EnvironmentVariable: "ANUMBER", 211 }, 212 }, 213 definition.Schema{ 214 Type: "number", 215 Default: 0.5, 216 ExclusiveMinimum: toFloat(0), 217 ExclusiveMaximum: toFloat(1), 218 }, 219 }, 220 { 221 "astringenum", 222 bundle.Parameter{ 223 Definition: "astringenum-parameter", 224 Destination: &bundle.Location{ 225 EnvironmentVariable: "ASTRINGENUM", 226 }, 227 }, 228 definition.Schema{ 229 Type: "string", 230 Default: "blue", 231 Enum: []interface{}{"blue", "red", "purple", "pink"}, 232 }, 233 }, 234 { 235 "astring", 236 bundle.Parameter{ 237 Definition: "astring-parameter", 238 Destination: &bundle.Location{ 239 EnvironmentVariable: "ASTRING", 240 }, 241 Required: true, 242 }, 243 definition.Schema{ 244 Type: "string", 245 MinLength: toInt(1), 246 MaxLength: toInt(10), 247 }, 248 }, 249 { 250 "aboolean", 251 bundle.Parameter{ 252 Definition: "aboolean-parameter", 253 Destination: &bundle.Location{ 254 EnvironmentVariable: "ABOOLEAN", 255 }, 256 }, 257 definition.Schema{ 258 Type: "boolean", 259 Default: true, 260 }, 261 }, 262 { 263 "installonly", 264 bundle.Parameter{ 265 Definition: "installonly-parameter", 266 Destination: &bundle.Location{ 267 EnvironmentVariable: "INSTALLONLY", 268 }, 269 ApplyTo: []string{ 270 "install", 271 }, 272 Required: true, 273 }, 274 definition.Schema{ 275 Type: "boolean", 276 }, 277 }, 278 { 279 "sensitive", 280 bundle.Parameter{ 281 Definition: "sensitive-parameter", 282 Destination: &bundle.Location{ 283 EnvironmentVariable: "SENSITIVE", 284 }, 285 Required: true, 286 }, 287 definition.Schema{ 288 Type: "string", 289 WriteOnly: toBool(true), 290 }, 291 }, 292 { 293 "jsonobject", 294 bundle.Parameter{ 295 Definition: "jsonobject-parameter", 296 Destination: &bundle.Location{ 297 EnvironmentVariable: "JSONOBJECT", 298 }, 299 }, 300 definition.Schema{ 301 Type: "string", 302 Default: `"myobject": { "foo": "true", "bar": [ 1, 2, 3 ] }`, 303 }, 304 }, 305 { 306 "afile", 307 bundle.Parameter{ 308 Definition: "afile-parameter", 309 Destination: &bundle.Location{ 310 Path: "/home/nonroot/.kube/config", 311 }, 312 Required: true, 313 }, 314 definition.Schema{ 315 Type: "string", 316 ContentEncoding: "base64", 317 }, 318 }, 319 { 320 "notype-file", 321 bundle.Parameter{ 322 Definition: "notype-file-parameter", 323 Destination: &bundle.Location{ 324 Path: "/cnab/app/config.toml", 325 }, 326 Required: true, 327 }, 328 definition.Schema{ 329 Type: "string", 330 ContentEncoding: "base64", 331 }, 332 }, 333 { 334 "notype-string", 335 bundle.Parameter{ 336 Definition: "notype-string-parameter", 337 Destination: &bundle.Location{ 338 EnvironmentVariable: "NOTYPE_STRING", 339 }, 340 Required: true, 341 }, 342 definition.Schema{ 343 Type: "string", 344 }, 345 }, 346 } 347 348 for _, tc := range testcases { 349 tc := tc 350 t.Run(tc.propname, func(t *testing.T) { 351 t.Parallel() 352 c := config.NewTestConfig(t) 353 c.TestContext.AddTestFile("testdata/porter-with-parameters.yaml", config.Name) 354 355 ctx := context.Background() 356 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 357 require.NoError(t, err, "could not load manifest") 358 359 a := NewManifestConverter(c.Config, m, nil, nil, false) 360 361 defs := make(definition.Definitions, len(m.Parameters)) 362 params := a.generateBundleParameters(ctx, &defs) 363 364 param, ok := params[tc.propname] 365 require.True(t, ok, "parameter definition was not generated") 366 367 def, ok := defs[param.Definition] 368 require.True(t, ok, "property definition was not generated") 369 370 assert.Equal(t, tc.wantParam, param) 371 assert.Equal(t, tc.wantDef, *def) 372 }) 373 } 374 } 375 376 func TestManifestConverter_buildDefaultPorterParameters(t *testing.T) { 377 t.Parallel() 378 379 c := config.NewTestConfig(t) 380 c.TestContext.AddTestFileFromRoot("pkg/manifest/testdata/simple.porter.yaml", config.Name) 381 382 ctx := context.Background() 383 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 384 require.NoError(t, err, "could not load manifest") 385 386 a := NewManifestConverter(c.Config, m, nil, nil, false) 387 388 defs := make(definition.Definitions, len(m.Parameters)) 389 params := a.generateBundleParameters(ctx, &defs) 390 391 debugParam, ok := params["porter-debug"] 392 assert.True(t, ok, "porter-debug parameter was not defined") 393 assert.Equal(t, "porter-debug-parameter", debugParam.Definition) 394 assert.Equal(t, "PORTER_DEBUG", debugParam.Destination.EnvironmentVariable) 395 396 debugDef, ok := defs["porter-debug-parameter"] 397 require.True(t, ok, "porter-debug definition was not defined") 398 assert.Equal(t, "boolean", debugDef.Type) 399 assert.Equal(t, false, debugDef.Default) 400 } 401 402 func TestManifestConverter_generateImages(t *testing.T) { 403 t.Parallel() 404 405 c := config.NewTestConfig(t) 406 c.TestContext.AddTestFileFromRoot("pkg/manifest/testdata/simple.porter.yaml", config.Name) 407 408 ctx := context.Background() 409 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 410 require.NoError(t, err, "could not load manifest") 411 412 a := NewManifestConverter(c.Config, m, nil, nil, false) 413 414 mappedImage := manifest.MappedImage{ 415 Description: "un petite server", 416 Repository: "getporter/myserver", 417 ImageType: "docker", 418 Digest: "abc123", 419 Size: 12, 420 MediaType: "download", 421 Labels: map[string]string{ 422 "OS": "linux", 423 "Architecture": "amd64", 424 }, 425 } 426 a.Manifest.ImageMap = map[string]manifest.MappedImage{ 427 "server": mappedImage, 428 } 429 430 images := a.generateBundleImages() 431 432 require.Len(t, images, 1) 433 img := images["server"] 434 assert.Equal(t, mappedImage.Description, img.Description) 435 assert.Equal(t, fmt.Sprintf("%s@%s", mappedImage.Repository, mappedImage.Digest), img.Image) 436 assert.Equal(t, mappedImage.ImageType, img.ImageType) 437 assert.Equal(t, mappedImage.Digest, img.Digest) 438 assert.Equal(t, mappedImage.Size, img.Size) 439 assert.Equal(t, mappedImage.MediaType, img.MediaType) 440 assert.Equal(t, mappedImage.Labels["OS"], img.Labels["OS"]) 441 assert.Equal(t, mappedImage.Labels["Architecture"], img.Labels["Architecture"]) 442 } 443 444 func TestManifestConverter_generateBundleImages_EmptyLabels(t *testing.T) { 445 t.Parallel() 446 447 c := config.NewTestConfig(t) 448 c.TestContext.AddTestFileFromRoot("pkg/manifest/testdata/simple.porter.yaml", config.Name) 449 450 ctx := context.Background() 451 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 452 require.NoError(t, err, "could not load manifest") 453 454 a := NewManifestConverter(c.Config, m, nil, nil, false) 455 456 mappedImage := manifest.MappedImage{ 457 Description: "un petite server", 458 Repository: "getporter/myserver", 459 Tag: "1.0.0", 460 ImageType: "docker", 461 Labels: nil, 462 } 463 a.Manifest.ImageMap = map[string]manifest.MappedImage{ 464 "server": mappedImage, 465 } 466 467 images := a.generateBundleImages() 468 require.Len(t, images, 1) 469 img := images["server"] 470 assert.Nil(t, img.Labels) 471 assert.Equal(t, fmt.Sprintf("%s:%s", mappedImage.Repository, mappedImage.Tag), img.Image) 472 } 473 474 func TestManifestConverter_generateBundleOutputs(t *testing.T) { 475 t.Parallel() 476 477 c := config.NewTestConfig(t) 478 c.TestContext.AddTestFileFromRoot("pkg/manifest/testdata/simple.porter.yaml", config.Name) 479 480 ctx := context.Background() 481 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 482 require.NoError(t, err, "could not load manifest") 483 484 a := NewManifestConverter(c.Config, m, nil, nil, false) 485 486 outputDefinitions := manifest.OutputDefinitions{ 487 "output1": { 488 Name: "output1", 489 ApplyTo: []string{ 490 "install", 491 "upgrade", 492 }, 493 Schema: definition.Schema{ 494 Type: "string", 495 Description: "Description of output1", 496 }, 497 Sensitive: true, 498 }, 499 "output2": { 500 Name: "output2", 501 Schema: definition.Schema{ 502 Type: "boolean", 503 Description: "Description of output2", 504 }, 505 }, 506 "kubeconfig": { 507 Name: "kubeconfig", 508 Path: "/home/nonroot/.kube/config", 509 Schema: definition.Schema{ 510 Type: "file", 511 Description: "Description of kubeconfig", 512 }, 513 }, 514 "notype-string": { 515 Name: "notype-string", 516 }, 517 "notype-file": { 518 Name: "notype-file", 519 Path: "/home/nonroot/.kube/config", 520 }, 521 } 522 523 a.Manifest.Outputs = outputDefinitions 524 525 defs := make(definition.Definitions, len(a.Manifest.Outputs)) 526 outputs := a.generateBundleOutputs(ctx, &defs) 527 require.Len(t, defs, 6) 528 529 wantOutputDefinitions := map[string]bundle.Output{ 530 "output1": { 531 Definition: "output1-output", 532 Description: "Description of output1", 533 ApplyTo: []string{ 534 "install", 535 "upgrade", 536 }, 537 Path: "/cnab/app/outputs/output1", 538 }, 539 "output2": { 540 Definition: "output2-output", 541 Description: "Description of output2", 542 Path: "/cnab/app/outputs/output2", 543 }, 544 "kubeconfig": { 545 Definition: "kubeconfig-output", 546 Description: "Description of kubeconfig", 547 Path: "/cnab/app/outputs/kubeconfig", 548 }, 549 "notype-string": { 550 Definition: "notype-string-output", 551 Path: "/cnab/app/outputs/notype-string", 552 }, 553 "notype-file": { 554 Definition: "notype-file-output", 555 Path: "/cnab/app/outputs/notype-file", 556 }, 557 "porter-state": { 558 Description: "Supports persisting state for bundles. Porter internal parameter that should not be set manually.", 559 Definition: "porter-state", 560 Path: "/cnab/app/outputs/porter-state", 561 }, 562 } 563 564 require.Equal(t, wantOutputDefinitions, outputs) 565 566 wantDefinitions := definition.Definitions{ 567 "output1-output": &definition.Schema{ 568 Type: "string", 569 Description: "Description of output1", 570 WriteOnly: toBool(true), 571 }, 572 "output2-output": &definition.Schema{ 573 Type: "boolean", 574 Description: "Description of output2", 575 }, 576 "kubeconfig-output": &definition.Schema{ 577 Type: "string", 578 ContentEncoding: "base64", 579 Description: "Description of kubeconfig", 580 }, 581 "notype-string-output": &definition.Schema{ 582 Type: "string", 583 }, 584 "notype-file-output": &definition.Schema{ 585 Type: "string", 586 ContentEncoding: "base64", 587 }, 588 "porter-state": &definition.Schema{ 589 ID: "https://porter.sh/generated-bundle/#porter-state", 590 Comment: "porter-internal", 591 Description: "Supports persisting state for bundles. Porter internal parameter that should not be set manually.", 592 Type: "string", 593 ContentEncoding: "base64", 594 }, 595 } 596 597 require.Equal(t, wantDefinitions, defs) 598 } 599 600 func TestManifestConverter_generateDependenciesv1(t *testing.T) { 601 t.Parallel() 602 603 testcases := []struct { 604 name string 605 wantDep depsv1ext.Dependency 606 }{ 607 {"no-version", depsv1ext.Dependency{ 608 Name: "mysql", 609 Bundle: "getporter/azure-mysql:5.7", 610 }}, 611 {"no-ranges, uses prerelease", depsv1ext.Dependency{ 612 Name: "ad", 613 Bundle: "getporter/azure-active-directory", 614 Version: &depsv1ext.DependencyVersion{ 615 AllowPrereleases: true, 616 Ranges: []string{"1.0.0-0"}, 617 }, 618 }}, 619 {"with-ranges", depsv1ext.Dependency{ 620 Name: "storage", 621 Bundle: "getporter/azure-blob-storage", 622 Version: &depsv1ext.DependencyVersion{ 623 Ranges: []string{ 624 "1.x - 2,2.1 - 3.x", 625 }, 626 }, 627 }}, 628 } 629 630 for _, tc := range testcases { 631 tc := tc 632 633 t.Run(tc.name, func(t *testing.T) { 634 t.Parallel() 635 c := config.NewTestConfig(t) 636 c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name) 637 638 ctx := context.Background() 639 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 640 require.NoError(t, err, "could not load manifest") 641 642 a := NewManifestConverter(c.Config, m, nil, nil, false) 643 defs := make(definition.Definitions, len(m.Parameters)) 644 645 depsExt, depsExtKey, err := a.generateDependencies(ctx, &defs) 646 require.NoError(t, err) 647 require.Equal(t, cnab.DependenciesV1ExtensionKey, depsExtKey, "expected the v1 dependencies extension key") 648 require.IsType(t, &depsv1ext.Dependencies{}, depsExt, "expected a v1 dependencies extension section") 649 deps := depsExt.(*depsv1ext.Dependencies) 650 require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated") 651 require.Equal(t, []string{"mysql", "ad", "storage"}, deps.Sequence, "incorrect sequence was generated") 652 653 var dep *depsv1ext.Dependency 654 for _, d := range deps.Requires { 655 if d.Bundle == tc.wantDep.Bundle { 656 dep = &d 657 break 658 } 659 } 660 661 require.NotNil(t, dep, "could not find bundle %s", tc.wantDep.Bundle) 662 assert.Equal(t, &tc.wantDep, dep) 663 }) 664 } 665 } 666 667 func TestManifestConverter_generateDependenciesv2(t *testing.T) { 668 t.Parallel() 669 670 testcases := []struct { 671 name string 672 wantDep depsv2ext.Dependency 673 wantDefs definition.Definitions 674 }{ 675 {name: "all fields", wantDep: depsv2ext.Dependency{ 676 Name: "mysql", 677 Bundle: "getporter/azure-mysql:5.7", 678 Version: "5.7.x", 679 Interface: &depsv2ext.DependencyInterface{ 680 ID: "https://porter.sh/interfaces/#mysql", 681 Reference: "getporter/mysql-spec:5.7", 682 Document: depsv2ext.DependencyInterfaceDocument{ 683 Outputs: map[string]bundle.Output{ 684 "myoutput": { 685 Definition: "myoutput-output", 686 Description: "worlds smallest output", 687 Path: "/cnab/app/outputs/myoutput", 688 }, 689 }, 690 Parameters: map[string]bundle.Parameter{ 691 "myparam": { 692 Definition: "myparam-parameter", 693 Description: "worlds biggest param", 694 Required: false, 695 Destination: &bundle.Location{ 696 Path: "", 697 EnvironmentVariable: "MYPARAM", 698 }, 699 }, 700 }, 701 Credentials: map[string]bundle.Credential{ 702 "mycred": { 703 Description: "credential", 704 Required: true, 705 }, 706 }, 707 }, 708 }, 709 Sharing: depsv2ext.SharingCriteria{ 710 Mode: true, 711 Group: depsv2ext.SharingGroup{Name: "myapp"}, 712 }, 713 Parameters: map[string]string{ 714 "database": "wordpress", 715 "collation": "${bundle.parameters.db_collation}", 716 }, 717 Credentials: map[string]string{ 718 "user": "${bundle.credentials.username}", 719 }, 720 }, 721 wantDefs: map[string]*definition.Schema{ 722 "myoutput-output": { 723 Type: "string", 724 Description: "worlds smallest output", 725 }, 726 "myparam-parameter": { 727 Type: "string", 728 Default: false, 729 Description: "worlds biggest param", 730 }, 731 }, 732 }, 733 } 734 735 for _, tc := range testcases { 736 tc := tc 737 738 t.Run(tc.name, func(t *testing.T) { 739 t.Parallel() 740 c := config.NewTestConfig(t) 741 c.SetExperimentalFlags(experimental.FlagDependenciesV2) 742 c.TestContext.AddTestFile("testdata/porter-with-depsv2.yaml", config.Name) 743 744 ctx := context.Background() 745 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 746 require.NoError(t, err, "could not load manifest") 747 748 a := NewManifestConverter(c.Config, m, nil, nil, false) 749 defs := make(definition.Definitions, len(m.Parameters)) 750 751 depsExt, depsExtKey, err := a.generateDependencies(ctx, &defs) 752 require.NoError(t, err) 753 require.Equal(t, cnab.DependenciesV2ExtensionKey, depsExtKey, "expected the v1 dependencies extension key") 754 require.IsType(t, &depsv2ext.Dependencies{}, depsExt, "expected a v1 dependencies extension section") 755 deps := depsExt.(*depsv2ext.Dependencies) 756 require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated") 757 758 var dep *depsv2ext.Dependency 759 for _, d := range deps.Requires { 760 if d.Bundle == tc.wantDep.Bundle { 761 dep = &d 762 break 763 } 764 } 765 766 require.NotNil(t, dep, "could not find bundle %s", tc.wantDep.Bundle) 767 assert.Equal(t, &tc.wantDep, dep) 768 assert.Equal(t, tc.wantDefs, defs) 769 }) 770 } 771 } 772 773 func TestManifestConverter_generateParameterSources(t *testing.T) { 774 t.Parallel() 775 776 c := config.NewTestConfig(t) 777 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 778 779 ctx := context.Background() 780 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 781 require.NoError(t, err, "could not load manifest") 782 783 a := NewManifestConverter(c.Config, m, nil, nil, false) 784 785 b, err := a.ToBundle(ctx) 786 require.NoError(t, err, "ToBundle failed") 787 sources, err := b.ReadParameterSources() 788 require.NoError(t, err, "ReadParameterSources failed") 789 790 want := cnab.ParameterSources{} 791 want.SetParameterFromOutput("porter-msg-output", "msg") 792 want.SetParameterFromOutput("tfstate", "tfstate") 793 want.SetParameterFromOutput("porter-state", "porter-state") 794 want.SetParameterFromDependencyOutput("mysql-connstr", "db", "connstr") 795 796 assert.Equal(t, want, sources) 797 } 798 799 func TestNewManifestConverter_generateOutputWiringParameter(t *testing.T) { 800 t.Parallel() 801 802 c := config.NewTestConfig(t) 803 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 804 805 ctx := context.Background() 806 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 807 require.NoError(t, err, "could not load manifest") 808 809 a := NewManifestConverter(c.Config, m, nil, nil, false) 810 811 outputDef := definition.Schema{ 812 Type: "string", 813 } 814 b := cnab.NewBundle(bundle.Bundle{ 815 Outputs: map[string]bundle.Output{ 816 "msg": { 817 Definition: "stringDef", 818 }, 819 "some-thing": { 820 Definition: "stringDef", 821 }, 822 }, 823 Definitions: map[string]*definition.Schema{ 824 "stringDef": &outputDef, 825 }, 826 }) 827 828 t.Run("generate parameter", func(t *testing.T) { 829 t.Parallel() 830 831 name, param, paramDef := a.generateOutputWiringParameter(b, "msg") 832 833 assert.Equal(t, "porter-msg-output", name, "unexpected parameter name") 834 assert.False(t, param.Required, "wiring parameters should NOT be required") 835 require.NotNil(t, param.Destination, "wiring parameters should have a destination set") 836 assert.Equal(t, "PORTER_MSG_OUTPUT", param.Destination.EnvironmentVariable, "unexpected destination environment variable set") 837 838 assert.Equal(t, "https://porter.sh/generated-bundle/#porter-parameter-source-definition", paramDef.ID, "wiring parameter should have a schema id set") 839 assert.NotSame(t, &outputDef, ¶mDef, "wiring parameter definition should be a copy") 840 assert.Equal(t, outputDef.Type, paramDef.Type, "output def and param def should have the same type") 841 assert.Equal(t, cnab.PorterInternal, paramDef.Comment, "wiring parameter should be flagged as internal") 842 }) 843 844 t.Run("param with hyphen", func(t *testing.T) { 845 t.Parallel() 846 847 name, param, _ := a.generateOutputWiringParameter(b, "some-thing") 848 849 assert.Equal(t, "porter-some-thing-output", name, "unexpected parameter name") 850 require.NotNil(t, param.Destination, "wiring parameters should have a destination set") 851 assert.Equal(t, "PORTER_SOME_THING_OUTPUT", param.Destination.EnvironmentVariable, "unexpected destination environment variable set") 852 }) 853 } 854 855 func TestNewManifestConverter_generateDependencyOutputWiringParameter(t *testing.T) { 856 t.Parallel() 857 858 c := config.NewTestConfig(t) 859 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 860 861 ctx := context.Background() 862 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 863 require.NoError(t, err, "could not load manifest") 864 865 a := NewManifestConverter(c.Config, m, nil, nil, false) 866 867 ref := manifest.DependencyOutputReference{Dependency: "mysql", Output: "mysql-password"} 868 name, param, paramDef := a.generateDependencyOutputWiringParameter(ref) 869 870 assert.Equal(t, "porter-mysql-mysql-password-dep-output", name, "unexpected parameter name") 871 assert.False(t, param.Required, "wiring parameters should NOT be required") 872 require.NotNil(t, param.Destination, "wiring parameters should have a destination set") 873 assert.Equal(t, "PORTER_MYSQL_MYSQL_PASSWORD_DEP_OUTPUT", param.Destination.EnvironmentVariable, "unexpected destination environment variable set") 874 875 assert.Equal(t, "https://porter.sh/generated-bundle/#porter-parameter-source-definition", paramDef.ID, "wiring parameter should have a schema id set") 876 assert.Equal(t, cnab.PorterInternal, paramDef.Comment, "wiring parameter should be flagged as internal") 877 assert.Empty(t, paramDef.Type, "dependency output types are of unknown types and should not be defined") 878 } 879 880 func TestManifestConverter_generateRequiredExtensions_ParameterSources(t *testing.T) { 881 t.Parallel() 882 883 c := config.NewTestConfig(t) 884 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 885 886 ctx := context.Background() 887 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 888 require.NoError(t, err, "could not load manifest") 889 890 a := NewManifestConverter(c.Config, m, nil, nil, false) 891 892 bun, err := a.ToBundle(ctx) 893 require.NoError(t, err, "ToBundle failed") 894 assert.Contains(t, bun.RequiredExtensions, "io.cnab.parameter-sources") 895 } 896 897 func TestManifestConverter_generateRequiredExtensions(t *testing.T) { 898 t.Parallel() 899 900 c := config.NewTestConfig(t) 901 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 902 903 ctx := context.Background() 904 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 905 require.NoError(t, err, "could not load manifest") 906 907 a := NewManifestConverter(c.Config, m, nil, nil, false) 908 909 bun, err := a.ToBundle(ctx) 910 require.NoError(t, err, "ToBundle failed") 911 912 expected := []string{"sh.porter.file-parameters", "io.cnab.dependencies", "io.cnab.parameter-sources", "io.cnab.docker"} 913 assert.Equal(t, expected, bun.RequiredExtensions) 914 } 915 916 func TestManifestConverter_generateCustomExtensions_withRequired(t *testing.T) { 917 t.Parallel() 918 919 c := config.NewTestConfig(t) 920 c.TestContext.AddTestFile("testdata/porter-with-required-extensions.yaml", config.Name) 921 922 ctx := context.Background() 923 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 924 require.NoError(t, err, "could not load manifest") 925 926 a := NewManifestConverter(c.Config, m, nil, nil, false) 927 928 bun, err := a.ToBundle(ctx) 929 require.NoError(t, err, "ToBundle failed") 930 assert.Contains(t, bun.Custom, cnab.FileParameterExtensionKey) 931 assert.Contains(t, bun.Custom, "requiredExtension1") 932 assert.Contains(t, bun.Custom, "requiredExtension2") 933 assert.Equal(t, map[string]interface{}{"config": true}, bun.Custom["requiredExtension2"]) 934 } 935 936 func TestManifestConverter_GenerateCustomActionDefinitions(t *testing.T) { 937 t.Parallel() 938 939 c := config.NewTestConfig(t) 940 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 941 942 ctx := context.Background() 943 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 944 require.NoError(t, err, "could not load manifest") 945 946 a := NewManifestConverter(c.Config, m, nil, nil, false) 947 948 defs := a.generateCustomActionDefinitions() 949 require.Len(t, defs, 3, "expected 3 custom action definitions to be generated") 950 951 require.Contains(t, defs, "status") 952 statusDef := defs["status"] 953 assert.Equal(t, "Print the installation status", statusDef.Description) 954 assert.False(t, statusDef.Stateless, "expected the status custom action to not be stateless") 955 assert.False(t, statusDef.Modifies, "expected the status custom action to not modify resources") 956 957 require.Contains(t, defs, "boom") 958 boomDef := defs["boom"] 959 assert.False(t, boomDef.Stateless, "expected the dry-run custom action to default to not stateless") 960 assert.True(t, boomDef.Modifies, "expected the dry-run custom action to default to modifying resources") 961 } 962 963 func TestManifestConverter_generateDefaultAction(t *testing.T) { 964 t.Parallel() 965 966 testcases := []struct { 967 action string 968 wantAction bundle.Action 969 }{ 970 {"dry-run", bundle.Action{ 971 Description: "Execute the installation in a dry-run mode, allowing to see what would happen with the given set of parameter values", 972 Modifies: false, 973 Stateless: true, 974 }}, 975 { 976 "help", bundle.Action{ 977 Description: "Print a help message to the standard output", 978 Modifies: false, 979 Stateless: true, 980 }}, 981 {"log", bundle.Action{ 982 Description: "Print logs of the installed system to the standard output", 983 Modifies: false, 984 Stateless: false, 985 }}, 986 {"status", bundle.Action{ 987 Description: "Print a human readable status message to the standard output", 988 Modifies: false, 989 Stateless: false, 990 }}, 991 {"zombies", bundle.Action{ 992 Description: "zombies", 993 Modifies: true, 994 Stateless: false, 995 }}, 996 } 997 998 for _, tc := range testcases { 999 tc := tc 1000 t.Run(tc.action, func(t *testing.T) { 1001 t.Parallel() 1002 1003 a := ManifestConverter{} 1004 gotAction := a.generateDefaultAction(tc.action) 1005 assert.Equal(t, tc.wantAction, gotAction) 1006 }) 1007 } 1008 } 1009 1010 func TestManifestConverter_generateCustomMetadata(t *testing.T) { 1011 t.Parallel() 1012 1013 c := config.NewTestConfig(t) 1014 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 1015 1016 ctx := context.Background() 1017 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 1018 require.NoError(t, err, "could not load manifest") 1019 1020 a := NewManifestConverter(c.Config, m, nil, nil, false) 1021 1022 bun, err := a.ToBundle(ctx) 1023 require.NoError(t, err, "ToBundle failed") 1024 assert.Len(t, bun.Custom, 7) 1025 1026 f, err := os.CreateTemp("", "") 1027 require.NoError(t, err, "Failed to create bundle file") 1028 defer os.Remove(f.Name()) 1029 1030 _, err = bun.WriteTo(f) 1031 require.NoError(t, err, "Failed to write bundle file") 1032 1033 expectedCustomMetaData := `"app":{"version":"1.2.3"},"foo":{"test1":true,"test2":1,"test3":"value","test4":["one","two","three"],"test5":{"1":"one","two":"two"}}` 1034 bundleData, err := os.ReadFile(f.Name()) 1035 require.NoError(t, err, "Failed to read bundle file") 1036 1037 assert.Contains(t, string(bundleData), expectedCustomMetaData, "Created bundle should be equal to expected bundle ") 1038 } 1039 1040 func TestManifestConverter_generatedMaintainers(t *testing.T) { 1041 want := []bundle.Maintainer{ 1042 {Name: "John Doe", Email: "john.doe@example.com", URL: "https://example.com/a"}, 1043 {Name: "Jane Doe", Email: "", URL: "https://example.com/b"}, 1044 {Name: "Janine Doe", Email: "janine.doe@example.com", URL: ""}, 1045 {Name: "", Email: "mike.doe@example.com", URL: "https://example.com/c"}, 1046 } 1047 1048 c := config.NewTestConfig(t) 1049 c.TestContext.AddTestFileFromRoot("tests/testdata/mybuns/porter.yaml", config.Name) 1050 1051 ctx := context.Background() 1052 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 1053 require.NoError(t, err, "could not load manifest") 1054 1055 a := NewManifestConverter(c.Config, m, nil, nil, false) 1056 1057 got := a.generateBundleMaintainers() 1058 assert.Len(t, got, len(want), "Created bundle should contain desired maintainers") 1059 1060 for _, wanted := range want { 1061 gm, err := getMaintainerByName(got, wanted.Name) 1062 if err != nil { 1063 t.Errorf("Created bundle should container maintainer '%s'", wanted.Name) 1064 } 1065 assert.Equal(t, wanted.Email, gm.Email, "Created bundle should specify email '%s' for maintainer '%s'", wanted.Email, wanted.Name) 1066 assert.Equal(t, wanted.URL, gm.URL, "Created bundle should specify url '%s' for maintainer '%s'", wanted.URL, wanted.Name) 1067 } 1068 } 1069 1070 func getMaintainerByName(source []bundle.Maintainer, name string) (bundle.Maintainer, error) { 1071 for _, m := range source { 1072 if m.Name == name { 1073 return m, nil 1074 } 1075 } 1076 return bundle.Maintainer{}, fmt.Errorf("Could not find maintainer with name '%s'", name) 1077 }