get.porter.sh/porter@v1.3.0/pkg/manifest/manifest_test.go (about) 1 package manifest 2 3 import ( 4 "context" 5 "os" 6 "strings" 7 "testing" 8 9 "get.porter.sh/porter/pkg/cnab" 10 "get.porter.sh/porter/pkg/config" 11 "get.porter.sh/porter/pkg/experimental" 12 "get.porter.sh/porter/pkg/portercontext" 13 "get.porter.sh/porter/pkg/schema" 14 "get.porter.sh/porter/pkg/yaml" 15 "github.com/Masterminds/semver/v3" 16 "github.com/cnabio/cnab-go/bundle/definition" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestLoadManifest(t *testing.T) { 22 c := config.NewTestConfig(t) 23 24 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 25 26 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 27 require.NoError(t, err, "could not load manifest") 28 29 require.NotNil(t, m, "manifest was nil") 30 require.Equal(t, m.Name, "hello", "manifest has incorrect name") 31 require.Equal(t, m.Description, "An example Porter configuration", "manifest has incorrect description") 32 require.Equal(t, m.Version, "0.1.0", "manifest has incorrect version") 33 require.Equal(t, m.Registry, "localhost:5000", "manifest has incorrect registry") 34 require.Equal(t, m.Reference, "localhost:5000/hello:v0.1.0", "manifest has incorrect reference") 35 36 require.Len(t, m.Maintainers, 4, "manifest has incorrect number of maintainers") 37 38 john, jane, janine, mike := m.Maintainers[0], m.Maintainers[1], m.Maintainers[2], m.Maintainers[3] 39 require.Equal(t, "John Doe", john.Name, "manifest: Maintainer name is incorrect") 40 require.Equal(t, "john.doe@example.com", john.Email, "manifest: Maintainer email is incorrect") 41 require.Equal(t, "https://example.com/a", john.Url, "manifest: Maintainer url is incorrect") 42 43 require.Equal(t, "Jane Doe", jane.Name, "manifest: Maintainer name is incorrect") 44 require.Equal(t, "", jane.Email, "manifest: Maintainer email is incorrect") 45 require.Equal(t, "https://example.com/b", jane.Url, "manifest: Maintainer url is incorrect") 46 47 require.Equal(t, "Janine Doe", janine.Name, "manifest: Maintainer name is incorrect") 48 require.Equal(t, "janine.doe@example.com", janine.Email, "manifest: Maintainer email is incorrect") 49 require.Equal(t, "", janine.Url, "manifest: Maintainer url is incorrect") 50 51 require.Equal(t, "", mike.Name, "manifest: Maintainer name is incorrect") 52 require.Equal(t, "mike.doe@example.com", mike.Email, "manifest: Maintainer email is incorrect") 53 require.Equal(t, "https://example.com/c", mike.Url, "manifest: Maintainer url is incorrect") 54 55 assert.Equal(t, []MixinDeclaration{{Name: "exec"}}, m.Mixins, "expected manifest to declare the exec mixin") 56 require.Len(t, m.Install, 1, "expected 1 install step") 57 58 installStep := m.Install[0] 59 description, _ := installStep.GetDescription() 60 assert.NotNil(t, description, "expected the install description to be populated") 61 62 mixin := installStep.GetMixinName() 63 assert.Equal(t, "exec", mixin, "incorrect install step mixin used") 64 65 require.Len(t, m.CustomActions, 1, "expected manifest to declare 1 custom action") 66 require.Contains(t, m.CustomActions, "status", "expected manifest to declare a status action") 67 68 statusStep := m.CustomActions["status"][0] 69 description, _ = statusStep.GetDescription() 70 assert.Equal(t, "Get World Status", description, "unexpected status step description") 71 72 mixin = statusStep.GetMixinName() 73 assert.Equal(t, "exec", mixin, "unexpected status step mixin") 74 } 75 76 func TestLoadManifestWithDependencies(t *testing.T) { 77 // Make sure that we can parse the bundle in both v1 dep mode and v2 dep mode 78 testcases := []struct { 79 name string 80 depsv2enabled bool 81 }{ 82 {"deps v1", false}, 83 {"deps v2", true}, 84 } 85 86 for _, tc := range testcases { 87 t.Run(tc.name, func(t *testing.T) { 88 89 c := config.NewTestConfig(t) 90 if tc.depsv2enabled { 91 c.SetExperimentalFlags(experimental.FlagDependenciesV2) 92 } 93 94 c.TestContext.AddTestFile("testdata/porter.yaml", config.Name) 95 c.TestContext.AddTestDirectory("testdata/bundles", "bundles") 96 97 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 98 require.NoError(t, err, "could not load manifest") 99 100 require.NotNil(t, m) 101 assert.Equal(t, []MixinDeclaration{{Name: "exec"}}, m.Mixins) 102 require.Len(t, m.Install, 1) 103 104 installStep := m.Install[0] 105 description, _ := installStep.GetDescription() 106 require.NotNil(t, description) 107 108 mixin := installStep.GetMixinName() 109 assert.Equal(t, "exec", mixin) 110 111 require.Len(t, m.Dependencies.Requires, 1, "expected one dependency") 112 dep := m.Dependencies.Requires[0] 113 assert.Equal(t, "getporter/azure-mysql:5.7", dep.Bundle.Reference, "expected the dependency to be set") 114 assert.Equal(t, "5.7.x", dep.Bundle.Version, "expected the version range to be set") 115 assert.Equal(t, map[string]string{"database-name": "wordpress"}, dep.Parameters, "expected the dependency parameters to be set") 116 117 // The remaining fields are only supported in depsv2 but the manifest still parses them. It's only a behavior difference if we act on the information or not. 118 assert.Equal(t, map[string]string{"password": "mcstuffins"}, dep.Credentials, "expected the dependency credentials to be set") 119 120 // TODO(PEP003) validate the bundle interface document 121 /* 122 wantDoc := &BundleInterfaceDocument{ 123 Parameters: map[string]ParameterDefinition{ 124 "password": { 125 Name: "password", 126 Schema: definition.Schema{Type: "string"}}, 127 }, 128 } 129 assert.Equal(t, "getporter/azure-mysql:5.7-interface", dep.Bundle.Interface.Reference, "expected the bundle interface reference to be set") 130 assert.Equal(t, wantDoc, dep.Bundle.Interface.Document, "expected the bundle interface document to be set") 131 */ 132 }) 133 } 134 } 135 136 func TestAction_Validate_RequireMixinDeclaration(t *testing.T) { 137 c := config.NewTestConfig(t) 138 139 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 140 141 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 142 require.NoError(t, err, "could not load manifest") 143 144 // Sabotage! 145 m.Mixins = []MixinDeclaration{} 146 147 err = m.Install.Validate(m) 148 assert.EqualError(t, err, "failed to validate 1st step: mixin (exec) was not declared") 149 } 150 151 func TestAction_Validate_RequireMixinData(t *testing.T) { 152 c := config.NewTestConfig(t) 153 154 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 155 156 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 157 require.NoError(t, err, "could not load manifest") 158 159 // Sabotage! 160 m.Install[0].Data = nil 161 162 err = m.Install.Validate(m) 163 assert.EqualError(t, err, "failed to validate 1st step: no mixin specified") 164 } 165 166 func TestAction_Validate_RequireSingleMixinData(t *testing.T) { 167 c := config.NewTestConfig(t) 168 169 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 170 171 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 172 require.NoError(t, err, "could not load manifest") 173 174 // Sabotage! 175 m.Install[0].Data["rando-mixin"] = "" 176 177 err = m.Install.Validate(m) 178 assert.EqualError(t, err, "failed to validate 1st step: malformed step, possibly incorrect indentation") 179 } 180 181 func TestAction_Validate_RequireSingleMixinData_Actions(t *testing.T) { 182 testcases := []struct { 183 name string 184 getStep func(*Manifest) *Steps 185 }{ 186 {"install", func(m *Manifest) *Steps { return &m.Install }}, 187 {"uninstall", func(m *Manifest) *Steps { return &m.Uninstall }}, 188 {"upgrade", func(m *Manifest) *Steps { return &m.Upgrade }}, 189 {"custom", func(m *Manifest) *Steps { status := m.CustomActions["status"]; return &status }}, 190 } 191 192 for _, tc := range testcases { 193 t.Run(tc.name, func(t *testing.T) { 194 ctx := context.Background() 195 c := config.NewTestConfig(t) 196 197 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 198 199 m, err := LoadManifestFrom(ctx, c.Config, config.Name) 200 require.NoError(t, err, "could not load manifest") 201 step := tc.getStep(m) 202 203 if len(*step) == 0 { 204 *step = make(Steps, 1) 205 (*step)[0] = &Step{ 206 Data: make(map[string]interface{}), 207 } 208 (*step)[0].Data["exec"] = "" 209 } 210 211 // Sabotage! 212 (*step)[0].Data["rando-mixin"] = "" 213 214 err = m.Validate(ctx, c.Config) 215 assert.ErrorContains(t, err, "malformed step, possibly incorrect indentation") 216 }) 217 } 218 } 219 220 func TestManifest_Empty_Steps(t *testing.T) { 221 c := config.NewTestConfig(t) 222 223 c.TestContext.AddTestFile("testdata/empty-steps.yaml", config.Name) 224 225 _, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 226 assert.EqualError(t, err, "3 errors occurred:\n\t* validation of action \"install\" failed: failed to validate 2nd step: found an empty step\n\t* validation of action \"uninstall\" failed: failed to validate 2nd step: found an empty step\n\t* validation of action \"status\" failed: failed to validate 1st step: found an empty step\n\n") 227 } 228 229 func TestManifest_Validate_Name(t *testing.T) { 230 c := config.NewTestConfig(t) 231 232 c.TestContext.AddTestFile("testdata/porter-no-name.yaml", config.Name) 233 234 _, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 235 assert.EqualError(t, err, "bundle name must be set") 236 } 237 238 func TestManifest_Validate_Description(t *testing.T) { 239 c := config.NewTestConfig(t) 240 241 c.TestContext.AddTestFile("testdata/porter-with-bad-description.yaml", config.Name) 242 243 _, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 244 assert.ErrorContains(t, err, "validation of action \"install\" failed: failed to validate 1st step: invalid description type (string) for mixin step (exec)") 245 } 246 247 func TestManifest_Validate_InvalidType(t *testing.T) { 248 c := config.NewTestConfig(t) 249 250 c.TestContext.AddTestFile("testdata/porter-with-bad-type.yaml", config.Name) 251 252 assert.NotPanics(t, func() { 253 _, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 254 assert.ErrorContains(t, err, "validation of action \"install\" failed: failed to validate 1st step: invalid mixin type (string) for mixin step (exec)") 255 }) 256 } 257 258 func TestManifest_Validate_SchemaVersion(t *testing.T) { 259 invalidVersionErr := schema.ErrInvalidSchemaVersion.Error() 260 261 t.Run("schemaVersion matches", func(t *testing.T) { 262 ctx := context.Background() 263 cfg := config.NewTestConfig(t) 264 cfg.TestContext.UseFilesystem() 265 cfg.Data.SchemaCheck = string(schema.CheckStrategyExact) 266 267 m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config) 268 require.NoError(t, err) 269 270 err = m.Validate(ctx, cfg.Config) 271 require.NoError(t, err) 272 assert.NotContains(t, cfg.TestContext.GetError(), invalidVersionErr) 273 }) 274 275 t.Run("schemaVersion requires experimental feature", func(t *testing.T) { 276 ctx := context.Background() 277 cfg := config.NewTestConfig(t) 278 cfg.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml") 279 cfg.Data.SchemaCheck = string(schema.CheckStrategyExact) 280 281 // Use a schema version that requires dependencies v2 enabled 282 cfg.TestContext.EditYaml("porter.yaml", func(yq *yaml.Editor) error { 283 return yq.SetValue("schemaVersion", "1.1.0") 284 }) 285 m, err := ReadManifest(cfg.Context, "porter.yaml", cfg.Config) 286 require.NoError(t, err) 287 288 err = m.Validate(ctx, cfg.Config) 289 require.ErrorContains(t, err, "invalid schema version") 290 291 cfg.SetExperimentalFlags(experimental.FlagDependenciesV2) 292 err = m.Validate(ctx, cfg.Config) 293 require.NoError(t, err) 294 assert.NotContains(t, cfg.TestContext.GetError(), invalidVersionErr) 295 }) 296 297 t.Run("schemaVersion missing, not required", func(t *testing.T) { 298 cfg := config.NewTestConfig(t) 299 cfg.TestContext.UseFilesystem() 300 ctx, span := cfg.StartRootSpan(context.Background(), t.Name()) 301 defer span.EndSpan() 302 cfg.Data.SchemaCheck = string(schema.CheckStrategyNone) 303 304 m, err := ReadManifest(cfg.Context, "testdata/porter.yaml", cfg.Config) 305 require.NoError(t, err) 306 307 m.SchemaVersion = "" 308 309 err = m.Validate(ctx, cfg.Config) 310 require.NoError(t, err) 311 312 // Check that a warning is printed 313 // We aren't returning an error because we want to give it a chance to work first. Later we may turn this into a hard error after people have had time to migrate. 314 assert.Contains(t, cfg.TestContext.GetOutput(), invalidVersionErr) 315 }) 316 } 317 318 func TestManifest_ValidateMetadata(t *testing.T) { 319 // Make sure that we allow a range of versions 320 invalidVersionErr := schema.ErrInvalidSchemaVersion.Error() 321 testcases := []struct { 322 schemaVersion string 323 wantErr string 324 }{ 325 {wantErr: invalidVersionErr}, 326 {schemaVersion: "1.0.0-alpha.1"}, 327 {schemaVersion: "1.0.0-alpha.2", wantErr: invalidVersionErr}, 328 {schemaVersion: "1.0.0"}, 329 } 330 331 for _, tc := range testcases { 332 t.Run(tc.schemaVersion, func(t *testing.T) { 333 cfg := config.NewTestConfig(t) 334 cfg.Data.SchemaCheck = string(schema.CheckStrategyExact) 335 336 m := Manifest{ 337 SchemaVersion: tc.schemaVersion, 338 Name: "mybuns", 339 Registry: "localhost:5000", 340 } 341 err := m.validateMetadata(context.Background(), cfg.Config) 342 343 if tc.wantErr == "" { 344 require.NoError(t, err) 345 assert.NotContains(t, cfg.TestContext.GetError(), invalidVersionErr) 346 } else { 347 require.ErrorContains(t, err, invalidVersionErr) 348 } 349 }) 350 } 351 } 352 353 func TestManifest_ValidateSchemaType(t *testing.T) { 354 testcases := []struct { 355 schemaType string 356 wantErr string 357 }{ 358 {schemaType: "", wantErr: ""}, 359 {schemaType: SchemaTypeBundle, wantErr: ""}, 360 {schemaType: strings.ToLower(SchemaTypeBundle), wantErr: ""}, 361 {schemaType: strings.ToUpper(SchemaTypeBundle), wantErr: ""}, 362 {schemaType: "CredentialSet", wantErr: "invalid schemaType CredentialSet, expected Bundle"}, 363 } 364 365 for _, tc := range testcases { 366 t.Run(tc.schemaType, func(t *testing.T) { 367 cfg := config.NewTestConfig(t) 368 cfg.Data.SchemaCheck = string(schema.CheckStrategyExact) 369 370 m := Manifest{ 371 SchemaType: tc.schemaType, 372 SchemaVersion: DefaultSchemaVersion.String(), 373 Name: "mybuns", 374 Registry: "localhost:5000", 375 } 376 err := m.validateMetadata(context.Background(), cfg.Config) 377 378 if tc.wantErr == "" { 379 require.NoError(t, err) 380 } else { 381 require.ErrorContains(t, err, tc.wantErr) 382 } 383 }) 384 } 385 } 386 387 func TestManifest_Validate_Dockerfile(t *testing.T) { 388 c := config.NewTestConfig(t) 389 c.Data.SchemaCheck = string(schema.CheckStrategyNone) 390 391 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 392 393 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 394 require.NoError(t, err, "could not load manifest") 395 396 m.Dockerfile = "Dockerfile" 397 398 err = m.Validate(context.Background(), c.Config) 399 400 assert.EqualError(t, err, "Dockerfile template cannot be named 'Dockerfile' because that is the filename generated during porter build") 401 } 402 403 func TestManifest_Validate_WrongSchema(t *testing.T) { 404 c := config.NewTestConfig(t) 405 406 c.TestContext.AddTestFile("testdata/porter-with-badschema.yaml", config.Name) 407 _, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 408 409 require.Error(t, err) 410 assert.Regexp(t, 411 "unsupported property set or a custom action is defined incorrectly: error unmarshaling custom action baddata", 412 err, 413 ) 414 } 415 416 func TestReadManifest_URL(t *testing.T) { 417 cxt := portercontext.NewTestContext(t) 418 url := "https://raw.githubusercontent.com/getporter/porter/v0.27.1/pkg/manifest/testdata/simple.porter.yaml" 419 m, err := ReadManifest(cxt.Context, url, config.NewTestConfig(t).Config) 420 421 require.NoError(t, err) 422 assert.Equal(t, "hello", m.Name) 423 } 424 425 func TestReadManifest_Validate_InvalidURL(t *testing.T) { 426 cxt := portercontext.NewTestContext(t) 427 _, err := ReadManifest(cxt.Context, "http://fake-example-porter", config.NewTestConfig(t).Config) 428 429 assert.Error(t, err) 430 assert.Regexp(t, "could not reach url http://fake-example-porter", err) 431 } 432 433 func TestReadManifest_File(t *testing.T) { 434 cxt := portercontext.NewTestContext(t) 435 cxt.AddTestFile("testdata/simple.porter.yaml", config.Name) 436 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 437 438 require.NoError(t, err) 439 assert.Equal(t, "hello", m.Name) 440 } 441 442 func TestSetDefaults(t *testing.T) { 443 t.Run("no registry or reference provided", func(t *testing.T) { 444 cfg := config.NewTestConfig(t) 445 m := Manifest{ 446 SchemaVersion: DefaultSchemaVersion.String(), 447 Name: "mybun", 448 Version: "1.2.3-beta.1", 449 } 450 err := m.validateMetadata(context.Background(), cfg.Config) 451 require.EqualError(t, err, "a registry or reference value must be provided") 452 }) 453 454 t.Run("bundle docker tag set on reference", func(t *testing.T) { 455 cfg := config.NewTestConfig(t) 456 m := Manifest{ 457 SchemaVersion: DefaultSchemaVersion.String(), 458 Name: "mybun", 459 Version: "1.2.3-beta.1", 460 Reference: "getporter/mybun:v1.2.3", 461 } 462 err := m.validateMetadata(context.Background(), cfg.Config) 463 require.NoError(t, err) 464 465 err = m.SetDefaults() 466 require.NoError(t, err) 467 assert.Equal(t, "getporter/mybun:v1.2.3", m.Reference) 468 assert.Equal(t, "getporter/mybun:porter-e7a4fac8f425d76ed9a5baa3a188824b", m.Image) 469 }) 470 471 t.Run("bundle docker tag not set on reference", func(t *testing.T) { 472 cfg := config.NewTestConfig(t) 473 m := Manifest{ 474 SchemaVersion: DefaultSchemaVersion.String(), 475 Name: "mybun", 476 Version: "1.2.3-beta.1+15", 477 Reference: "getporter/mybun", 478 } 479 err := m.validateMetadata(context.Background(), cfg.Config) 480 require.NoError(t, err) 481 482 err = m.SetDefaults() 483 require.NoError(t, err) 484 assert.Equal(t, "getporter/mybun:v1.2.3-beta.1_15", m.Reference) 485 assert.Equal(t, "getporter/mybun:porter-bcd1325906d287fb3b93500c8bfd2947", m.Image) 486 }) 487 488 t.Run("bundle reference includes registry with port", func(t *testing.T) { 489 cfg := config.NewTestConfig(t) 490 m := Manifest{ 491 SchemaVersion: DefaultSchemaVersion.String(), 492 Name: "mybun", 493 Version: "0.1.0", 494 Reference: "localhost:5000/missing-invocation-image", 495 } 496 err := m.validateMetadata(context.Background(), cfg.Config) 497 require.NoError(t, err) 498 499 err = m.SetDefaults() 500 require.NoError(t, err) 501 assert.Equal(t, "localhost:5000/missing-invocation-image:v0.1.0", m.Reference) 502 assert.Equal(t, "localhost:5000/missing-invocation-image:porter-fea49a80fb6822ee71f71e2ce4a48a37", m.Image) 503 }) 504 505 t.Run("registry provided, no reference", func(t *testing.T) { 506 cfg := config.NewTestConfig(t) 507 m := Manifest{ 508 SchemaVersion: DefaultSchemaVersion.String(), 509 Name: "mybun", 510 Version: "1.2.3-beta.1", 511 Registry: "getporter", 512 } 513 err := m.validateMetadata(context.Background(), cfg.Config) 514 require.NoError(t, err) 515 516 err = m.SetDefaults() 517 require.NoError(t, err) 518 assert.Equal(t, "getporter/mybun:v1.2.3-beta.1", m.Reference) 519 assert.Equal(t, "getporter/mybun:porter-b4b9ce8671aacb5a093574b04f9f87e1", m.Image) 520 }) 521 522 t.Run("registry provided with org, no reference", func(t *testing.T) { 523 cfg := config.NewTestConfig(t) 524 m := Manifest{ 525 SchemaVersion: DefaultSchemaVersion.String(), 526 Name: "mybun", 527 Version: "1.2.3-beta.1", 528 Registry: "getporter/myorg", 529 } 530 err := m.validateMetadata(context.Background(), cfg.Config) 531 require.NoError(t, err) 532 533 err = m.SetDefaults() 534 require.NoError(t, err) 535 assert.Equal(t, "getporter/myorg/mybun:v1.2.3-beta.1", m.Reference) 536 assert.Equal(t, "getporter/myorg/mybun:porter-f4f017f099257ee41d0c05d5e3180f88", m.Image) 537 }) 538 539 t.Run("registry and reference provided", func(t *testing.T) { 540 cfg := config.NewTestConfig(t) 541 ctx, span := cfg.StartRootSpan(context.Background(), t.Name()) // Start a span so we can capture trace/logs emitted with a WARNING 542 defer span.EndSpan() 543 m := Manifest{ 544 SchemaVersion: DefaultSchemaVersion.String(), 545 Name: "mybun", 546 Version: "1.2.3-beta.1", 547 Registry: "myregistry/myorg", 548 Reference: "getporter/org/mybun:v1.2.3", 549 } 550 err := m.validateMetadata(ctx, cfg.Config) 551 require.NoError(t, err) 552 require.Contains(t, 553 cfg.TestContext.GetOutput(), 554 "WARNING: both registry and reference were provided; using the reference value of getporter/org/mybun:v1.2.3 for the bundle reference\n", 555 ) 556 557 err = m.SetDefaults() 558 require.NoError(t, err) 559 assert.Equal(t, "getporter/org/mybun:v1.2.3", m.Reference) 560 assert.Equal(t, "getporter/org/mybun:porter-93d4bfba61358eca91debf6dd4ddc61f", m.Image) 561 }) 562 } 563 564 func TestReadManifest_Validate_MissingFile(t *testing.T) { 565 cxt := portercontext.NewTestContext(t) 566 _, err := ReadManifest(cxt.Context, "fake-porter.yaml", config.NewTestConfig(t).Config) 567 568 assert.EqualError(t, err, "the specified porter configuration file fake-porter.yaml does not exist") 569 } 570 571 func TestMixinDeclaration_UnmarshalYAML(t *testing.T) { 572 cxt := portercontext.NewTestContext(t) 573 cxt.AddTestFile("testdata/mixin-with-config.yaml", config.Name) 574 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 575 576 require.NoError(t, err) 577 assert.Len(t, m.Mixins, 3, "expected 3 mixins") 578 assert.Equal(t, "exec", m.Mixins[0].Name) 579 assert.Equal(t, "az", m.Mixins[1].Name) 580 assert.Equal(t, "terraform", m.Mixins[2].Name) 581 assert.Equal(t, map[string]interface{}{"extensions": []interface{}{"iot"}}, m.Mixins[1].Config) 582 } 583 584 func TestMixinDeclaration_UnmarshalYAML_Invalid(t *testing.T) { 585 cxt := portercontext.NewTestContext(t) 586 cxt.AddTestFile("testdata/mixin-with-bad-config.yaml", config.Name) 587 _, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 588 589 require.Error(t, err) 590 assert.Contains(t, err.Error(), "mixin declaration contained more than one mixin") 591 } 592 593 func TestMixinDeclaration_UnmarshalYAML_Versions(t *testing.T) { 594 cxt := portercontext.NewTestContext(t) 595 cxt.AddTestFile("testdata/mixin-with-versions.yaml", config.Name) 596 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 597 598 execVersion, _ := semver.NewConstraint("1") 599 axVersion, _ := semver.NewConstraint("1.1.X") 600 terraformVersoin, _ := semver.NewConstraint(">=2") 601 602 require.NoError(t, err) 603 assert.Len(t, m.Mixins, 3, "expected 3 mixins") 604 assert.Equal(t, "exec", m.Mixins[0].Name) 605 assert.Equal(t, "az", m.Mixins[1].Name) 606 assert.Equal(t, "terraform", m.Mixins[2].Name) 607 assert.Equal(t, execVersion, m.Mixins[0].Version) 608 assert.Equal(t, axVersion, m.Mixins[1].Version) 609 assert.Equal(t, terraformVersoin, m.Mixins[2].Version) 610 } 611 612 func TestMixinDeclaration_UnmarshalYAML_Versions_Empty(t *testing.T) { 613 cxt := portercontext.NewTestContext(t) 614 cxt.AddTestFile("testdata/mixin-with-empty-version.yaml", config.Name) 615 _, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 616 617 require.Error(t, err) 618 assert.Contains(t, err.Error(), "invalid mixin name/version: improper constraint:") 619 } 620 621 func TestMixinDeclaration_UnmarshalYAML_Versions_Invalid(t *testing.T) { 622 cxt := portercontext.NewTestContext(t) 623 cxt.AddTestFile("testdata/mixin-with-invalid-version.yaml", config.Name) 624 _, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 625 626 require.Error(t, err) 627 assert.Contains(t, err.Error(), "invalid mixin name/version: expected name@version, got: az@this@that") 628 } 629 630 func TestCredentialsDefinition_UnmarshalYAML(t *testing.T) { 631 assertAllCredentialsRequired := func(t *testing.T, creds CredentialDefinitions) { 632 for _, cred := range creds { 633 assert.EqualValuesf(t, true, cred.Required, "Credential: %s should be required", cred.Name) 634 } 635 } 636 t.Run("all credentials in the generated manifest file are required", func(t *testing.T) { 637 cxt := portercontext.NewTestContext(t) 638 cxt.AddTestFile("testdata/with-credentials.yaml", config.Name) 639 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 640 require.NoError(t, err) 641 assertAllCredentialsRequired(t, m.Credentials) 642 643 require.Len(t, m.Credentials, 5) 644 assert.Contains(t, m.Credentials, "kubeconfig", "expected a kubeconfig credential definition") 645 assert.Equal(t, []string{"status", "uninstall"}, m.Credentials["kubeconfig"].ApplyTo, "credential kubeconfig has incorrect applyTo") 646 647 }) 648 } 649 650 func TestMixinDeclaration_MarshalYAML(t *testing.T) { 651 m := struct { 652 Mixins []MixinDeclaration 653 }{ 654 []MixinDeclaration{ 655 {Name: "exec"}, 656 {Name: "az", Config: map[string]interface{}{"extensions": []interface{}{"iot"}}}, 657 {Name: "terraform"}, 658 }, 659 } 660 661 gotYaml, err := yaml.Marshal(m) 662 require.NoError(t, err, "could not marshal data") 663 664 wantYaml, err := os.ReadFile("testdata/mixin-with-config.yaml") 665 require.NoError(t, err, "could not read testdata") 666 667 assert.Equal(t, string(wantYaml), string(gotYaml)) 668 } 669 670 func TestValidateParameterDefinition_missingPath(t *testing.T) { 671 pd := ParameterDefinition{ 672 Name: "myparam", 673 Schema: definition.Schema{ 674 Type: "file", 675 }, 676 } 677 678 pd.Destination = Location{} 679 680 err := pd.Validate() 681 assert.EqualError(t, err, `1 error occurred: 682 * no destination path supplied for parameter myparam 683 684 `) 685 686 pd.Destination.Path = "/path/to/file" 687 688 err = pd.Validate() 689 assert.NoError(t, err) 690 } 691 692 func TestValidateParameterDefinition_invalidSchema(t *testing.T) { 693 pd := ParameterDefinition{ 694 Name: "myparam", 695 Schema: definition.Schema{ 696 Type: "invalid", 697 }, 698 } 699 700 err := pd.Validate() 701 assert.Contains(t, err.Error(), `encountered an error while validating definition for parameter "myparam"`) 702 assert.Contains(t, err.Error(), `schema not valid: error unmarshaling type from json: "invalid" is not a valid type`) 703 } 704 705 func TestValidateParameterDefinition_defaultFailsValidation(t *testing.T) { 706 pd := ParameterDefinition{ 707 Name: "myparam", 708 Schema: definition.Schema{ 709 Type: "string", 710 Default: 1, 711 }, 712 } 713 714 err := pd.Validate() 715 assert.EqualError(t, err, `1 error occurred: 716 * encountered an error validating the default value 1 for parameter "myparam": type should be string, got integer 717 718 `) 719 } 720 721 func TestValidateOutputDefinition_missingPath(t *testing.T) { 722 od := OutputDefinition{ 723 Name: "myoutput", 724 Schema: definition.Schema{ 725 Type: "file", 726 }, 727 } 728 729 err := od.Validate() 730 assert.EqualError(t, err, `1 error occurred: 731 * no path supplied for output myoutput 732 733 `) 734 735 od.Path = "/path/to/file" 736 737 err = od.Validate() 738 assert.NoError(t, err) 739 } 740 741 func TestValidateOutputDefinition_invalidSchema(t *testing.T) { 742 od := OutputDefinition{ 743 Name: "myoutput", 744 Schema: definition.Schema{ 745 Type: "invalid", 746 }, 747 } 748 749 err := od.Validate() 750 assert.Contains(t, err.Error(), `encountered an error while validating definition for output "myoutput"`) 751 assert.Contains(t, err.Error(), `schema not valid: error unmarshaling type from json: "invalid" is not a valid type`) 752 } 753 754 func TestValidateOutputDefinition_defaultFailsValidation(t *testing.T) { 755 od := OutputDefinition{ 756 Name: "myoutput", 757 Schema: definition.Schema{ 758 Type: "string", 759 Default: 1, 760 }, 761 } 762 763 err := od.Validate() 764 assert.EqualError(t, err, `1 error occurred: 765 * encountered an error validating the default value 1 for output "myoutput": type should be string, got integer 766 767 `) 768 } 769 770 func TestValidateImageMap(t *testing.T) { 771 t.Run("with valid image digest, valid repository format and valid tag", func(t *testing.T) { 772 mi := MappedImage{ 773 Repository: "getporter/myserver", 774 Digest: "sha256:8f1133d81f1b078c865cdb11d17d1ff15f55c449d3eecca50190eed0f5e5e26f", 775 Tag: "latest", 776 } 777 778 err := mi.Validate() 779 assert.NoError(t, err) 780 ref, err := mi.ToOCIReference() 781 require.NoError(t, err) 782 require.Equal(t, "getporter/myserver@sha256:8f1133d81f1b078c865cdb11d17d1ff15f55c449d3eecca50190eed0f5e5e26f", ref.String(), "failed to convert image map to its OCI reference") 783 }) 784 t.Run("with valid repository format and valid tag", func(t *testing.T) { 785 mi := MappedImage{ 786 Repository: "getporter/myserver", 787 Tag: "v0.1.0", 788 } 789 790 err := mi.Validate() 791 assert.NoError(t, err) 792 ref, err := mi.ToOCIReference() 793 require.NoError(t, err) 794 require.Equal(t, "getporter/myserver:v0.1.0", ref.String(), "failed to convert image map to its OCI reference") 795 }) 796 t.Run("with both valid image digest and valid repository format", func(t *testing.T) { 797 mi := MappedImage{ 798 Repository: "getporter/myserver", 799 Digest: "sha256:8f1133d81f1b078c865cdb11d17d1ff15f55c449d3eecca50190eed0f5e5e26f", 800 } 801 802 err := mi.Validate() 803 require.NoError(t, err) 804 ref, err := mi.ToOCIReference() 805 require.NoError(t, err) 806 require.Equal(t, "getporter/myserver@sha256:8f1133d81f1b078c865cdb11d17d1ff15f55c449d3eecca50190eed0f5e5e26f", ref.String(), "failed to convert image map to its OCI reference") 807 }) 808 809 t.Run("with no image digest supplied and valid repository format", func(t *testing.T) { 810 mi := MappedImage{ 811 Repository: "getporter/myserver", 812 } 813 814 err := mi.Validate() 815 assert.NoError(t, err) 816 ref, err := mi.ToOCIReference() 817 require.NoError(t, err) 818 require.Equal(t, "getporter/myserver", ref.String(), "failed to convert image map to its OCI reference") 819 }) 820 821 t.Run("with valid image digest but invalid repository format", func(t *testing.T) { 822 mi := MappedImage{ 823 Repository: "getporter//myserver//", 824 Digest: "sha256:8f1133d81f1b078c865cdb11d17d1ff15f55c449d3eecca50190eed0f5e5e26f", 825 } 826 827 err := mi.Validate() 828 assert.Error(t, err) 829 _, err = mi.ToOCIReference() 830 assert.ErrorContains(t, err, "failed to parse named reference") 831 }) 832 833 t.Run("with invalid image digest format", func(t *testing.T) { 834 mi := MappedImage{ 835 Repository: "getporter/myserver", 836 Digest: "abc123", 837 } 838 839 err := mi.Validate() 840 assert.Error(t, err) 841 _, err = mi.ToOCIReference() 842 assert.ErrorContains(t, err, "failed to create a new reference with digest for repository") 843 }) 844 } 845 846 func TestLoadManifestWithCustomData(t *testing.T) { 847 c := config.NewTestConfig(t) 848 849 c.TestContext.AddTestFile("testdata/porter-with-custom-metadata.yaml", config.Name) 850 851 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 852 require.NoError(t, err, "could not load manifest") 853 854 require.NotNil(t, m, "manifest was nil") 855 val, ok := m.Custom["foo"].(map[string]interface{}) 856 require.True(t, ok, "Cannot cast foo value to map[string]interface{}") 857 858 val1, ok := val["test1"].(bool) 859 require.True(t, ok, "Cannot cast test1 value to bool") 860 require.True(t, val1, "test1 value is unexpected") 861 862 val2, ok := val["test2"].(int) 863 require.True(t, ok, "Cannot cast test2 value to int") 864 require.Equal(t, 1, val2, "test2 value is unexpected") 865 866 val3, ok := val["test3"].(string) 867 require.True(t, ok, "Cannot cast test3 value to string") 868 require.Equal(t, "value", val3, "test3 value is unexpected") 869 870 val4, ok := val["test4"].([]interface{}) 871 require.True(t, ok, "Cannot cast test4 value to interface{} array") 872 val5, ok := val4[0].(string) 873 require.True(t, ok, "Cannot cast test4[0] value to string") 874 require.Equal(t, "one", val5, "test4[0] value is unexpected") 875 val6, ok := val4[1].(string) 876 require.True(t, ok, "Cannot cast test4[1] value to string") 877 require.Equal(t, "two", val6, "test4[1] value is unexpected") 878 val7, ok := val4[2].(string) 879 require.True(t, ok, "Cannot cast test4[2] value to string") 880 require.Equal(t, "three", val7, "test4[2] value is unexpected") 881 882 val8, ok := val["test5"].(map[string]interface{}) 883 require.True(t, ok, "Cannot cast test5 value to interface{} array") 884 val9, ok := val8["1"].(string) 885 require.True(t, ok, "Cannot cast test5[0] value to string") 886 require.Equal(t, "one", val9, "test54[0] value is unexpected") 887 val10, ok := val8["two"].(string) 888 require.True(t, ok, "Cannot cast test5[1] value to string") 889 require.Equal(t, "two", val10, "test5[1] value is unexpected") 890 } 891 892 func TestLoadManifestWithRequiredExtensions(t *testing.T) { 893 c := config.NewTestConfig(t) 894 895 c.TestContext.AddTestFile("testdata/porter.yaml", config.Name) 896 897 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 898 require.NoError(t, err, "could not load manifest") 899 900 expected := []RequiredExtension{ 901 RequiredExtension{ 902 Name: "requiredExtension1", 903 }, 904 RequiredExtension{ 905 Name: "requiredExtension2", 906 Config: map[string]interface{}{ 907 "config": true, 908 }, 909 }, 910 } 911 912 assert.NotNil(t, m) 913 assert.Equal(t, expected, m.Required) 914 } 915 916 func TestReadManifest_WithTemplateVariables(t *testing.T) { 917 cxt := portercontext.NewTestContext(t) 918 cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name) 919 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 920 require.NoError(t, err, "ReadManifest failed") 921 wantVars := []string{"bundle.dependencies.mysql.outputs.mysql-password", "bundle.outputs.msg", "bundle.outputs.name"} 922 assert.Equal(t, wantVars, m.TemplateVariables) 923 } 924 925 func TestManifest_GetTemplatedOutputs(t *testing.T) { 926 cxt := portercontext.NewTestContext(t) 927 cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name) 928 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 929 require.NoError(t, err, "ReadManifest failed") 930 931 outputs := m.GetTemplatedOutputs() 932 933 require.Len(t, outputs, 1) 934 assert.Equal(t, "msg", outputs["msg"].Name) 935 } 936 937 func TestManifest_GetTemplatedDependencyOutputs(t *testing.T) { 938 cxt := portercontext.NewTestContext(t) 939 cxt.AddTestFile("testdata/porter-with-templating.yaml", config.Name) 940 m, err := ReadManifest(cxt.Context, config.Name, config.NewTestConfig(t).Config) 941 require.NoError(t, err, "ReadManifest failed") 942 943 outputs := m.GetTemplatedDependencyOutputs() 944 945 require.Len(t, outputs, 1) 946 ref := outputs["mysql.mysql-password"] 947 assert.Equal(t, "mysql", ref.Dependency) 948 assert.Equal(t, "mysql-password", ref.Output) 949 } 950 951 func TestParamToEnvVar(t *testing.T) { 952 testcases := []struct { 953 name string 954 paramName string 955 envName string 956 }{ 957 {"no special characters", "myparam", "MYPARAM"}, 958 {"dash", "my-param", "MY_PARAM"}, 959 {"period", "my.param", "MY_PARAM"}, 960 } 961 962 for _, tc := range testcases { 963 t.Run(tc.name, func(t *testing.T) { 964 got := ParamToEnvVar(tc.paramName) 965 assert.Equal(t, tc.envName, got) 966 }) 967 } 968 } 969 970 func TestParameterDefinition_UpdateApplyTo(t *testing.T) { 971 c := config.NewTestConfig(t) 972 973 c.TestContext.AddTestFile("testdata/simple.porter.yaml", config.Name) 974 975 m, err := LoadManifestFrom(context.Background(), c.Config, config.Name) 976 require.NoError(t, err, "could not load manifest") 977 978 testcases := []struct { 979 name string 980 defaultValue string 981 applyTo []string 982 source ParameterSource 983 wantApplyTo []string 984 }{ 985 {"no source", "", nil, ParameterSource{}, nil}, 986 {"has default", "myparam", nil, ParameterSource{Output: "myoutput"}, nil}, 987 {"has applyTo", "", []string{"status"}, ParameterSource{Output: "myoutput"}, []string{"status"}}, 988 {"no default, no applyTo", "", nil, ParameterSource{Output: "myoutput"}, []string{"status", "uninstall"}}, 989 } 990 991 for _, tc := range testcases { 992 t.Run(tc.name, func(t *testing.T) { 993 pd := ParameterDefinition{ 994 Name: "myparam", 995 Schema: definition.Schema{ 996 Type: "file", 997 }, 998 Source: tc.source, 999 ApplyTo: tc.applyTo, 1000 } 1001 1002 if tc.defaultValue != "" { 1003 pd.Schema.Default = tc.defaultValue 1004 } 1005 1006 pd.UpdateApplyTo(m) 1007 require.Equal(t, tc.wantApplyTo, pd.ApplyTo) 1008 }) 1009 } 1010 } 1011 1012 func TestManifest_getTemplatePrefix(t *testing.T) { 1013 testcases := []struct { 1014 schemaVersion string 1015 wantPrefix string 1016 }{ 1017 {"", ""}, 1018 {"1.0.0-alpha.1", ""}, 1019 {"1.0.0-alpha.2", TemplateDelimiterPrefix}, 1020 {"1.0.0", TemplateDelimiterPrefix}, 1021 {"1.1.0", TemplateDelimiterPrefix}, 1022 {"3.0.0", TemplateDelimiterPrefix}, 1023 } 1024 for _, tc := range testcases { 1025 t.Run(tc.schemaVersion, func(t *testing.T) { 1026 m := Manifest{SchemaVersion: tc.schemaVersion} 1027 prefix := m.GetTemplatePrefix() 1028 require.Equal(t, tc.wantPrefix, prefix) 1029 }) 1030 } 1031 } 1032 1033 func TestManifest_DetermineDependenciesExtensionUsed(t *testing.T) { 1034 t.Run("no dependencies used", func(t *testing.T) { 1035 m := Manifest{} 1036 depsExt := m.DetermineDependenciesExtensionUsed() 1037 assert.Empty(t, depsExt) 1038 }) 1039 1040 t.Run("v1 features only", func(t *testing.T) { 1041 m := Manifest{ 1042 Dependencies: Dependencies{Requires: []*Dependency{ 1043 { 1044 Name: "mysql", 1045 Bundle: BundleCriteria{Reference: "mysql:5.7", Version: "5.7 - 6"}, 1046 Parameters: map[string]string{"loglevel": "4"}, 1047 }, 1048 }}, 1049 } 1050 depsExt := m.DetermineDependenciesExtensionUsed() 1051 assert.Equal(t, cnab.DependenciesV1ExtensionKey, depsExt) 1052 }) 1053 1054 t.Run("v2 declared but no deps defined", func(t *testing.T) { 1055 m := Manifest{ 1056 Required: []RequiredExtension{ 1057 {Name: cnab.DependenciesV2ExtensionShortHand}, 1058 }, 1059 } 1060 depsExt := m.DetermineDependenciesExtensionUsed() 1061 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1062 }) 1063 1064 t.Run("v2 shorthand declared", func(t *testing.T) { 1065 m := Manifest{ 1066 Required: []RequiredExtension{ 1067 {Name: cnab.DependenciesV2ExtensionShortHand}, 1068 }, 1069 } 1070 depsExt := m.DetermineDependenciesExtensionUsed() 1071 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1072 }) 1073 1074 t.Run("v2 full key declared", func(t *testing.T) { 1075 m := Manifest{ 1076 Required: []RequiredExtension{ 1077 {Name: cnab.DependenciesV2ExtensionKey}, 1078 }, 1079 Dependencies: Dependencies{Requires: []*Dependency{ 1080 {Name: "mysql", Bundle: BundleCriteria{Reference: "mysql:5.7", Version: "5.7 - 6"}}, 1081 }}, 1082 } 1083 depsExt := m.DetermineDependenciesExtensionUsed() 1084 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1085 }) 1086 1087 t.Run("provides interface used", func(t *testing.T) { 1088 m := Manifest{ 1089 1090 Dependencies: Dependencies{ 1091 Provides: &DependencyProvider{Interface: InterfaceDeclaration{ID: "myinterface"}}, 1092 }, 1093 } 1094 depsExt := m.DetermineDependenciesExtensionUsed() 1095 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1096 }) 1097 1098 t.Run("provides interface empty", func(t *testing.T) { 1099 // Even if they aren't using it, declaring that the bundle provides an (empty) interface is enough that 1100 // we should use the v2 dependency 1101 m := Manifest{ 1102 Dependencies: Dependencies{ 1103 Provides: &DependencyProvider{Interface: InterfaceDeclaration{ID: ""}}, 1104 }, 1105 } 1106 depsExt := m.DetermineDependenciesExtensionUsed() 1107 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1108 }) 1109 1110 t.Run("bundle interface criteria used", func(t *testing.T) { 1111 m := Manifest{ 1112 Dependencies: Dependencies{Requires: []*Dependency{ 1113 { 1114 Name: "mysql", 1115 Bundle: BundleCriteria{ 1116 Reference: "mysql:5.7", 1117 Version: "5.7 - 6", 1118 Interface: &BundleInterface{}}}, 1119 }}, 1120 } 1121 depsExt := m.DetermineDependenciesExtensionUsed() 1122 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1123 }) 1124 1125 t.Run("sharing criteria used", func(t *testing.T) { 1126 m := Manifest{ 1127 Dependencies: Dependencies{Requires: []*Dependency{ 1128 { 1129 Name: "mysql", 1130 Bundle: BundleCriteria{Reference: "mysql:5.7", Version: "5.7 - 6"}, 1131 Sharing: SharingCriteria{Mode: true, Group: SharingGroup{Name: "myapp"}}, 1132 }, 1133 }}, 1134 } 1135 depsExt := m.DetermineDependenciesExtensionUsed() 1136 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1137 }) 1138 1139 t.Run("credential wiring used", func(t *testing.T) { 1140 m := Manifest{ 1141 Dependencies: Dependencies{Requires: []*Dependency{ 1142 { 1143 Name: "mysql", 1144 Bundle: BundleCriteria{Reference: "mysql:5.7", Version: "5.7 - 6"}, 1145 Credentials: map[string]string{"kubeconfig": "${bundle.credentials.kubeconfig}"}, 1146 }, 1147 }}, 1148 } 1149 depsExt := m.DetermineDependenciesExtensionUsed() 1150 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1151 }) 1152 1153 t.Run("output wiring used", func(t *testing.T) { 1154 m := Manifest{ 1155 Dependencies: Dependencies{Requires: []*Dependency{ 1156 { 1157 Name: "mysql", 1158 Bundle: BundleCriteria{Reference: "mysql:5.7", Version: "5.7 - 6"}, 1159 Outputs: map[string]string{"endpoint": "https://${outputs.host}:${outputs.port}/myapp"}, 1160 }, 1161 }}, 1162 } 1163 depsExt := m.DetermineDependenciesExtensionUsed() 1164 assert.Equal(t, cnab.DependenciesV2ExtensionKey, depsExt) 1165 }) 1166 }