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

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