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  }