get.porter.sh/porter@v1.3.0/pkg/porter/lifecycle_test.go (about)

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"runtime"
     7  	"sort"
     8  	"testing"
     9  
    10  	"get.porter.sh/porter/pkg"
    11  	"get.porter.sh/porter/pkg/cnab"
    12  	configadapter "get.porter.sh/porter/pkg/cnab/config-adapter"
    13  	cnabprovider "get.porter.sh/porter/pkg/cnab/provider"
    14  	"get.porter.sh/porter/pkg/config"
    15  	"get.porter.sh/porter/pkg/manifest"
    16  	"get.porter.sh/porter/pkg/secrets"
    17  	"get.porter.sh/porter/pkg/storage"
    18  	"get.porter.sh/porter/tests"
    19  	"github.com/cnabio/cnab-go/secrets/host"
    20  	"github.com/cnabio/cnab-to-oci/relocation"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  var (
    26  	kahnlatest = cnab.MustParseOCIReference("deislabs/kubekahn:latest")
    27  )
    28  
    29  func TestInstallFromTagIgnoresCurrentBundle(t *testing.T) {
    30  	p := NewTestPorter(t)
    31  	defer p.Close()
    32  
    33  	err := p.Create()
    34  	require.NoError(t, err)
    35  
    36  	installOpts := NewInstallOptions()
    37  	installOpts.Reference = "mybun:1.0"
    38  
    39  	err = installOpts.Validate(context.Background(), []string{}, p.Porter)
    40  	require.NoError(t, err)
    41  
    42  	assert.Empty(t, installOpts.File, "The install should ignore the bundle in the current directory because we are installing from a tag")
    43  }
    44  
    45  func TestPorter_BuildActionArgs(t *testing.T) {
    46  	ctx := context.Background()
    47  
    48  	t.Run("no bundle set", func(t *testing.T) {
    49  		p := NewTestPorter(t)
    50  		defer p.Close()
    51  		opts := NewInstallOptions()
    52  		opts.Name = "mybuns"
    53  
    54  		err := opts.Validate(ctx, nil, p.Porter)
    55  		require.Error(t, err, "Validate should fail")
    56  		assert.Contains(t, err.Error(), "No bundle specified")
    57  	})
    58  
    59  	t.Run("porter.yaml set", func(t *testing.T) {
    60  		p := NewTestPorter(t)
    61  		defer p.Close()
    62  
    63  		opts := NewInstallOptions()
    64  		opts.File = "porter.yaml"
    65  		p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml")
    66  		p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", ".cnab/bundle.json")
    67  
    68  		// pretend that we've resolved the parameters
    69  		opts.finalParams = map[string]interface{}{}
    70  
    71  		err := opts.Validate(ctx, nil, p.Porter)
    72  		require.NoError(t, err, "Validate failed")
    73  		args, err := p.BuildActionArgs(ctx, storage.Installation{}, opts)
    74  		require.NoError(t, err, "BuildActionArgs failed")
    75  
    76  		assert.NotEmpty(t, args.BundleReference.Definition)
    77  	})
    78  
    79  	// Just do a quick check that things are populated correctly when a bundle.json is passed
    80  	t.Run("bundle.json set", func(t *testing.T) {
    81  		p := NewTestPorter(t)
    82  		opts := NewInstallOptions()
    83  		opts.CNABFile = "/bundle.json"
    84  		p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json")
    85  
    86  		// pretend that we've resolved the parameters
    87  		opts.finalParams = map[string]interface{}{}
    88  
    89  		err := opts.Validate(ctx, nil, p.Porter)
    90  		require.NoError(t, err, "Validate failed")
    91  		args, err := p.BuildActionArgs(ctx, storage.Installation{}, opts)
    92  		require.NoError(t, err, "BuildActionArgs failed")
    93  
    94  		assert.NotEmpty(t, args.BundleReference.Definition, "BundlePath was not populated correctly")
    95  	})
    96  
    97  	t.Run("remaining fields", func(t *testing.T) {
    98  		p := NewTestPorter(t)
    99  		p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml")
   100  		p.TestConfig.TestContext.AddTestFileFromRoot("pkg/runtime/testdata/relocation-mapping.json", "relocation-mapping.json")
   101  		p.TestCredentials.AddTestCredentials("testdata/test-creds/mycreds.yaml")
   102  
   103  		opts := InstallOptions{
   104  			BundleExecutionOptions: &BundleExecutionOptions{
   105  				AllowDockerHostAccess: true,
   106  				DebugMode:             true,
   107  				Params: []string{
   108  					"my-first-param=1",
   109  				},
   110  				ParameterSets: []string{
   111  					"porter-hello",
   112  				},
   113  				CredentialIdentifiers: []string{
   114  					"mycreds",
   115  				},
   116  				Driver: "docker",
   117  				BundleReferenceOptions: &BundleReferenceOptions{
   118  					installationOptions: installationOptions{
   119  						BundleDefinitionOptions: BundleDefinitionOptions{
   120  							RelocationMapping: "relocation-mapping.json",
   121  							File:              config.Name,
   122  						},
   123  						Name: "MyInstallation",
   124  					},
   125  				},
   126  			},
   127  		}
   128  		p.TestParameters.AddSecret("PARAM2_SECRET", "VALUE2")
   129  		p.TestParameters.AddTestParameters("testdata/paramset2.json")
   130  
   131  		err := opts.Validate(ctx, nil, p.Porter)
   132  		require.NoError(t, err, "Validate failed")
   133  		existingInstall := storage.NewInstallation(opts.Namespace, opts.Name)
   134  
   135  		// resolve the parameters before building the action options to use for running the bundle
   136  		err = p.applyActionOptionsToInstallation(ctx, opts, &existingInstall)
   137  		require.NoError(t, err)
   138  
   139  		args, err := p.BuildActionArgs(ctx, existingInstall, opts)
   140  		require.NoError(t, err, "BuildActionArgs failed")
   141  
   142  		assert.Equal(t, opts.AllowDockerHostAccess, args.AllowDockerHostAccess, "AllowDockerHostAccess not populated correctly")
   143  		assert.Equal(t, opts.Driver, args.Driver, "Driver not populated correctly")
   144  		assert.NotEmpty(t, args.Installation, "Installation not populated")
   145  		wantReloMap := relocation.ImageRelocationMap{"gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687": "my.registry/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"}
   146  		assert.Equal(t, wantReloMap, args.BundleReference.RelocationMap, "RelocationMapping not populated correctly")
   147  	})
   148  }
   149  
   150  func TestManifestIgnoredWithTag(t *testing.T) {
   151  	p := NewTestPorter(t)
   152  	defer p.Close()
   153  
   154  	t.Run("ignore manifest in cwd if tag present", func(t *testing.T) {
   155  		opts := BundleReferenceOptions{}
   156  		opts.Reference = "deislabs/kubekahn:latest"
   157  
   158  		// `path.Join(wd...` -> makes cnab.go#defaultBundleFiles#manifestExists `true`
   159  		// Only when `manifestExists` eq to `true`, default bundle logic will run
   160  		require.NoError(t, p.TestConfig.TestContext.AddTestFileContents([]byte(""), config.Name))
   161  		// When execution reach to `readFromFile`, manifest file path will be lost.
   162  		// So, had to use root manifest file also for error simulation purpose
   163  		require.NoError(t, p.TestConfig.TestContext.AddTestFileContents([]byte(""), config.Name))
   164  
   165  		err := opts.Validate(context.Background(), nil, p.Porter)
   166  		require.NoError(t, err, "Validate failed")
   167  	})
   168  }
   169  
   170  func TestBundleActionOptions_Validate(t *testing.T) {
   171  	t.Run("driver flag unset", func(t *testing.T) {
   172  		p := NewTestPorter(t)
   173  		p.DataLoader = config.LoadFromEnvironment()
   174  		require.NoError(t, p.FileSystem.WriteFile("/home/myuser/.porter/config.yaml", []byte("runtime-driver: kubernetes"), pkg.FileModeWritable))
   175  		ctx, err := p.Connect(context.Background())
   176  		require.NoError(t, err)
   177  
   178  		opts := NewInstallOptions()
   179  		opts.Reference = "ghcr.io/getporter/examples/porter-hello:v0.2.0"
   180  		require.NoError(t, opts.Validate(ctx, nil, p.Porter))
   181  		assert.Equal(t, "kubernetes", opts.Driver)
   182  	})
   183  	t.Run("driver flag set", func(t *testing.T) {
   184  		p := NewTestPorter(t)
   185  		p.DataLoader = config.LoadFromEnvironment()
   186  		require.NoError(t, p.FileSystem.WriteFile("/home/myuser/.porter/config.yaml", []byte("driver: kubernetes"), pkg.FileModeWritable))
   187  		ctx, err := p.Connect(context.Background())
   188  		require.NoError(t, err)
   189  
   190  		opts := NewInstallOptions()
   191  		opts.Driver = "docker"
   192  		opts.Reference = "ghcr.io/getporter/examples/porter-hello:v0.2.0"
   193  		require.NoError(t, opts.Validate(ctx, nil, p.Porter))
   194  		assert.Equal(t, "docker", opts.Driver)
   195  	})
   196  }
   197  
   198  func TestBundleExecutionOptions_defaultDriver(t *testing.T) {
   199  	t.Run("no driver specified", func(t *testing.T) {
   200  		p := NewTestPorter(t)
   201  		defer p.Close()
   202  
   203  		opts := NewBundleExecutionOptions()
   204  
   205  		opts.defaultDriver(p.Porter)
   206  
   207  		assert.Equal(t, "docker", opts.Driver, "expected the driver value to default to docker")
   208  	})
   209  
   210  	t.Run("driver flag set", func(t *testing.T) {
   211  		p := NewTestPorter(t)
   212  		defer p.Close()
   213  
   214  		opts := NewBundleExecutionOptions()
   215  		opts.Driver = "kubernetes"
   216  
   217  		opts.defaultDriver(p.Porter)
   218  
   219  		assert.Equal(t, "kubernetes", opts.Driver, "expected the --driver flag value to be used")
   220  	})
   221  
   222  	t.Run("allow docker host access defaults to config", func(t *testing.T) {
   223  		p := NewTestPorter(t)
   224  		defer p.Close()
   225  		p.Config.Data.AllowDockerHostAccess = true
   226  
   227  		opts := NewBundleExecutionOptions()
   228  
   229  		opts.defaultDriver(p.Porter)
   230  
   231  		assert.True(t, opts.AllowDockerHostAccess, "expected allow-docker-host-access to inherit the value from the config file when the flag isn't specified")
   232  	})
   233  
   234  	t.Run("allow docker host access flag set", func(t *testing.T) {
   235  		p := NewTestPorter(t)
   236  		defer p.Close()
   237  		p.Config.Data.AllowDockerHostAccess = false
   238  
   239  		opts := NewBundleExecutionOptions()
   240  		opts.AllowDockerHostAccess = true
   241  
   242  		opts.defaultDriver(p.Porter)
   243  
   244  		assert.True(t, opts.AllowDockerHostAccess, "expected allow-docker-host-access to use the flag value when specified")
   245  	})
   246  
   247  }
   248  
   249  func TestBundleExecutionOptions_ParseParamSets(t *testing.T) {
   250  	p := NewTestPorter(t)
   251  	defer p.Close()
   252  
   253  	p.AddTestFile("testdata/porter.yaml", "porter.yaml")
   254  	p.TestParameters.AddSecret("foo_secret", "foo_value")
   255  	p.TestParameters.AddSecret("PARAM2_SECRET", "VALUE2")
   256  	p.TestParameters.AddTestParameters("testdata/paramset2.json")
   257  
   258  	ctx := context.Background()
   259  	m, err := manifest.LoadManifestFrom(ctx, p.Config, config.Name)
   260  	require.NoError(t, err)
   261  	bun, err := configadapter.ConvertToTestBundle(ctx, p.Config, m)
   262  	require.NoError(t, err)
   263  
   264  	opts := NewUpgradeOptions()
   265  	opts.ParameterSets = []string{"porter-hello"}
   266  	opts.bundleRef = &cnab.BundleReference{Definition: bun}
   267  
   268  	err = opts.Validate(ctx, []string{}, p.Porter)
   269  	assert.NoError(t, err)
   270  
   271  	inst := storage.NewInstallation("", "mybuns")
   272  	err = p.applyActionOptionsToInstallation(ctx, opts, &inst)
   273  	require.NoError(t, err)
   274  
   275  	wantParams := map[string]interface{}{
   276  		"my-second-param": "VALUE2",
   277  		"porter-debug":    false,
   278  		"porter-state":    nil,
   279  	}
   280  	assert.Equal(t, wantParams, opts.GetParameters(), "resolved unexpected parameter values")
   281  }
   282  
   283  func TestBundleExecutionOptions_ParseParamSets_Failed(t *testing.T) {
   284  	p := NewTestPorter(t)
   285  	defer p.Close()
   286  
   287  	p.TestConfig.TestContext.AddTestFile("testdata/porter-with-file-param.yaml", config.Name)
   288  	p.TestConfig.TestContext.AddTestFile("testdata/paramset-with-file-param.json", "/paramset.json")
   289  
   290  	ctx := context.Background()
   291  	m, err := manifest.LoadManifestFrom(ctx, p.Config, config.Name)
   292  	require.NoError(t, err)
   293  	bun, err := configadapter.ConvertToTestBundle(ctx, p.Config, m)
   294  	require.NoError(t, err)
   295  
   296  	opts := NewInstallOptions()
   297  	opts.ParameterSets = []string{
   298  		"/paramset.json",
   299  	}
   300  	opts.bundleRef = &cnab.BundleReference{Definition: bun}
   301  
   302  	err = opts.Validate(ctx, []string{}, p.Porter)
   303  	assert.NoError(t, err)
   304  
   305  	inst := storage.NewInstallation("myns", "mybuns")
   306  
   307  	err = p.applyActionOptionsToInstallation(ctx, opts, &inst)
   308  	tests.RequireErrorContains(t, err, "/paramset.json not found", "Porter no longer supports passing a parameter set file to the -p flag, validate that passing a file doesn't work")
   309  }
   310  
   311  // Validate that when an installation is run with a mix of overrides and parameter sets
   312  // that it follows the rules for the paramter hierarchy
   313  // highest -> lowest preceence
   314  // - user override
   315  // - previous value from last run
   316  // - value resolved from a named parameter set
   317  // - default value of the parameter
   318  func TestPorter_applyActionOptionsToInstallation_FollowsParameterHierarchy(t *testing.T) {
   319  	t.Parallel()
   320  
   321  	p := NewTestPorter(t)
   322  	defer p.Close()
   323  	ctx := context.Background()
   324  
   325  	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", config.Name)
   326  	m, err := manifest.LoadManifestFrom(context.Background(), p.Config, config.Name)
   327  	require.NoError(t, err)
   328  	bun, err := configadapter.ConvertToTestBundle(ctx, p.Config, m)
   329  	require.NoError(t, err)
   330  
   331  	err = p.TestParameters.InsertParameterSet(ctx, storage.NewParameterSet("", "myps",
   332  		storage.ValueStrategy("my-second-param", "via_paramset")))
   333  	require.NoError(t, err, "Create my-second-param parameter set failed")
   334  
   335  	makeOpts := func() InstallOptions {
   336  		opts := NewInstallOptions()
   337  		opts.BundleReferenceOptions.bundleRef = &cnab.BundleReference{
   338  			Reference:  kahnlatest,
   339  			Definition: bun,
   340  		}
   341  		return opts
   342  	}
   343  
   344  	t.Run("no override present, no parameter set present", func(t *testing.T) {
   345  		i := storage.NewInstallation("", bun.Name)
   346  		opts := makeOpts()
   347  		err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   348  		require.NoError(t, err)
   349  
   350  		finalParams := opts.GetParameters()
   351  		wantParams := map[string]interface{}{
   352  			"my-first-param":  9,
   353  			"my-second-param": "spring-music-demo",
   354  			"porter-debug":    false,
   355  			"porter-state":    nil,
   356  		}
   357  		assert.Equal(t, wantParams, finalParams,
   358  			"expected combined params to have the default parameter values from the bundle")
   359  	})
   360  
   361  	t.Run("override present, no parameter set present", func(t *testing.T) {
   362  		i := storage.NewInstallation("", bun.Name)
   363  		opts := makeOpts()
   364  		opts.Params = []string{"my-second-param=cli_override"}
   365  		err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   366  		require.NoError(t, err)
   367  
   368  		finalParams := opts.GetParameters()
   369  		require.Contains(t, finalParams, "my-second-param",
   370  			"expected my-second-param to be a parameter")
   371  		require.Equal(t, "cli_override", finalParams["my-second-param"],
   372  			"expected param 'my-second-param' to be set with the override specified by the user")
   373  	})
   374  
   375  	t.Run("no override present, parameter set present", func(t *testing.T) {
   376  		i := storage.NewInstallation("", bun.Name)
   377  		opts := makeOpts()
   378  		opts.ParameterSets = []string{"myps"}
   379  		err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   380  		require.NoError(t, err)
   381  
   382  		finalParams := opts.GetParameters()
   383  		require.Contains(t, finalParams, "my-second-param",
   384  			"expected my-second-param to be a parameter")
   385  		require.Equal(t, finalParams["my-second-param"], "via_paramset",
   386  			"expected param 'my-second-param' to be set with the value from the parameter set")
   387  	})
   388  
   389  	t.Run("override present, parameter set present", func(t *testing.T) {
   390  		i := storage.NewInstallation("", bun.Name)
   391  		opts := makeOpts()
   392  		opts.Params = []string{"my-second-param=cli_override"}
   393  		opts.ParameterSets = []string{"myps"}
   394  		err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   395  		require.NoError(t, err)
   396  
   397  		finalParams := opts.GetParameters()
   398  		require.Contains(t, finalParams, "my-second-param",
   399  			"expected my-second-param to be a parameter")
   400  		require.Equal(t, finalParams["my-second-param"], "cli_override",
   401  			"expected param 'my-second-param' to be set with the value of the user override, which has precedence over the parameter set value")
   402  	})
   403  
   404  	t.Run("debug mode on", func(t *testing.T) {
   405  		i := storage.NewInstallation("", bun.Name)
   406  		opts := makeOpts()
   407  		opts.DebugMode = true
   408  		err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   409  		require.NoError(t, err)
   410  
   411  		finalParams := opts.GetParameters()
   412  		debugParam, ok := finalParams["porter-debug"]
   413  		require.True(t, ok, "expected porter-debug to be set")
   414  		require.Equal(t, true, debugParam, "expected porter-debug to be true")
   415  	})
   416  }
   417  
   418  // Validate that when we resolve parameters on an installation that sensitive parameters are
   419  // not persisted on the installation record and instead are referenced by a secret
   420  func TestPorter_applyActionOptionsToInstallation_sanitizesParameters(t *testing.T) {
   421  	p := NewTestPorter(t)
   422  	defer p.Close()
   423  
   424  	ctx := context.Background()
   425  
   426  	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", config.Name)
   427  	m, err := manifest.LoadManifestFrom(context.Background(), p.Config, config.Name)
   428  	require.NoError(t, err)
   429  	bun, err := configadapter.ConvertToTestBundle(ctx, p.Config, m)
   430  	require.NoError(t, err)
   431  
   432  	sensitiveParamName := "my-second-param"
   433  	sensitiveParamValue := "2"
   434  	nonsensitiveParamName := "my-first-param"
   435  	nonsensitiveParamValue := "1"
   436  	opts := NewInstallOptions()
   437  	opts.BundleReferenceOptions.bundleRef = &cnab.BundleReference{
   438  		Reference:  kahnlatest,
   439  		Definition: bun,
   440  	}
   441  	opts.Params = []string{nonsensitiveParamName + "=" + nonsensitiveParamValue, sensitiveParamName + "=" + sensitiveParamValue}
   442  
   443  	i := storage.NewInstallation("", bun.Name)
   444  
   445  	err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   446  	require.NoError(t, err)
   447  	require.Len(t, i.Parameters.Parameters, 2)
   448  
   449  	// there should be no sensitive value on installation record
   450  	for _, param := range i.Parameters.Parameters {
   451  		if param.Name == sensitiveParamName {
   452  			require.Equal(t, param.Source.Strategy, secrets.SourceSecret)
   453  			require.NotEqual(t, param.Source.Hint, sensitiveParamValue)
   454  			continue
   455  		}
   456  		require.Equal(t, param.Source.Strategy, host.SourceValue)
   457  		require.Equal(t, param.Source.Hint, nonsensitiveParamValue)
   458  	}
   459  
   460  	// When no parameter override specified, installation record should be updated
   461  	// as well
   462  	opts = NewInstallOptions()
   463  	opts.BundleReferenceOptions.bundleRef = &cnab.BundleReference{
   464  		Reference:  kahnlatest,
   465  		Definition: bun,
   466  	}
   467  	err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   468  	require.NoError(t, err)
   469  
   470  	// Check that when no parameter overrides are specified, we use the originally specified parameters from the previous run
   471  	sort.Sort(i.Parameters.Parameters)
   472  	require.Len(t, i.Parameters.Parameters, 2)
   473  	require.Equal(t, "my-first-param", i.Parameters.Parameters[0].Name)
   474  	require.Equal(t, "1", i.Parameters.Parameters[0].Source.Hint)
   475  	require.Equal(t, "my-second-param", i.Parameters.Parameters[1].Name)
   476  	require.Equal(t, "secret", i.Parameters.Parameters[1].Source.Strategy)
   477  }
   478  
   479  // When the installation has been used before with a parameter value
   480  // the previous param value should be updated and the other previous values
   481  // that were not set this time around should be re-used.
   482  // i.e. you should be able to run
   483  // porter install --param logLevel=debug --param featureA=enabled
   484  // porter upgrade --param logLevel=info
   485  // and when upgrade is run, the old value for featureA is kept
   486  func TestPorter_applyActionOptionsToInstallation_PreservesExistingParams(t *testing.T) {
   487  	p := NewTestPorter(t)
   488  	defer p.Close()
   489  
   490  	ctx := context.Background()
   491  
   492  	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", config.Name)
   493  	m, err := manifest.LoadManifestFrom(context.Background(), p.Config, config.Name)
   494  	require.NoError(t, err)
   495  	bun, err := configadapter.ConvertToTestBundle(ctx, p.Config, m)
   496  	require.NoError(t, err)
   497  
   498  	nonsensitiveParamName := "my-first-param"
   499  	nonsensitiveParamValue := "3"
   500  	opts := NewUpgradeOptions()
   501  	opts.BundleReferenceOptions.bundleRef = &cnab.BundleReference{
   502  		Reference:  kahnlatest,
   503  		Definition: bun,
   504  	}
   505  	opts.Params = []string{nonsensitiveParamName + "=" + nonsensitiveParamValue}
   506  
   507  	i := storage.NewInstallation("", bun.Name)
   508  	i.Parameters = storage.NewParameterSet("", "internal-ps",
   509  		storage.ValueStrategy("my-first-param", "1"),
   510  		storage.ValueStrategy("my-second-param", "2"),
   511  	)
   512  
   513  	err = p.applyActionOptionsToInstallation(ctx, opts, &i)
   514  	require.NoError(t, err)
   515  	require.Len(t, i.Parameters.Parameters, 2)
   516  
   517  	// Check that overrides are applied on top of existing parameters
   518  	require.Len(t, i.Parameters.Parameters, 2)
   519  	require.Equal(t, "my-first-param", i.Parameters.Parameters[0].Name)
   520  	require.Equal(t, "value", i.Parameters.Parameters[0].Source.Strategy, "my-first-param isn't sensitive and can be stored in a hard-coded value")
   521  	require.Equal(t, "my-second-param", i.Parameters.Parameters[1].Name)
   522  	require.Equal(t, "secret", i.Parameters.Parameters[1].Source.Strategy, "my-second-param should be stored on the installation using a secret since it's sensitive")
   523  
   524  	// Check the values stored are correct
   525  	params, err := p.Parameters.ResolveAll(ctx, i.Parameters, i.Parameters.Keys())
   526  	require.NoError(t, err, "Failed to resolve the installation parameters")
   527  	require.Equal(t, secrets.Set{
   528  		"my-first-param":  "3", // Should have used the override
   529  		"my-second-param": "2", // Should have kept the existing value from the last run
   530  	}, params, "Incorrect parameter values were persisted on the installationß")
   531  }
   532  
   533  func Test_ensureVPrefix(t *testing.T) {
   534  	ref, err := cnab.ParseOCIReference("registry/bundle:1.2.3")
   535  	require.NoError(t, err)
   536  
   537  	testCases := []struct {
   538  		name       string
   539  		reference  string
   540  		ref        *cnab.OCIReference
   541  		want       string
   542  		wantRefTag string
   543  	}{
   544  		{
   545  			name:      "adds v prefix to semver reference",
   546  			reference: "registry/bundle:1.2.3",
   547  			ref:       nil,
   548  			want:      "registry/bundle:v1.2.3",
   549  		},
   550  		{
   551  			name:       "updates _ref if present",
   552  			reference:  "registry/bundle:1.2.3",
   553  			ref:        &ref,
   554  			want:       "registry/bundle:v1.2.3",
   555  			wantRefTag: "v1.2.3",
   556  		},
   557  		{
   558  			name:      "is idempotent",
   559  			reference: "registry/bundle:v1.2.3",
   560  			ref:       nil,
   561  			want:      "registry/bundle:v1.2.3",
   562  		},
   563  		{
   564  			name:      "ignores non-semver references",
   565  			reference: "registry/bundle:latest",
   566  			ref:       nil,
   567  			want:      "registry/bundle:latest",
   568  		},
   569  		{
   570  			name:      "ignores references with no tag",
   571  			reference: "registry/bundle",
   572  			ref:       nil,
   573  			want:      "registry/bundle",
   574  		},
   575  	}
   576  
   577  	for _, tc := range testCases {
   578  		t.Run(tc.name, func(t *testing.T) {
   579  			opts := BundleReferenceOptions{
   580  				installationOptions: installationOptions{},
   581  				BundlePullOptions: BundlePullOptions{
   582  					Reference:        tc.reference,
   583  					_ref:             tc.ref,
   584  					InsecureRegistry: false,
   585  					Force:            false,
   586  				},
   587  				bundleRef: nil,
   588  			}
   589  
   590  			err = ensureVPrefix(&opts, io.Discard)
   591  
   592  			assert.Equal(t, tc.want, opts.BundlePullOptions.Reference)
   593  			assert.NoError(t, err)
   594  			if tc.wantRefTag == "" {
   595  				assert.Nil(t, opts.BundlePullOptions._ref)
   596  			} else {
   597  				require.NotNil(t, opts.BundlePullOptions._ref)
   598  				assert.Equal(t, tc.wantRefTag, opts.BundlePullOptions._ref.Tag())
   599  			}
   600  		})
   601  	}
   602  }
   603  
   604  func TestBundleExecutionOptions_GetHostVolumeMounts(t *testing.T) {
   605  	t.Run("valid host volume mounts", func(t *testing.T) {
   606  		opts := &BundleExecutionOptions{
   607  			HostVolumeMounts: []string{
   608  				"/host/path:/target/path:ro",
   609  				"/host/path:/target/path:rw",
   610  				"/host/path:/target/path",
   611  			},
   612  		}
   613  
   614  		expected := []cnabprovider.HostVolumeMountSpec{
   615  			{
   616  				Source:   "/host/path",
   617  				Target:   "/target/path",
   618  				ReadOnly: true,
   619  			},
   620  			{
   621  				Source:   "/host/path",
   622  				Target:   "/target/path",
   623  				ReadOnly: false,
   624  			},
   625  			{
   626  				Source:   "/host/path",
   627  				Target:   "/target/path",
   628  				ReadOnly: true,
   629  			},
   630  		}
   631  
   632  		actual := opts.GetHostVolumeMounts()
   633  
   634  		if len(expected) != len(actual) {
   635  			t.Errorf("expected %v but got %v", expected, actual)
   636  		}
   637  		for i := range expected {
   638  			if expected[i].Source != actual[i].Source {
   639  				t.Errorf("expected %v but got %v", expected[i].Source, actual[i].Source)
   640  			}
   641  			if expected[i].Target != actual[i].Target {
   642  				t.Errorf("expected %v but got %v", expected[i].Target, actual[i].Target)
   643  			}
   644  			if expected[i].ReadOnly != actual[i].ReadOnly {
   645  				t.Errorf("expected %v but got %v", expected[i].ReadOnly, actual[i].ReadOnly)
   646  			}
   647  		}
   648  	})
   649  
   650  	t.Run("invalid host volume mounts", func(t *testing.T) {
   651  		opts := &BundleExecutionOptions{
   652  			HostVolumeMounts: []string{
   653  				"1=",
   654  				"/host/path",
   655  			},
   656  		}
   657  
   658  		actual := opts.GetHostVolumeMounts()
   659  
   660  		if len(actual) != 0 {
   661  			t.Errorf("expected no host volume mounts but got %v", actual)
   662  		}
   663  
   664  	})
   665  
   666  	t.Run("invalid host volume mount r/w option value", func(t *testing.T) {
   667  		opts := &BundleExecutionOptions{
   668  			HostVolumeMounts: []string{
   669  				"/host/path:/target/path:invalid-option",
   670  			},
   671  		}
   672  
   673  		actual := opts.GetHostVolumeMounts()
   674  
   675  		if !actual[0].ReadOnly {
   676  			t.Errorf("expected ReadOnly to be true but got %v", actual[0].ReadOnly)
   677  		}
   678  
   679  	})
   680  }
   681  
   682  func TestBundleExecutionOptions_GetHostVolumeMountsWindows(t *testing.T) {
   683  
   684  	if runtime.GOOS != "windows" {
   685  		t.Skip("Skipping test on non-windows platform")
   686  	}
   687  
   688  	t.Run("valid host volume mounts", func(t *testing.T) {
   689  		opts := &BundleExecutionOptions{
   690  			HostVolumeMounts: []string{
   691  				"C:\\Users\\testuser\\folderpath:/target/path:rw",
   692  				"C:\\Users\\testuser\\folderpath:/target/path",
   693  				"C:\\Users\\Test User\\test path:/target/path",
   694  			},
   695  		}
   696  
   697  		expected := []cnabprovider.HostVolumeMountSpec{
   698  			{
   699  				Source:   "C:\\Users\\testuser\\folderpath",
   700  				Target:   "/target/path",
   701  				ReadOnly: false,
   702  			},
   703  			{
   704  				Source:   "C:\\Users\\testuser\\folderpath",
   705  				Target:   "/target/path",
   706  				ReadOnly: true,
   707  			},
   708  			{
   709  				Source:   "C:\\Users\\Test User\\test path",
   710  				Target:   "/target/path",
   711  				ReadOnly: true,
   712  			},
   713  		}
   714  
   715  		actual := opts.GetHostVolumeMounts()
   716  
   717  		if len(expected) != len(actual) {
   718  			t.Errorf("expected %v but got %v", expected, actual)
   719  		}
   720  		for i := range expected {
   721  			if expected[i].Source != actual[i].Source {
   722  				t.Errorf("expected %v but got %v", expected[i].Source, actual[i].Source)
   723  			}
   724  			if expected[i].Target != actual[i].Target {
   725  				t.Errorf("expected %v but got %v", expected[i].Target, actual[i].Target)
   726  			}
   727  			if expected[i].ReadOnly != actual[i].ReadOnly {
   728  				t.Errorf("expected %v but got %v", expected[i].ReadOnly, actual[i].ReadOnly)
   729  			}
   730  		}
   731  	})
   732  
   733  }