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

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"testing"
     9  
    10  	"get.porter.sh/porter/pkg"
    11  	"get.porter.sh/porter/pkg/cnab"
    12  	"get.porter.sh/porter/pkg/config"
    13  	"get.porter.sh/porter/pkg/experimental"
    14  	"get.porter.sh/porter/pkg/manifest"
    15  	"get.porter.sh/porter/pkg/portercontext"
    16  	"get.porter.sh/porter/tests"
    17  	"github.com/cnabio/cnab-go/bundle"
    18  	"github.com/cnabio/cnab-go/bundle/definition"
    19  	"github.com/cnabio/cnab-to-oci/relocation"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func runtimeManifestFromStepYaml(t *testing.T, testConfig *config.TestConfig, stepYaml string) *RuntimeManifest {
    25  	mContent := []byte(stepYaml)
    26  	require.NoError(t, testConfig.FileSystem.WriteFile("/cnab/app/porter.yaml", mContent, pkg.FileModeWritable))
    27  	m, err := manifest.ReadManifest(testConfig.Context, "/cnab/app/porter.yaml", testConfig.Config)
    28  	require.NoError(t, err, "ReadManifest failed")
    29  	cfg := NewConfigFor(testConfig.Config)
    30  	return NewRuntimeManifest(cfg, cnab.ActionInstall, m)
    31  }
    32  
    33  func TestResolveMapParam(t *testing.T) {
    34  	ctx := context.Background()
    35  	testConfig := config.NewTestConfig(t)
    36  	testConfig.Setenv("PERSON", "Ralpha")
    37  	testConfig.Setenv("CONTACT", "{ \"name\": \"Breta\" }")
    38  
    39  	mContent := `schemaVersion: 1.0.0-alpha.2
    40  parameters:
    41  - name: person
    42  - name: place
    43    applyTo: [install]
    44  - name: contact
    45    type: object
    46  
    47  install:
    48  - mymixin:
    49      Parameters:
    50        Thing: ${ bundle.parameters.person }
    51        ObjectName: ${ bundle.parameters.contact.name }
    52        Object: '${ bundle.parameters.contact }'
    53  `
    54  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
    55  	s := rm.Install[0]
    56  
    57  	err := rm.ResolveStep(ctx, 0, s)
    58  	require.NoError(t, err)
    59  
    60  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
    61  	mixin := s.Data["mymixin"].(map[string]interface{})
    62  	require.IsType(t, mixin["Parameters"], map[string]interface{}{}, "Data.mymixin.Parameters has incorrect type")
    63  	pms := mixin["Parameters"].(map[string]interface{})
    64  	require.IsType(t, "string", pms["Thing"], "Data.mymixin.Parameters.Thing has incorrect type")
    65  	val := pms["Thing"].(string)
    66  
    67  	assert.Equal(t, "Ralpha", val)
    68  	assert.NotContains(t, "place", pms, "parameters that don't apply to the current action should not be resolved")
    69  
    70  	// Asserting `bundle.parameters.contact.name` works.
    71  	require.IsType(t, "string", pms["ObjectName"], "Data.mymixin.Parameters.ObjectName has incorrect type")
    72  	contactName := pms["ObjectName"].(string)
    73  	require.IsType(t, "string", contactName, "Data.mymixin.Parameters.ObjectName.name has incorrect type")
    74  	assert.Equal(t, "Breta", contactName)
    75  
    76  	// Asserting `bundle.parameters.contact` evaluates to the JSON string
    77  	// representation of the object.
    78  	require.IsType(t, "string", pms["Object"], "Data.mymixin.Parameters.Object has incorrect type")
    79  	contact := pms["Object"].(string)
    80  	require.IsType(t, "string", contact, "Data.mymixin.Parameters.Object has incorrect type")
    81  	assert.Equal(t, "{\"name\":\"Breta\"}", contact)
    82  
    83  	err = rm.Initialize(ctx)
    84  	require.NoError(t, err)
    85  }
    86  func TestStateBagUnpack(t *testing.T) {
    87  	ctx := context.Background()
    88  	testConfig := config.NewTestConfig(t)
    89  	testConfig.Setenv("PERSON", "Ralpha")
    90  
    91  	mContent := `schemaVersion: 1.0.0-alpha.2
    92  parameters:
    93  - name: person
    94  - name: place
    95    applyTo: [install]
    96  
    97  install:
    98  - mymixin:
    99      Parameters:
   100        Thing: ${ bundle.parameters.person }
   101  state:
   102  - name: foo
   103    path: foo/state.json
   104  `
   105  	tests := []struct {
   106  		name         string
   107  		stateContent string
   108  		expErr       error
   109  	}{
   110  		{
   111  			name:         "/porter/state.tgz is empty file",
   112  			stateContent: "",
   113  			expErr:       nil,
   114  		},
   115  		{
   116  			name:         "/porter/state.tgz has null string",
   117  			stateContent: "null",
   118  			expErr:       nil,
   119  		},
   120  		{
   121  			name:         "/porter/state.tgz has newline",
   122  			stateContent: "\n",
   123  			expErr:       io.ErrUnexpectedEOF,
   124  		},
   125  	}
   126  	for _, test := range tests {
   127  		t.Run(test.name, func(t *testing.T) {
   128  			rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   129  			require.NoError(t, testConfig.FileSystem.WriteFile("/porter/state.tgz", []byte(test.stateContent), pkg.FileModeWritable))
   130  			s := rm.Install[0]
   131  
   132  			err := rm.ResolveStep(ctx, 0, s)
   133  			require.NoError(t, err)
   134  
   135  			err = rm.Initialize(ctx)
   136  			if test.expErr == nil {
   137  				require.NoError(t, err)
   138  			} else {
   139  				require.Contains(t, err.Error(), test.expErr.Error())
   140  			}
   141  			if test.stateContent != "null" {
   142  				err = testConfig.FileSystem.Remove("/porter/state.tgz")
   143  				require.NoError(t, err)
   144  			}
   145  		})
   146  	}
   147  }
   148  
   149  func TestResolvePathParam(t *testing.T) {
   150  	ctx := context.Background()
   151  	testConfig := config.NewTestConfig(t)
   152  
   153  	mContent := `schemaVersion: 1.0.0-alpha.2
   154  parameters:
   155  - name: person
   156    path: person.txt
   157  
   158  install:
   159  - mymixin:
   160      Parameters:
   161        Thing: ${ bundle.parameters.person }
   162  `
   163  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   164  	s := rm.Install[0]
   165  
   166  	err := rm.ResolveStep(ctx, 0, s)
   167  	require.NoError(t, err)
   168  
   169  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   170  	mixin := s.Data["mymixin"].(map[string]interface{})
   171  	require.IsType(t, mixin["Parameters"], map[string]interface{}{}, "Data.mymixin.Parameters has incorrect type")
   172  	pms := mixin["Parameters"].(map[string]interface{})
   173  	require.IsType(t, "string", pms["Thing"], "Data.mymixin.Parameters.Thing has incorrect type")
   174  	val := pms["Thing"].(string)
   175  
   176  	assert.Equal(t, "person.txt", val)
   177  }
   178  
   179  func TestMetadataAvailableForTemplating(t *testing.T) {
   180  	ctx := context.Background()
   181  	c := config.NewTestConfig(t)
   182  
   183  	c.TestContext.AddTestFile("testdata/metadata-substitution.yaml", config.Name)
   184  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   185  	require.NoError(t, err, "LoadManifestFrom")
   186  	cfg := NewConfigFor(c.Config)
   187  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   188  
   189  	s := rm.Install[0]
   190  	err = rm.ResolveStep(ctx, 0, s)
   191  	require.NoError(t, err)
   192  
   193  	pms, ok := s.Data["exec"].(map[string]interface{})
   194  	require.True(t, ok)
   195  	cmd := pms["command"].(string)
   196  	assert.Equal(t, "echo \"name:porter-hello version:0.1.0 description:An example Porter configuration image:jeremyrickard/porter-hello:porter-39a022ca907e26c3d8fffabd4bb8dbbc\"", cmd)
   197  }
   198  
   199  func TestDependencyMetadataAvailableForTemplating(t *testing.T) {
   200  	ctx := context.Background()
   201  	c := config.NewTestConfig(t)
   202  	c.TestContext.AddTestFile("testdata/dep-metadata-substitution.yaml", config.Name)
   203  
   204  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   205  	require.NoError(t, err, "LoadManifestFrom")
   206  	cfg := NewConfigFor(c.Config)
   207  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   208  	rm.bundles = map[string]cnab.ExtendedBundle{
   209  		"mysql": cnab.NewBundle(bundle.Bundle{
   210  			Name:        "Azure MySQL",
   211  			Description: "Azure MySQL database as a service",
   212  			Version:     "v1.0.0",
   213  		}),
   214  	}
   215  
   216  	s := rm.Install[0]
   217  	err = rm.ResolveStep(ctx, 0, s)
   218  	require.NoError(t, err)
   219  
   220  	pms, ok := s.Data["exec"].(map[string]interface{})
   221  	require.True(t, ok)
   222  	cmd := pms["command"].(string)
   223  	assert.Equal(t, "echo \"dep name: Azure MySQL dep version: v1.0.0 dep description: Azure MySQL database as a service\"", cmd)
   224  }
   225  
   226  func TestResolveMapParamUnknown(t *testing.T) {
   227  	ctx := context.Background()
   228  	testConfig := config.NewTestConfig(t)
   229  
   230  	mContent := `schemaVersion: 1.0.0
   231  install:
   232  - mymixin:
   233      Parameters:
   234        Thing: ${bundle.parameters.person}
   235  `
   236  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   237  	s := rm.Install[0]
   238  
   239  	err := rm.ResolveStep(ctx, 0, s)
   240  	require.Error(t, err)
   241  	tests.RequireErrorContains(t, err, "missing variable \"person\"")
   242  }
   243  
   244  func TestResolveArrayUnknown(t *testing.T) {
   245  	ctx := context.Background()
   246  	testConfig := config.NewTestConfig(t)
   247  
   248  	mContent := `schemaVersion: 1.0.0
   249  parameters:
   250  - name: name
   251  
   252  install:
   253  - exec:
   254      Arguments:
   255        - ${bundle.parameters.person}
   256  `
   257  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   258  	s := rm.Install[0]
   259  
   260  	err := rm.ResolveStep(ctx, 0, s)
   261  	require.Error(t, err)
   262  	assert.Contains(t, err.Error(), `missing variable "person"`)
   263  }
   264  
   265  func TestResolveArray(t *testing.T) {
   266  	ctx := context.Background()
   267  	testConfig := config.NewTestConfig(t)
   268  	testConfig.Setenv("PERSON", "Ralpha")
   269  
   270  	mContent := `schemaVersion: 1.0.0
   271  parameters:
   272  - name: person
   273  
   274  install:
   275  - mymixin:
   276      Arguments:
   277      - ${ bundle.parameters.person }
   278  `
   279  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   280  	s := rm.Install[0]
   281  
   282  	err := rm.ResolveStep(ctx, 0, s)
   283  	require.NoError(t, err)
   284  
   285  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   286  	mixin := s.Data["mymixin"].(map[string]interface{})
   287  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   288  	args := mixin["Arguments"].([]interface{})
   289  
   290  	assert.Equal(t, "Ralpha", args[0].(string))
   291  }
   292  
   293  func TestResolveSensitiveParameter(t *testing.T) {
   294  	ctx := context.Background()
   295  	testConfig := config.NewTestConfig(t)
   296  	testConfig.Setenv("SENSITIVE_PARAM", "deliciou$dubonnet")
   297  	testConfig.Setenv("SENSITIVE_OBJECT", "{ \"secret\": \"this_is_secret\" }")
   298  	testConfig.Setenv("REGULAR_PARAM", "regular param value")
   299  
   300  	mContent := `schemaVersion: 1.0.0
   301  parameters:
   302  - name: sensitive_param
   303    sensitive: true
   304  - name: sensitive_object
   305    sensitive: true
   306    type: object
   307  - name: regular_param
   308  
   309  install:
   310  - mymixin:
   311      Arguments:
   312      - ${ bundle.parameters.sensitive_param }
   313      - '${ bundle.parameters.sensitive_object }'
   314      - ${ bundle.parameters.regular_param }
   315  `
   316  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   317  	s := rm.Install[0]
   318  
   319  	// Prior to resolving step values, this method should return an empty string array
   320  	assert.Equal(t, rm.GetSensitiveValues(), []string{})
   321  
   322  	err := rm.ResolveStep(ctx, 0, s)
   323  	require.NoError(t, err)
   324  
   325  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   326  	mixin := s.Data["mymixin"].(map[string]interface{})
   327  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   328  	args := mixin["Arguments"].([]interface{})
   329  
   330  	require.Len(t, args, 3)
   331  	assert.Equal(t, "deliciou$dubonnet", args[0])
   332  	assert.Equal(t, "{\"secret\":\"this_is_secret\"}", args[1])
   333  	assert.Equal(t, "regular param value", args[2])
   334  
   335  	// There should now be one sensitive value tracked under the manifest
   336  	assert.ElementsMatch(t, []string{"deliciou$dubonnet", "{\"secret\":\"this_is_secret\"}"}, rm.GetSensitiveValues())
   337  }
   338  
   339  func TestResolveCredential(t *testing.T) {
   340  	ctx := context.Background()
   341  	testConfig := config.NewTestConfig(t)
   342  	testConfig.Setenv("PASSWORD", "deliciou$dubonnet")
   343  
   344  	mContent := `schemaVersion: 1.0.0
   345  credentials:
   346  - name: password
   347    env: PASSWORD
   348  
   349  install:
   350  - mymixin:
   351      Arguments:
   352      - ${ bundle.credentials.password }
   353  `
   354  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   355  	s := rm.Install[0]
   356  
   357  	// Prior to resolving step values, this method should return an empty string array
   358  	assert.Equal(t, rm.GetSensitiveValues(), []string{})
   359  
   360  	err := rm.ResolveStep(ctx, 0, s)
   361  	require.NoError(t, err)
   362  
   363  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   364  	mixin := s.Data["mymixin"].(map[string]interface{})
   365  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   366  	args := mixin["Arguments"].([]interface{})
   367  
   368  	assert.Equal(t, "deliciou$dubonnet", args[0])
   369  	// There should now be a sensitive value tracked under the manifest
   370  	assert.Equal(t, []string{"deliciou$dubonnet"}, rm.GetSensitiveValues())
   371  }
   372  
   373  func TestResolveStep_DependencyOutput(t *testing.T) {
   374  	ctx := context.Background()
   375  	testConfig := config.NewTestConfig(t)
   376  	testConfig.Setenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT", "password")
   377  	testConfig.Setenv("PORTER_MYSQL_ROOT_PASSWORD_DEP_OUTPUT", "mysql-password")
   378  
   379  	mContent := `schemaVersion: 1.0.0
   380  dependencies:
   381    requires: 
   382    - name: mysql
   383      bundle:
   384        reference: "getporter/porter-mysql"
   385  
   386  install:
   387  - mymixin:
   388      Arguments:
   389      - ${ bundle.dependencies.mysql.outputs.password }
   390      - ${ bundle.dependencies.mysql.outputs.root-password }
   391  `
   392  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   393  	ps := cnab.ParameterSources{}
   394  	ps.SetParameterFromDependencyOutput("porter-mysql-password", "mysql", "password")
   395  	ps.SetParameterFromDependencyOutput("porter-mysql-root-password", "mysql", "root-password")
   396  	rm.bundle = cnab.NewBundle(bundle.Bundle{
   397  		Custom: map[string]interface{}{
   398  			cnab.ParameterSourcesExtensionKey: ps,
   399  		},
   400  		RequiredExtensions: []string{cnab.ParameterSourcesExtensionKey},
   401  	})
   402  
   403  	rm.bundles = map[string]cnab.ExtendedBundle{
   404  		"mysql": cnab.NewBundle(bundle.Bundle{
   405  			Outputs: map[string]bundle.Output{
   406  				"password": {
   407  					Definition: "password",
   408  				},
   409  				"root-password": {
   410  					Definition: "root-password",
   411  				},
   412  			},
   413  			Definitions: map[string]*definition.Schema{
   414  				"password":      {WriteOnly: makeBoolPtr(true)},
   415  				"root-password": {WriteOnly: makeBoolPtr(true)},
   416  			},
   417  		}),
   418  	}
   419  
   420  	s := rm.Install[0]
   421  	err := rm.ResolveStep(ctx, 0, s)
   422  	require.NoError(t, err)
   423  
   424  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   425  	mixin := s.Data["mymixin"].(map[string]interface{})
   426  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   427  	args := mixin["Arguments"].([]interface{})
   428  
   429  	assert.Equal(t, []interface{}{"password", "mysql-password"}, args, "Incorrect template args passed to the mixin step")
   430  
   431  	// There should now be a sensitive value tracked under the manifest
   432  	gotSensitiveValues := rm.GetSensitiveValues()
   433  	sort.Strings(gotSensitiveValues)
   434  	assert.Equal(t, []string{"mysql-password", "password"}, gotSensitiveValues, "Incorrect values were marked as sensitive")
   435  }
   436  
   437  func TestResolveStep_DependencyMappedOutput(t *testing.T) {
   438  	ctx := context.Background()
   439  	testConfig := config.NewTestConfig(t)
   440  	testConfig.SetExperimentalFlags(experimental.FlagDependenciesV2)
   441  
   442  	mContent := `schemaVersion: 1.0.0
   443  dependencies:
   444    requires: 
   445    - name: mysql
   446      bundle:
   447        reference: "getporter/porter-mysql"
   448      outputs:
   449        mappedOutput: Mapped
   450  
   451  install:
   452  - mymixin:
   453      Arguments:
   454      - ${ bundle.dependencies.mysql.outputs.mappedOutput }
   455  `
   456  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   457  	rm.bundles = map[string]cnab.ExtendedBundle{
   458  		"mysql": cnab.NewBundle(bundle.Bundle{}),
   459  	}
   460  
   461  	s := rm.Install[0]
   462  	err := rm.ResolveStep(ctx, 0, s)
   463  	require.NoError(t, err)
   464  
   465  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   466  	mixin := s.Data["mymixin"].(map[string]interface{})
   467  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   468  	args := mixin["Arguments"].([]interface{})
   469  
   470  	assert.Equal(t, []interface{}{"Mapped"}, args, "Incorrect template args passed to the mixin step")
   471  }
   472  
   473  func TestResolveStep_DependencyTemplatedMappedOutput(t *testing.T) {
   474  	ctx := context.Background()
   475  	testConfig := config.NewTestConfig(t)
   476  	testConfig.SetExperimentalFlags(experimental.FlagDependenciesV2)
   477  	testConfig.Setenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT", "password")
   478  
   479  	mContent := `schemaVersion: 1.0.0
   480  dependencies:
   481    requires: 
   482    - name: mysql
   483      bundle:
   484        reference: "getporter/porter-mysql"
   485      outputs:
   486        mappedOutput: ${ bundle.dependencies.mysql.outputs.password }
   487  
   488  install:
   489  - mymixin:
   490      Arguments:
   491      - ${ bundle.dependencies.mysql.outputs.mappedOutput }
   492  `
   493  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   494  	ps := cnab.ParameterSources{}
   495  	ps.SetParameterFromDependencyOutput("porter-mysql-password", "mysql", "password")
   496  	rm.bundle = cnab.NewBundle(bundle.Bundle{
   497  		Custom: map[string]interface{}{
   498  			cnab.ParameterSourcesExtensionKey: ps,
   499  		},
   500  		RequiredExtensions: []string{cnab.ParameterSourcesExtensionKey},
   501  	})
   502  
   503  	rm.bundles = map[string]cnab.ExtendedBundle{
   504  		"mysql": cnab.NewBundle(bundle.Bundle{
   505  			Outputs: map[string]bundle.Output{
   506  				"password": {
   507  					Definition: "password",
   508  				},
   509  			},
   510  			Definitions: map[string]*definition.Schema{
   511  				"password": {WriteOnly: makeBoolPtr(true)},
   512  			},
   513  		}),
   514  	}
   515  
   516  	s := rm.Install[0]
   517  	err := rm.ResolveStep(ctx, 0, s)
   518  	require.NoError(t, err)
   519  
   520  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   521  	mixin := s.Data["mymixin"].(map[string]interface{})
   522  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   523  	args := mixin["Arguments"].([]interface{})
   524  
   525  	assert.Equal(t, []interface{}{"password"}, args, "Incorrect template args passed to the mixin step")
   526  }
   527  
   528  func TestResolveStep_DependencyTemplatedMappedOutput_OutputVariable(t *testing.T) {
   529  	ctx := context.Background()
   530  	testConfig := config.NewTestConfig(t)
   531  	testConfig.SetExperimentalFlags(experimental.FlagDependenciesV2)
   532  	testConfig.Setenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT", "password")
   533  
   534  	mContent := `schemaVersion: 1.0.0
   535  dependencies:
   536    requires: 
   537    - name: mysql
   538      bundle:
   539        reference: "getporter/porter-mysql"
   540      outputs:
   541        mappedOutput: combined-${ outputs.password }
   542  
   543  install:
   544  - mymixin:
   545      Arguments:
   546      - ${ bundle.dependencies.mysql.outputs.mappedOutput }
   547  `
   548  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   549  	ps := cnab.ParameterSources{}
   550  	ps.SetParameterFromDependencyOutput("porter-mysql-password", "mysql", "password")
   551  	rm.bundle = cnab.NewBundle(bundle.Bundle{
   552  		Custom: map[string]interface{}{
   553  			cnab.ParameterSourcesExtensionKey: ps,
   554  		},
   555  		RequiredExtensions: []string{cnab.ParameterSourcesExtensionKey},
   556  	})
   557  
   558  	rm.bundles = map[string]cnab.ExtendedBundle{
   559  		"mysql": cnab.NewBundle(bundle.Bundle{
   560  			Outputs: map[string]bundle.Output{
   561  				"password": {
   562  					Definition: "password",
   563  				},
   564  			},
   565  			Definitions: map[string]*definition.Schema{
   566  				"password": {WriteOnly: makeBoolPtr(true)},
   567  			},
   568  		}),
   569  	}
   570  
   571  	s := rm.Install[0]
   572  	err := rm.ResolveStep(ctx, 0, s)
   573  	require.NoError(t, err)
   574  
   575  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has incorrect type")
   576  	mixin := s.Data["mymixin"].(map[string]interface{})
   577  	require.IsType(t, mixin["Arguments"], []interface{}{}, "Data.mymixin.Arguments has incorrect type")
   578  	args := mixin["Arguments"].([]interface{})
   579  
   580  	assert.Equal(t, []interface{}{"combined-password"}, args, "Incorrect template args passed to the mixin step")
   581  }
   582  
   583  func TestResolveInMainDict(t *testing.T) {
   584  	ctx := context.Background()
   585  	c := config.NewTestConfig(t)
   586  
   587  	c.TestContext.AddTestFile("testdata/param-test-in-block.yaml", config.Name)
   588  
   589  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   590  	require.NoError(t, err, "could not load manifest")
   591  
   592  	cfg := NewConfigFor(c.Config)
   593  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   594  
   595  	installStep := rm.Install[0]
   596  
   597  	rm.config.Setenv("COMMAND", "echo hello world")
   598  	err = rm.ResolveStep(ctx, 0, installStep)
   599  	require.NoError(t, err)
   600  
   601  	require.IsType(t, map[string]interface{}{}, installStep.Data["exec"], "Data.exec has the wrong type")
   602  	exec := installStep.Data["exec"].(map[string]interface{})
   603  	command := exec["command"]
   604  	require.IsType(t, "string", command, "Data.exec.command has the wrong type")
   605  	cmdVal := command.(string)
   606  
   607  	assert.Equal(t, "echo hello world", cmdVal)
   608  }
   609  
   610  func TestResolveSliceWithAMap(t *testing.T) {
   611  	ctx := context.Background()
   612  	c := config.NewTestConfig(t)
   613  
   614  	c.TestContext.AddTestFile("testdata/slice-test.yaml", config.Name)
   615  
   616  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   617  	require.NoError(t, err, "could not load manifest")
   618  
   619  	cfg := NewConfigFor(c.Config)
   620  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   621  
   622  	installStep := rm.Install[0]
   623  
   624  	rm.config.Setenv("COMMAND", "echo hello world")
   625  	err = rm.ResolveStep(ctx, 0, installStep)
   626  	require.NoError(t, err)
   627  
   628  	require.NotNil(t, installStep.Data)
   629  	exec := installStep.Data["exec"].(map[string]interface{})
   630  	require.NotNil(t, exec)
   631  	flags := exec["flags"].(map[string]interface{})
   632  	require.Len(t, flags, 1)
   633  	assert.Equal(t, "echo hello world", flags["c"].(string))
   634  }
   635  
   636  func TestResolveMissingStepOutputs(t *testing.T) {
   637  	ctx := context.Background()
   638  	testConfig := config.NewTestConfig(t)
   639  
   640  	mContent := `schemaVersion: 1.0.0
   641  install:
   642  - mymixin:
   643      Arguments:
   644      - jdbc://${bundle.outputs.database_url}:${bundle.outputs.database_port}
   645  `
   646  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   647  	s := rm.Install[0]
   648  
   649  	err := rm.ResolveStep(ctx, 0, s)
   650  	tests.RequireErrorContains(t, err, `missing variable "database_url"`)
   651  }
   652  
   653  func TestResolveSensitiveOutputs(t *testing.T) {
   654  	ctx := context.Background()
   655  	testConfig := config.NewTestConfig(t)
   656  	mContent := `schemaVersion: 1.0.0
   657  outputs:
   658  - name: username
   659  - name: password
   660    sensitive: true
   661  
   662  install:
   663  - mymixin:
   664      Arguments:
   665      - ${ bundle.outputs.username }
   666      - ${ bundle.outputs.password }
   667  `
   668  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   669  	rm.outputs = map[string]string{
   670  		"username": "sally",
   671  		"password": "top$ecret!",
   672  	}
   673  	s := rm.Install[0]
   674  
   675  	err := rm.ResolveStep(ctx, 0, s)
   676  	require.NoError(t, err)
   677  
   678  	require.IsType(t, s.Data["mymixin"], map[string]interface{}{}, "Data.mymixin has the wrong type")
   679  	mixin := s.Data["mymixin"].(map[string]interface{})
   680  	require.IsType(t, []interface{}{}, mixin["Arguments"], "Data.mymixin.Arguments has the wrong type")
   681  	args := mixin["Arguments"].([]interface{})
   682  
   683  	require.Len(t, args, 2)
   684  	require.Equal(t, "sally", args[0])
   685  	require.Equal(t, "top$ecret!", args[1])
   686  
   687  	// There should be only one sensitive value being tracked
   688  	require.Equal(t, []string{"top$ecret!"}, rm.GetSensitiveValues())
   689  }
   690  
   691  func TestManifest_ResolveBundleName(t *testing.T) {
   692  	ctx := context.Background()
   693  	testConfig := config.NewTestConfig(t)
   694  	mContent := `schemaVersion: 1.0.0
   695  name: mybuns
   696  
   697  install:
   698  - mymixin:
   699      Arguments:
   700      - ${ bundle.name }
   701  `
   702  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   703  	s := rm.Install[0]
   704  
   705  	err := rm.ResolveStep(ctx, 0, s)
   706  	require.NoError(t, err)
   707  
   708  	require.IsType(t, s.Data["mymixin"], map[string]interface{}{}, "Data.mymixin has the wrong type")
   709  	mixin := s.Data["mymixin"].(map[string]interface{})
   710  	require.IsType(t, []interface{}{}, mixin["Arguments"], "Data.mymixin.Arguments has the wrong type")
   711  	args := mixin["Arguments"].([]interface{})
   712  
   713  	assert.Equal(t, "mybuns", args[0].(string))
   714  }
   715  
   716  func TestReadManifest_Validate_BundleOutput(t *testing.T) {
   717  	c := config.NewTestConfig(t)
   718  
   719  	c.TestContext.AddTestFile("testdata/outputs/bundle-outputs.yaml", config.Name)
   720  
   721  	wantOutputs := manifest.OutputDefinitions{
   722  		"mysql-root-password": {
   723  			Name: "mysql-root-password",
   724  			Schema: definition.Schema{
   725  				Description: "The root MySQL password",
   726  				Type:        "string",
   727  			},
   728  		},
   729  		"mysql-password": {
   730  			Name: "mysql-password",
   731  			Schema: definition.Schema{
   732  				Type: "string",
   733  			},
   734  			ApplyTo: []string{
   735  				"install",
   736  				"upgrade",
   737  			},
   738  		},
   739  	}
   740  
   741  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   742  	require.NoError(t, err, "could not load manifest")
   743  
   744  	require.Equal(t, wantOutputs, m.Outputs)
   745  }
   746  
   747  func TestReadManifest_Validate_BundleOutput_Error(t *testing.T) {
   748  	c := config.NewTestConfig(t)
   749  
   750  	c.TestContext.AddTestFile("testdata/outputs/bundle-outputs-error.yaml", config.Name)
   751  
   752  	_, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   753  	require.Error(t, err)
   754  }
   755  
   756  func TestDependencyV1_Validate(t *testing.T) {
   757  	testcases := []struct {
   758  		name       string
   759  		dep        manifest.Dependency
   760  		wantOutput string
   761  		wantError  string
   762  	}{
   763  		{
   764  			name:       "version in reference",
   765  			dep:        manifest.Dependency{Name: "mysql", Bundle: manifest.BundleCriteria{Reference: "deislabs/azure-mysql:5.7"}},
   766  			wantOutput: "",
   767  			wantError:  "",
   768  		}, {
   769  			name:       "version ranges",
   770  			dep:        manifest.Dependency{Name: "mysql", Bundle: manifest.BundleCriteria{Reference: "deislabs/azure-mysql", Version: "5.7.x-6"}},
   771  			wantOutput: "",
   772  			wantError:  "",
   773  		}, {
   774  			name:       "missing reference",
   775  			dep:        manifest.Dependency{Name: "mysql", Bundle: manifest.BundleCriteria{Reference: ""}},
   776  			wantOutput: "",
   777  			wantError:  `reference is required for dependency "mysql"`,
   778  		}, {
   779  			name:       "version not specified",
   780  			dep:        manifest.Dependency{Name: "mysql", Bundle: manifest.BundleCriteria{Reference: "deislabs/azure-mysql", Version: ""}},
   781  			wantOutput: "",
   782  			wantError:  `reference for dependency "mysql" can specify only a repository, without a digest or tag, when a version constraint is specified`,
   783  		}, { // When a range is specified, but also a default version, we use the default version when we can't find a matching version from the range
   784  			name:       "default version and range specified",
   785  			dep:        manifest.Dependency{Name: "mysql", Bundle: manifest.BundleCriteria{Reference: "deislabs/azure-mysql:5.7", Version: "5.7.x-6"}},
   786  			wantOutput: "",
   787  			wantError:  "",
   788  		},
   789  	}
   790  
   791  	for _, tc := range testcases {
   792  		t.Run(tc.name, func(t *testing.T) {
   793  			pCtx := portercontext.NewTestContext(t)
   794  
   795  			err := tc.dep.Validate(pCtx.Context)
   796  
   797  			if tc.wantError == "" {
   798  				require.NoError(t, err)
   799  			} else {
   800  				tests.RequireErrorContains(t, err, tc.wantError)
   801  			}
   802  
   803  			gotOutput := pCtx.GetOutput()
   804  			if gotOutput != "" {
   805  				require.Equal(t, tc.wantOutput, gotOutput)
   806  			}
   807  		})
   808  	}
   809  }
   810  
   811  func TestManifest_ApplyStepOutputs(t *testing.T) {
   812  	c := config.NewTestConfig(t)
   813  
   814  	c.TestContext.AddTestFileFromRoot("pkg/manifest/testdata/porter-with-templating.yaml", config.Name)
   815  
   816  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   817  	require.NoError(t, err, "could not load manifest")
   818  
   819  	cfg := NewConfigFor(c.Config)
   820  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   821  
   822  	err = rm.ApplyStepOutputs(map[string]string{"name": "world"})
   823  	require.NoError(t, err)
   824  
   825  	assert.Contains(t, rm.outputs, "name")
   826  	assert.Equal(t, "world", rm.outputs["name"])
   827  }
   828  
   829  func makeBoolPtr(value bool) *bool {
   830  	return &value
   831  }
   832  
   833  func TestManifest_ResolveImageMap(t *testing.T) {
   834  	ctx := context.Background()
   835  	c := config.NewTestConfig(t)
   836  	c.TestContext.AddTestFile("testdata/porter-images.yaml", config.Name)
   837  
   838  	m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name)
   839  	require.NoError(t, err, "could not load manifest")
   840  
   841  	cfg := NewConfigFor(c.Config)
   842  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
   843  	expectedImage, ok := m.ImageMap["something"]
   844  	require.True(t, ok, "couldn't get expected image")
   845  	expectedRef := fmt.Sprintf("%s@%s", expectedImage.Repository, expectedImage.Digest)
   846  	step := rm.Install[0]
   847  	err = rm.ResolveStep(ctx, 0, step)
   848  	assert.NoError(t, err, "Should have successfully resolved step")
   849  	s := step.Data["searcher"].(map[string]interface{})
   850  	assert.NotNil(t, s)
   851  	img, ok := s["image"]
   852  	assert.True(t, ok, "should have found image")
   853  	val := fmt.Sprintf("%v", img)
   854  	assert.Equal(t, expectedRef, val)
   855  
   856  	repo, ok := s["repo"]
   857  	assert.True(t, ok, "should have found repo")
   858  	val = fmt.Sprintf("%v", repo)
   859  	assert.Equal(t, expectedImage.Repository, val)
   860  
   861  	digest, ok := s["digest"]
   862  	assert.True(t, ok, "should have found content digest")
   863  	val = fmt.Sprintf("%v", digest)
   864  	assert.Equal(t, expectedImage.Digest, val)
   865  
   866  	tag, ok := s["tag"]
   867  	assert.True(t, ok, "should have found tag")
   868  	val = fmt.Sprintf("%v", tag)
   869  	assert.Equal(t, expectedImage.Tag, val)
   870  }
   871  
   872  func TestManifest_ResolveImageMapMissingKey(t *testing.T) {
   873  	// Try to access an images entry that doesn't exist
   874  	ctx := context.Background()
   875  	testConfig := config.NewTestConfig(t)
   876  	mContent := `schemaVersion: 1.0.0-alpha.2
   877  images:
   878    something:
   879      repository: "blah/blah"
   880      digest: "sha1234:cafebab"
   881  
   882  install:
   883  - mymixin:
   884      Arguments:
   885        - ${ bundle.images.notsomething.digest }
   886  `
   887  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
   888  	s := rm.Install[0]
   889  
   890  	err := rm.ResolveStep(ctx, 0, s)
   891  	tests.RequireErrorContains(t, err, `missing variable "notsomething"`)
   892  }
   893  
   894  func TestResolveImage(t *testing.T) {
   895  	tests := []struct {
   896  		name      string
   897  		reference string
   898  		want      manifest.MappedImage
   899  	}{
   900  		{
   901  			name:      "canonical reference",
   902  			reference: "getporter/porter-hello@sha256:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   903  			want: manifest.MappedImage{
   904  				Repository: "getporter/porter-hello",
   905  				Digest:     "sha256:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   906  			},
   907  		},
   908  		{
   909  			name:      "tagged reference",
   910  			reference: "ghcr.io/getporter/examples/porter-hello:v0.2.0",
   911  			want: manifest.MappedImage{
   912  				Repository: "ghcr.io/getporter/examples/porter-hello",
   913  				Tag:        "v0.2.0",
   914  			},
   915  		},
   916  		{
   917  			name:      "named reference",
   918  			reference: "getporter/porter-hello",
   919  			want: manifest.MappedImage{
   920  				Repository: "getporter/porter-hello",
   921  				Tag:        "latest",
   922  			},
   923  		},
   924  		{
   925  			name:      "the one with a hostname",
   926  			reference: "deislabs.io/getporter/porter-hello",
   927  			want: manifest.MappedImage{
   928  				Repository: "deislabs.io/getporter/porter-hello",
   929  				Tag:        "latest",
   930  			},
   931  		},
   932  		{
   933  			name:      "the one with a hostname and port",
   934  			reference: "deislabs.io:9090/getporter/porter-hello:foo",
   935  			want: manifest.MappedImage{
   936  				Repository: "deislabs.io:9090/getporter/porter-hello",
   937  				Tag:        "foo",
   938  			},
   939  		},
   940  		{
   941  
   942  			name:      "tagged and digested",
   943  			reference: "ghcr.io/getporter/examples/porter-hello:v0.2.0@sha256:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   944  			want: manifest.MappedImage{
   945  				Repository: "ghcr.io/getporter/examples/porter-hello",
   946  				Tag:        "v0.2.0",
   947  				Digest:     "sha256:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   948  			},
   949  		},
   950  	}
   951  	for _, test := range tests {
   952  		t.Run(test.name, func(t *testing.T) {
   953  			got := &manifest.MappedImage{}
   954  			err := resolveImage(got, test.reference)
   955  			require.NoError(t, err)
   956  			assert.Equal(t, test.want.Repository, got.Repository)
   957  			assert.Equal(t, test.want.Tag, got.Tag)
   958  			assert.Equal(t, test.want.Digest, got.Digest)
   959  		})
   960  	}
   961  }
   962  
   963  func TestResolveImageErrors(t *testing.T) {
   964  	tests := []struct {
   965  		name      string
   966  		reference string
   967  		want      string
   968  	}{
   969  		{
   970  			name:      "no algo digest",
   971  			reference: "getporter/porter-hello@8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   972  			want:      "invalid reference format",
   973  		},
   974  		{
   975  			name:      "bad digest",
   976  			reference: "getporter/porter-hello@sha256:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f",
   977  			want:      "invalid checksum digest length",
   978  		},
   979  		{
   980  			name:      "bad digest algo",
   981  			reference: "getporter/porter-hello@sha356:8b06c3da72dc9fa7002b9bc1f73a7421b4287c9cf0d3b08633287473707f9a63",
   982  			want:      "unsupported digest algorithm",
   983  		},
   984  		{
   985  			name:      "malformed tagged ref",
   986  			reference: "getporter/porter-hello@latest",
   987  			want:      "invalid reference format",
   988  		},
   989  		{
   990  			name:      "too many ports tagged ref",
   991  			reference: "deislabs:8080:8080/porter-hello:latest",
   992  			want:      "invalid reference format",
   993  		},
   994  	}
   995  	for _, test := range tests {
   996  		t.Run(test.name, func(t *testing.T) {
   997  			got := &manifest.MappedImage{}
   998  			err := resolveImage(got, test.reference)
   999  			require.Error(t, err)
  1000  			assert.Contains(t, err.Error(), test.want)
  1001  		})
  1002  	}
  1003  }
  1004  
  1005  func TestResolveImageWithUpdatedBundle(t *testing.T) {
  1006  	m := &manifest.Manifest{
  1007  		ImageMap: map[string]manifest.MappedImage{
  1008  			"machine": manifest.MappedImage{
  1009  				Repository: "deislabs/ghost",
  1010  				Tag:        "latest",
  1011  				Digest:     "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041",
  1012  			},
  1013  		},
  1014  	}
  1015  
  1016  	img := bundle.Image{}
  1017  	img.Image = "blah/ghost:latest"
  1018  	img.Digest = "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"
  1019  	bun := cnab.NewBundle(bundle.Bundle{
  1020  		Images: map[string]bundle.Image{
  1021  			"machine": img,
  1022  		},
  1023  	})
  1024  
  1025  	reloMap := relocation.ImageRelocationMap{}
  1026  
  1027  	cfg := NewConfigFor(config.NewTestConfig(t).Config)
  1028  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
  1029  	err := rm.ResolveImages(bun, reloMap)
  1030  	require.NoError(t, err)
  1031  	mi := rm.ImageMap["machine"]
  1032  	assert.Equal(t, "blah/ghost", mi.Repository)
  1033  }
  1034  
  1035  func TestResolveImageWithUpdatedMismatchedBundle(t *testing.T) {
  1036  	m := &manifest.Manifest{
  1037  		ImageMap: map[string]manifest.MappedImage{
  1038  			"machine": manifest.MappedImage{
  1039  				Repository: "deislabs/ghost",
  1040  				Tag:        "latest",
  1041  				Digest:     "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041",
  1042  			},
  1043  		},
  1044  	}
  1045  
  1046  	img := bundle.Image{}
  1047  	img.Image = "blah/ghost:latest"
  1048  	img.Digest = "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"
  1049  	bun := cnab.NewBundle(bundle.Bundle{
  1050  		Images: map[string]bundle.Image{
  1051  			"ghost": img,
  1052  		},
  1053  	})
  1054  
  1055  	reloMap := relocation.ImageRelocationMap{}
  1056  
  1057  	cfg := NewConfigFor(config.NewTestConfig(t).Config)
  1058  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
  1059  	err := rm.ResolveImages(bun, reloMap)
  1060  	assert.Error(t, err)
  1061  	assert.EqualError(t, err, fmt.Sprintf("unable to find image in porter manifest: %s", "ghost"))
  1062  
  1063  }
  1064  
  1065  func TestResolveImageWithRelo(t *testing.T) {
  1066  	m := &manifest.Manifest{
  1067  		ImageMap: map[string]manifest.MappedImage{
  1068  			"machine": manifest.MappedImage{
  1069  				Repository: "gabrtv/microservice",
  1070  				Tag:        "latest",
  1071  				Digest:     "sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687",
  1072  			},
  1073  		},
  1074  	}
  1075  
  1076  	img := bundle.Image{}
  1077  	img.Image = "gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"
  1078  	img.Digest = "sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"
  1079  	bun := cnab.NewBundle(bundle.Bundle{
  1080  		Images: map[string]bundle.Image{
  1081  			"machine": img,
  1082  		},
  1083  	})
  1084  
  1085  	reloMap := relocation.ImageRelocationMap{
  1086  		"gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687": "my.registry/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687",
  1087  	}
  1088  
  1089  	cfg := NewConfigFor(config.NewTestConfig(t).Config)
  1090  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
  1091  	err := rm.ResolveImages(bun, reloMap)
  1092  	require.NoError(t, err)
  1093  	mi := rm.ImageMap["machine"]
  1094  	assert.Equal(t, "my.registry/microservice", mi.Repository)
  1095  }
  1096  
  1097  func TestResolveImageRelocationNoMatch(t *testing.T) {
  1098  	m := &manifest.Manifest{
  1099  		ImageMap: map[string]manifest.MappedImage{
  1100  			"machine": manifest.MappedImage{
  1101  				Repository: "deislabs/ghost",
  1102  				Tag:        "latest",
  1103  				Digest:     "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041",
  1104  			},
  1105  		},
  1106  	}
  1107  
  1108  	img := bundle.Image{}
  1109  	img.Image = "deislabs/ghost:latest"
  1110  	img.Digest = "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"
  1111  	bun := cnab.NewBundle(bundle.Bundle{
  1112  		Images: map[string]bundle.Image{
  1113  			"machine": img,
  1114  		},
  1115  	})
  1116  
  1117  	reloMap := relocation.ImageRelocationMap{
  1118  		"deislabs/nogood:latest": "cnabio/ghost:latest",
  1119  	}
  1120  
  1121  	cfg := NewConfigFor(config.NewTestConfig(t).Config)
  1122  	rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
  1123  	err := rm.ResolveImages(bun, reloMap)
  1124  	require.NoError(t, err)
  1125  	assert.Equal(t, "deislabs/ghost", rm.ImageMap["machine"].Repository)
  1126  }
  1127  
  1128  func TestResolveStepEncoding(t *testing.T) {
  1129  	ctx := context.Background()
  1130  	testConfig := config.NewTestConfig(t)
  1131  
  1132  	wantValue := `{"test":"value"}`
  1133  	testConfig.Setenv("TEST", wantValue)
  1134  
  1135  	mContent := `schemaVersion: 1.0.0
  1136  parameters:
  1137  - name: test
  1138    env: TEST
  1139  
  1140  install:
  1141  - mymixin:
  1142      Flags:
  1143        c: '${bundle.parameters.test}'
  1144  `
  1145  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
  1146  	s := rm.Install[0]
  1147  
  1148  	err := rm.ResolveStep(ctx, 0, s)
  1149  	require.NoError(t, err)
  1150  
  1151  	require.IsType(t, s.Data["mymixin"], map[string]interface{}{}, "Data.mymixin has the wrong type")
  1152  	mixin := s.Data["mymixin"].(map[string]interface{})
  1153  	require.IsType(t, map[string]interface{}{}, mixin["Flags"], "Data.mymixin.Flags has the wrong type")
  1154  	flags := mixin["Flags"].(map[string]interface{})
  1155  
  1156  	assert.Equal(t, flags["c"], wantValue)
  1157  }
  1158  
  1159  func TestResolveInstallation(t *testing.T) {
  1160  	ctx := context.Background()
  1161  	testConfig := config.NewTestConfig(t)
  1162  	testConfig.Setenv(config.EnvPorterInstallationNamespace, "mynamespace")
  1163  	testConfig.Setenv(config.EnvPorterInstallationName, "mybun")
  1164  
  1165  	mContent := `schemaVersion: 1.0.0
  1166  install:
  1167  - mymixin:
  1168      ns: ${ installation.namespace }
  1169      release: ${ installation.name }
  1170  `
  1171  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
  1172  	s := rm.Install[0]
  1173  
  1174  	err := rm.ResolveStep(ctx, 0, s)
  1175  	require.NoError(t, err)
  1176  
  1177  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has the wrong type")
  1178  	mixin := s.Data["mymixin"].(map[string]interface{})
  1179  
  1180  	assert.Equal(t, "mynamespace", mixin["ns"], "installation.namespace was not rendered")
  1181  	assert.Equal(t, "mybun", mixin["release"], "installation.name was not rendered")
  1182  }
  1183  
  1184  func TestResolveCustomMetadata(t *testing.T) {
  1185  	ctx := context.Background()
  1186  	testConfig := config.NewTestConfig(t)
  1187  
  1188  	mContent := `schemaVersion: 1.0.0
  1189  custom:
  1190    foo: foobar
  1191    myApp:
  1192      featureFlags:
  1193        featureA: true
  1194  
  1195  install:
  1196  - mymixin:
  1197      release: ${ bundle.custom.foo }
  1198      featureA: ${ bundle.custom.myApp.featureFlags.featureA }
  1199      notabool: "${ bundle.custom.myApp.featureFlags.featureA }"
  1200  `
  1201  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
  1202  	s := rm.Install[0]
  1203  
  1204  	err := rm.ResolveStep(ctx, 0, s)
  1205  	require.NoError(t, err)
  1206  
  1207  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has the wrong type")
  1208  	mixin := s.Data["mymixin"].(map[string]interface{})
  1209  
  1210  	err = rm.ResolveStep(ctx, 0, s)
  1211  	require.NoError(t, err, "ResolveStep failed")
  1212  
  1213  	assert.Equal(t, "foobar", mixin["release"], "custom metadata was not rendered")
  1214  	assert.Equal(t, true, mixin["featureA"], "nested custom metadata was not rendered, an unquoted boolean should render as a bool")
  1215  	assert.Equal(t, "true", mixin["notabool"], "a quoted boolean should render as a string")
  1216  }
  1217  
  1218  func TestResolveEnvironmentVariable(t *testing.T) {
  1219  	ctx := context.Background()
  1220  	testConfig := config.NewTestConfig(t)
  1221  	testConfig.Setenv("foo", "foo-value")
  1222  	testConfig.Setenv("BAR", "bar-value")
  1223  
  1224  	mContent := `schemaVersion: 1.0.0
  1225  install:
  1226  - mymixin:
  1227      someInput: ${ env.foo }
  1228      moreInput: ${ env.BAR }
  1229  `
  1230  	rm := runtimeManifestFromStepYaml(t, testConfig, mContent)
  1231  	s := rm.Install[0]
  1232  
  1233  	err := rm.ResolveStep(ctx, 0, s)
  1234  	require.NoError(t, err)
  1235  
  1236  	require.IsType(t, map[string]interface{}{}, s.Data["mymixin"], "Data.mymixin has the wrong type")
  1237  	mixin := s.Data["mymixin"].(map[string]interface{})
  1238  
  1239  	assert.Equal(t, "foo-value", mixin["someInput"], "expected lower-case foo env var was resolved")
  1240  	assert.Equal(t, "bar-value", mixin["moreInput"], "expected upper-case BAR env var was resolved")
  1241  }
  1242  
  1243  func TestResolveInvocationImage(t *testing.T) {
  1244  	testcases := []struct {
  1245  		name                string
  1246  		bundleInvocationImg bundle.BaseImage
  1247  		relocationMap       relocation.ImageRelocationMap
  1248  		expectedImg         string
  1249  		wantErr             string
  1250  	}{
  1251  		{name: "success with no relocation map",
  1252  			bundleInvocationImg: bundle.BaseImage{Image: "blah/ghost:latest", ImageType: "docker", Digest: "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"},
  1253  			expectedImg:         "blah/ghost:latest@sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041",
  1254  		},
  1255  		{name: "success with relocation map",
  1256  			bundleInvocationImg: bundle.BaseImage{Image: "blah/ghost:latest", ImageType: "docker", Digest: "sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"},
  1257  			relocationMap:       relocation.ImageRelocationMap{"blah/ghost:latest@sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041": "relocated-ghost@sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041"},
  1258  			expectedImg:         "relocated-ghost@sha256:75c495e5ce9c428d482973d72e3ce9925e1db304a97946c9aa0b540d7537e041",
  1259  		},
  1260  		{name: "success with no update",
  1261  			expectedImg: "test/image:latest",
  1262  		},
  1263  		{name: "failure with invalid digest",
  1264  			bundleInvocationImg: bundle.BaseImage{Image: "blah/ghost:latest", ImageType: "docker", Digest: "123"},
  1265  			wantErr:             "unable to get bundle image reference with digest",
  1266  		},
  1267  	}
  1268  
  1269  	cfg := NewConfigFor(config.NewTestConfig(t).Config)
  1270  
  1271  	for _, tc := range testcases {
  1272  		t.Run(tc.name, func(t *testing.T) {
  1273  
  1274  			bun := cnab.NewBundle(bundle.Bundle{
  1275  				InvocationImages: []bundle.InvocationImage{
  1276  					{BaseImage: tc.bundleInvocationImg},
  1277  				},
  1278  			})
  1279  			m := &manifest.Manifest{
  1280  				Image: "test/image:latest",
  1281  			}
  1282  			rm := NewRuntimeManifest(cfg, cnab.ActionInstall, m)
  1283  
  1284  			err := rm.ResolveInvocationImage(bun, tc.relocationMap)
  1285  			if tc.wantErr != "" {
  1286  				require.ErrorContains(t, err, tc.wantErr)
  1287  				return
  1288  			}
  1289  			require.NoError(t, err)
  1290  			require.Equal(t, tc.expectedImg, m.Image)
  1291  		})
  1292  	}
  1293  
  1294  }