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

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"path/filepath"
    10  	"slices"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"get.porter.sh/porter/pkg/cnab"
    16  	"get.porter.sh/porter/pkg/editor"
    17  	"get.porter.sh/porter/pkg/encoding"
    18  	"get.porter.sh/porter/pkg/generator"
    19  	"get.porter.sh/porter/pkg/printer"
    20  	"get.porter.sh/porter/pkg/secrets"
    21  	"get.porter.sh/porter/pkg/storage"
    22  	"get.porter.sh/porter/pkg/tracing"
    23  	dtprinter "github.com/carolynvs/datetime-printer"
    24  	"github.com/cnabio/cnab-go/bundle"
    25  	"github.com/cnabio/cnab-go/bundle/definition"
    26  	"github.com/cnabio/cnab-go/secrets/host"
    27  	"github.com/olekukonko/tablewriter"
    28  	"go.mongodb.org/mongo-driver/bson"
    29  )
    30  
    31  // ParameterShowOptions represent options for Porter's parameter show command
    32  type ParameterShowOptions struct {
    33  	printer.PrintOptions
    34  	Name      string
    35  	Namespace string
    36  }
    37  
    38  // ParameterEditOptions represent iptions for Porter's parameter edit command
    39  type ParameterEditOptions struct {
    40  	Name      string
    41  	Namespace string
    42  }
    43  
    44  // ListParameters lists saved parameter sets.
    45  func (p *Porter) ListParameters(ctx context.Context, opts ListOptions) ([]DisplayParameterSet, error) {
    46  	listOpts := storage.ListOptions{
    47  		Namespace: opts.GetNamespace(),
    48  		Name:      opts.Name,
    49  		Labels:    opts.ParseLabels(),
    50  		Skip:      opts.Skip,
    51  		Limit:     opts.Limit,
    52  	}
    53  	results, err := p.Parameters.ListParameterSets(ctx, listOpts)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	displayResults := make([]DisplayParameterSet, len(results))
    59  	for i, ps := range results {
    60  		displayResults[i] = NewDisplayParameterSet(ps)
    61  	}
    62  
    63  	return displayResults, nil
    64  }
    65  
    66  // PrintParameters prints saved parameter sets.
    67  func (p *Porter) PrintParameters(ctx context.Context, opts ListOptions) error {
    68  	params, err := p.ListParameters(ctx, opts)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	switch opts.Format {
    74  	case printer.FormatJson:
    75  		return printer.PrintJson(p.Out, params)
    76  	case printer.FormatYaml:
    77  		return printer.PrintYaml(p.Out, params)
    78  	case printer.FormatPlaintext:
    79  		// have every row use the same "now" starting ... NOW!
    80  		now := time.Now()
    81  		tp := dtprinter.DateTimePrinter{
    82  			Now: func() time.Time { return now },
    83  		}
    84  		result := [][]string{}
    85  		for _, ps := range params {
    86  			for _, param := range ps.Parameters {
    87  				list := []string{}
    88  				list = append(list, ps.Namespace, ps.Name, param.Name, param.Source.Strategy, param.Source.Hint, tp.Format(ps.Status.Modified))
    89  				result = append(result, list)
    90  			}
    91  		}
    92  		return printer.PrintTableParameterSet(p.Out, result,
    93  			"NAMESPACE", "PARAMETER SET", "NAME", "TYPE", "VALUE", "MODIFIED")
    94  	default:
    95  		return fmt.Errorf("invalid format: %s", opts.Format)
    96  	}
    97  }
    98  
    99  // ParameterOptions represent generic/base options for a Porter parameters command
   100  type ParameterOptions struct {
   101  	BundleReferenceOptions
   102  	Silent bool
   103  	Labels []string
   104  }
   105  
   106  func (o ParameterOptions) ParseLabels() map[string]string {
   107  	return parseLabels(o.Labels)
   108  }
   109  
   110  // Validate prepares for an action and validates the options.
   111  // For example, relative paths are converted to full paths and then checked that
   112  // they exist and are accessible.
   113  func (o *ParameterOptions) Validate(ctx context.Context, args []string, p *Porter) error {
   114  	err := o.validateParamName(args)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	return o.BundleReferenceOptions.Validate(ctx, args, p)
   120  }
   121  
   122  func (o *ParameterOptions) validateParamName(args []string) error {
   123  	if len(args) == 1 {
   124  		o.Name = args[0]
   125  	} else if len(args) > 1 {
   126  		return fmt.Errorf("only one positional argument may be specified, the parameter set name, but multiple were received: %s", args)
   127  	}
   128  	return nil
   129  }
   130  
   131  // GenerateParameters builds a new parameter set based on the given options. This can be either
   132  // a silent build, based on the opts.Silent flag, or interactive using a survey. Returns an
   133  // error if unable to generate parameters
   134  func (p *Porter) GenerateParameters(ctx context.Context, opts ParameterOptions) error {
   135  	bundleRef, err := opts.GetBundleReference(ctx, p)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	name := opts.Name
   141  	if name == "" {
   142  		name = bundleRef.Definition.Name
   143  	}
   144  	genOpts := generator.GenerateParametersOptions{
   145  		GenerateOptions: generator.GenerateOptions{
   146  			Name:      name,
   147  			Namespace: opts.Namespace,
   148  			Labels:    opts.ParseLabels(),
   149  			Silent:    opts.Silent,
   150  		},
   151  		Bundle: bundleRef.Definition,
   152  	}
   153  	fmt.Fprintf(p.Out, "Generating new parameter set %s from bundle %s\n", genOpts.Name, bundleRef.Definition.Name)
   154  	numExternalParams := 0
   155  
   156  	for name := range bundleRef.Definition.Parameters {
   157  		if !bundleRef.Definition.IsInternalParameter(name) {
   158  			numExternalParams += 1
   159  		}
   160  	}
   161  
   162  	fmt.Fprintf(p.Out, "==> %d parameter(s) declared for bundle %s\n", numExternalParams, bundleRef.Definition.Name)
   163  
   164  	pset, err := genOpts.GenerateParameters()
   165  	if err != nil {
   166  		return fmt.Errorf("unable to generate parameter set: %w", err)
   167  	}
   168  
   169  	if len(pset.Parameters) == 0 {
   170  		return nil
   171  	}
   172  
   173  	pset.Status.Created = time.Now()
   174  	pset.Status.Modified = pset.Status.Created
   175  
   176  	err = p.Parameters.UpsertParameterSet(ctx, pset)
   177  	if err != nil {
   178  		return fmt.Errorf("unable to save parameter set: %w", err)
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  // Validate validates the args provided to Porter's parameter show command
   185  func (o *ParameterShowOptions) Validate(args []string) error {
   186  	if err := validateParameterName(args); err != nil {
   187  		return err
   188  	}
   189  	o.Name = args[0]
   190  	return o.ParseFormat()
   191  }
   192  
   193  // Validate validates the args provided to Porter's parameter edit command
   194  func (o *ParameterEditOptions) Validate(args []string) error {
   195  	if err := validateParameterName(args); err != nil {
   196  		return err
   197  	}
   198  	o.Name = args[0]
   199  	return nil
   200  }
   201  
   202  // EditParameter edits the parameters of the provided name.
   203  func (p *Porter) EditParameter(ctx context.Context, opts ParameterEditOptions) error {
   204  	paramSet, err := p.Parameters.GetParameterSet(ctx, opts.Namespace, opts.Name)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	contents, err := encoding.MarshalYaml(paramSet)
   210  	if err != nil {
   211  		return fmt.Errorf("unable to load parameter set: %w", err)
   212  	}
   213  
   214  	editor := editor.New(p.Context, fmt.Sprintf("porter-%s.yaml", paramSet.Name), contents)
   215  	output, err := editor.Run(ctx)
   216  	if err != nil {
   217  		return fmt.Errorf("unable to open editor to edit parameter set: %w", err)
   218  	}
   219  
   220  	err = encoding.UnmarshalYaml(output, &paramSet)
   221  	if err != nil {
   222  		return fmt.Errorf("unable to process parameter set: %w", err)
   223  	}
   224  
   225  	err = p.Parameters.Validate(ctx, paramSet)
   226  	if err != nil {
   227  		return fmt.Errorf("parameter set is invalid: %w", err)
   228  	}
   229  
   230  	paramSet.Status.Modified = time.Now()
   231  	err = p.Parameters.UpdateParameterSet(ctx, paramSet)
   232  	if err != nil {
   233  		return fmt.Errorf("unable to save parameter set: %w", err)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  type DisplayParameterSet struct {
   240  	storage.ParameterSet `yaml:",inline"`
   241  }
   242  
   243  func NewDisplayParameterSet(ps storage.ParameterSet) DisplayParameterSet {
   244  	ds := DisplayParameterSet{ParameterSet: ps}
   245  	ds.SchemaType = storage.SchemaTypeParameterSet
   246  	return ds
   247  }
   248  
   249  // ShowParameter shows the parameter set corresponding to the provided name, using
   250  // the provided printer.PrintOptions for display.
   251  func (p *Porter) ShowParameter(ctx context.Context, opts ParameterShowOptions) error {
   252  	ps, err := p.Parameters.GetParameterSet(ctx, opts.Namespace, opts.Name)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	paramSet := NewDisplayParameterSet(ps)
   258  
   259  	switch opts.Format {
   260  	case printer.FormatJson:
   261  		return printer.PrintJson(p.Out, paramSet)
   262  	case printer.FormatYaml:
   263  		return printer.PrintYaml(p.Out, paramSet)
   264  	case printer.FormatPlaintext:
   265  		// Set up human friendly time formatter
   266  		now := time.Now()
   267  		tp := dtprinter.DateTimePrinter{
   268  			Now: func() time.Time { return now },
   269  		}
   270  
   271  		// Here we use an instance of olekukonko/tablewriter as our table,
   272  		// rather than using the printer pkg variant, as we wish to decorate
   273  		// the table a bit differently from the default
   274  		var rows [][]string
   275  
   276  		// Iterate through all ParameterStrategies and add to rows
   277  		for _, pset := range paramSet.Parameters {
   278  			rows = append(rows, []string{pset.Name, pset.Source.Hint, pset.Source.Strategy})
   279  		}
   280  
   281  		// Build and configure our tablewriter
   282  		table := tablewriter.NewWriter(p.Out)
   283  		table.SetCenterSeparator("")
   284  		table.SetColumnSeparator("")
   285  		table.SetAlignment(tablewriter.ALIGN_LEFT)
   286  		table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
   287  		table.SetBorders(tablewriter.Border{Left: false, Right: false, Bottom: false, Top: true})
   288  		table.SetAutoFormatHeaders(false)
   289  
   290  		// First, print the ParameterSet metadata
   291  		fmt.Fprintf(p.Out, "Name: %s\n", paramSet.Name)
   292  		fmt.Fprintf(p.Out, "Created: %s\n", tp.Format(paramSet.Status.Created))
   293  		fmt.Fprintf(p.Out, "Modified: %s\n\n", tp.Format(paramSet.Status.Modified))
   294  
   295  		// Print labels, if any
   296  		if len(paramSet.Labels) > 0 {
   297  			fmt.Fprintln(p.Out, "Labels:")
   298  
   299  			for k, v := range paramSet.Labels {
   300  				fmt.Fprintf(p.Out, "  %s: %s\n", k, v)
   301  			}
   302  			fmt.Fprintln(p.Out)
   303  		}
   304  
   305  		// Now print the table
   306  		table.SetHeader([]string{"Name", "Local Source", "Source Type"})
   307  		for _, row := range rows {
   308  			table.Append(row)
   309  		}
   310  		table.Render()
   311  		return nil
   312  	default:
   313  		return fmt.Errorf("invalid format: %s", opts.Format)
   314  	}
   315  }
   316  
   317  // ParameterDeleteOptions represent options for Porter's parameter delete command
   318  type ParameterDeleteOptions struct {
   319  	Name      string
   320  	Namespace string
   321  }
   322  
   323  // DeleteParameter deletes the parameter set corresponding to the provided
   324  // names.
   325  func (p *Porter) DeleteParameter(ctx context.Context, opts ParameterDeleteOptions) error {
   326  	ctx, span := tracing.StartSpan(ctx)
   327  	defer span.EndSpan()
   328  
   329  	err := p.Parameters.RemoveParameterSet(ctx, opts.Namespace, opts.Name)
   330  	if errors.Is(err, storage.ErrNotFound{}) {
   331  		span.Debug("Cannot remove parameter set because it already doesn't exist")
   332  		return nil
   333  	}
   334  	if err != nil {
   335  		return span.Error(fmt.Errorf("unable to delete parameter set: %w", err))
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  // Validate the args provided to the delete parameter command
   342  func (o *ParameterDeleteOptions) Validate(args []string) error {
   343  	if err := validateParameterName(args); err != nil {
   344  		return err
   345  	}
   346  	o.Name = args[0]
   347  	return nil
   348  }
   349  
   350  func validateParameterName(args []string) error {
   351  	switch len(args) {
   352  	case 0:
   353  		return fmt.Errorf("no parameter set name was specified")
   354  	case 1:
   355  		return nil
   356  	default:
   357  		return fmt.Errorf("only one positional argument may be specified, the parameter set name, but multiple were received: %s", args)
   358  	}
   359  }
   360  
   361  // loadParameterSets loads parameter values per their parameter set strategies
   362  func (p *Porter) loadParameterSets(ctx context.Context, bun cnab.ExtendedBundle, namespace string, params []string, overridenParameters secrets.StrategyList) (secrets.Set, error) {
   363  	resolvedParameters := secrets.Set{}
   364  
   365  	for _, name := range params {
   366  		// Try to get the params in the local namespace first, fallback to the global creds
   367  		query := storage.FindOptions{
   368  			Sort: []string{"-namespace"},
   369  			Filter: bson.M{
   370  				"name": name,
   371  				"$or": []bson.M{
   372  					{"namespace": ""},
   373  					{"namespace": namespace},
   374  				},
   375  			},
   376  		}
   377  		store := p.Parameters.GetDataStore()
   378  
   379  		var pset storage.ParameterSet
   380  		err := store.FindOne(ctx, storage.CollectionParameters, query, &pset)
   381  		if err != nil {
   382  			return nil, err
   383  		}
   384  
   385  		var skipParams []string
   386  		for _, param := range pset.Parameters {
   387  			if overridenParameters.Contains(param.Name) {
   388  				skipParams = append(skipParams, param.Name)
   389  			}
   390  		}
   391  
   392  		// A parameter may correspond to a Porter-specific parameter type of 'file'
   393  		// If so and the hint is a filepath, pass the value directly and remove from pset
   394  		for paramName, paramDef := range bun.Parameters {
   395  			paramSchema, ok := bun.Definitions[paramDef.Definition]
   396  			if !ok {
   397  				return nil, fmt.Errorf("definition %s not defined in bundle", paramDef.Definition)
   398  			}
   399  			if bun.IsFileType(paramSchema) {
   400  				for i, param := range pset.Parameters {
   401  					if param.Name == paramName && param.Source.Strategy == host.SourcePath {
   402  						// Pass through value (filepath) directly to resolvedParameters
   403  						resolvedParameters[param.Name] = param.Source.Hint
   404  						// Eliminate this param from pset to prevent its resolution by
   405  						// the cnab-go library, which doesn't support this parameter type
   406  						pset.Parameters[i] = pset.Parameters[len(pset.Parameters)-1]
   407  						pset.Parameters = pset.Parameters[:len(pset.Parameters)-1]
   408  					}
   409  				}
   410  			}
   411  		}
   412  
   413  		keysToResolve := pset.Keys()
   414  		if len(skipParams) > 0 {
   415  			keysToResolve = []string{}
   416  			for _, param := range pset.Parameters {
   417  				if !slices.Contains(skipParams, param.Name) {
   418  					keysToResolve = append(keysToResolve, param.Name)
   419  				}
   420  			}
   421  		}
   422  
   423  		rc, err := p.Parameters.ResolveAll(ctx, pset, keysToResolve)
   424  		if err != nil {
   425  			return nil, err
   426  		}
   427  
   428  		for k, v := range rc {
   429  			resolvedParameters[k] = v
   430  		}
   431  	}
   432  
   433  	return resolvedParameters, nil
   434  }
   435  
   436  type DisplayValue struct {
   437  	Name      string      `json:"name" yaml:"name"`
   438  	Type      string      `json:"type" yaml:"type"`
   439  	Sensitive bool        `json:"sensitive" yaml:"sensitive"`
   440  	Value     interface{} `json:"value" yaml:"value"`
   441  }
   442  
   443  func (v *DisplayValue) SetValue(value interface{}) {
   444  	switch val := value.(type) {
   445  	case []byte:
   446  		v.Value = string(val)
   447  	default:
   448  		v.Value = val
   449  	}
   450  }
   451  
   452  func (v DisplayValue) PrintValue() string {
   453  	if v.Sensitive {
   454  		return "******"
   455  	}
   456  
   457  	var printedValue string
   458  	switch val := v.Value.(type) {
   459  	case string:
   460  		printedValue = val
   461  	default:
   462  		b, err := json.Marshal(v.Value)
   463  		if err != nil {
   464  			return "error rendering value"
   465  		}
   466  		printedValue = string(b)
   467  	}
   468  	return truncateString(printedValue, 60)
   469  }
   470  
   471  type DisplayValues []DisplayValue
   472  
   473  func (v DisplayValues) Len() int {
   474  	return len(v)
   475  }
   476  
   477  func (v DisplayValues) Swap(i, j int) {
   478  	v[i], v[j] = v[j], v[i]
   479  }
   480  
   481  func (v DisplayValues) Less(i, j int) bool {
   482  	return v[i].Name < v[j].Name
   483  }
   484  
   485  func (v DisplayValues) Get(name string) (DisplayValue, bool) {
   486  	for _, entry := range v {
   487  		if entry.Name == name {
   488  			return entry, true
   489  		}
   490  	}
   491  
   492  	return DisplayValue{}, false
   493  }
   494  
   495  func NewDisplayValuesFromParameters(bun cnab.ExtendedBundle, params map[string]interface{}) DisplayValues {
   496  	// Iterate through all Bundle Outputs, fetch their metadata
   497  	// via their corresponding Definitions and add to rows
   498  	displayParams := make(DisplayValues, 0, len(params))
   499  	for name, value := range params {
   500  		def, ok := bun.Parameters[name]
   501  		if !ok || bun.IsInternalParameter(name) {
   502  			continue
   503  		}
   504  
   505  		dp := &DisplayValue{Name: name}
   506  		dp.SetValue(value)
   507  
   508  		schema, ok := bun.Definitions[def.Definition]
   509  		if ok {
   510  			dp.Type = bun.GetParameterType(schema)
   511  			if schema.WriteOnly != nil && *schema.WriteOnly {
   512  				dp.Sensitive = true
   513  			}
   514  		} else {
   515  			dp.Type = "unknown"
   516  		}
   517  
   518  		displayParams = append(displayParams, *dp)
   519  	}
   520  
   521  	sort.Sort(displayParams)
   522  	return displayParams
   523  }
   524  
   525  func (p *Porter) printDisplayValuesTable(values []DisplayValue) error {
   526  	// Build and configure our tablewriter for the outputs
   527  	table := tablewriter.NewWriter(p.Out)
   528  	table.SetCenterSeparator("")
   529  	table.SetColumnSeparator("")
   530  	table.SetAlignment(tablewriter.ALIGN_LEFT)
   531  	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
   532  	table.SetBorders(tablewriter.Border{Left: false, Right: false, Bottom: false, Top: true})
   533  	table.SetAutoFormatHeaders(false)
   534  
   535  	table.SetHeader([]string{"Name", "Type", "Value"})
   536  	for _, param := range values {
   537  		table.Append([]string{param.Name, param.Type, param.PrintValue()})
   538  	}
   539  	table.Render()
   540  
   541  	return nil
   542  }
   543  
   544  func (p *Porter) ParametersApply(ctx context.Context, o ApplyOptions) error {
   545  	ctx, span := tracing.StartSpan(ctx)
   546  	defer span.EndSpan()
   547  
   548  	span.Debugf("Reading input file %s...", o.File)
   549  	namespace, err := p.getNamespaceFromFile(o)
   550  	if err != nil {
   551  		return span.Error(err)
   552  	}
   553  
   554  	var params DisplayParameterSet
   555  	err = encoding.UnmarshalFile(p.FileSystem, o.File, &params)
   556  	if err != nil {
   557  		return span.Error(fmt.Errorf("could not load %s as a parameter set: %w", o.File, err))
   558  	}
   559  
   560  	checkStrategy := p.GetSchemaCheckStrategy(ctx)
   561  	if err = params.Validate(ctx, checkStrategy); err != nil {
   562  		return span.Error(fmt.Errorf("invalid parameter set: %w", err))
   563  	}
   564  
   565  	params.Namespace = namespace
   566  	params.Status.Modified = time.Now()
   567  
   568  	err = p.Parameters.Validate(ctx, params.ParameterSet)
   569  	if err != nil {
   570  		return span.Error(fmt.Errorf("parameter set is invalid: %w", err))
   571  	}
   572  
   573  	err = p.Parameters.UpsertParameterSet(ctx, params.ParameterSet)
   574  	if err != nil {
   575  		return err
   576  	}
   577  
   578  	fmt.Fprintf(p.Out, "Applied %s parameter set\n", params)
   579  	return nil
   580  }
   581  
   582  // finalizeParameters accepts a set of resolved parameters and combines them
   583  // with parameter sources and default parameter values to create a full set
   584  // of parameters that are defined in proper Go types, and not strings.
   585  func (p *Porter) finalizeParameters(ctx context.Context, installation storage.Installation, bun cnab.ExtendedBundle, action string, params map[string]string) (map[string]interface{}, error) {
   586  	mergedParams := make(secrets.Set, len(params))
   587  	paramSources, err := p.resolveParameterSources(ctx, bun, installation)
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	for key, val := range paramSources {
   593  		mergedParams[key] = val
   594  	}
   595  
   596  	// Apply user supplied parameter overrides last
   597  	for key, rawValue := range params {
   598  		param, ok := bun.Parameters[key]
   599  		if !ok {
   600  			return nil, fmt.Errorf("parameter %s not defined in bundle", key)
   601  		}
   602  
   603  		def, ok := bun.Definitions[param.Definition]
   604  		if !ok {
   605  			return nil, fmt.Errorf("definition %s not defined in bundle", param.Definition)
   606  		}
   607  
   608  		// Apply porter specific conversions, like retrieving file contents
   609  		value, err := p.getUnconvertedValueFromRaw(bun, def, key, rawValue)
   610  		if err != nil {
   611  			return nil, err
   612  		}
   613  
   614  		mergedParams[key] = value
   615  	}
   616  
   617  	// Now convert all parameters which are currently strings into the
   618  	// proper type for the parameter, e.g. "false" -> false
   619  	typedParams := make(map[string]interface{}, len(mergedParams))
   620  	for key, unconverted := range mergedParams {
   621  		param, ok := bun.Parameters[key]
   622  		if !ok {
   623  			return nil, fmt.Errorf("parameter %s not defined in bundle", key)
   624  		}
   625  
   626  		def, ok := bun.Definitions[param.Definition]
   627  		if !ok {
   628  			return nil, fmt.Errorf("definition %s not defined in bundle", param.Definition)
   629  		}
   630  
   631  		if def.Type != nil {
   632  			value, err := def.ConvertValue(unconverted)
   633  			if err != nil {
   634  				return nil, fmt.Errorf("unable to convert parameter's %s value %s to the destination parameter type %s: %w", key, unconverted, def.Type, err)
   635  			}
   636  			typedParams[key] = value
   637  		} else {
   638  			// bundle dependency parameters can be any type, not sure we have a solid way to do a typed conversion
   639  			typedParams[key] = unconverted
   640  		}
   641  
   642  	}
   643  
   644  	return bundle.ValuesOrDefaults(typedParams, &bun.Bundle, action)
   645  }
   646  
   647  func (p *Porter) getUnconvertedValueFromRaw(b cnab.ExtendedBundle, def *definition.Schema, key, rawValue string) (string, error) {
   648  	// the parameter value (via rawValue) may represent a file on the local filesystem
   649  	if b.IsFileType(def) {
   650  		if _, err := p.FileSystem.Stat(rawValue); err == nil {
   651  			bytes, err := p.FileSystem.ReadFile(rawValue)
   652  			if err != nil {
   653  				return "", fmt.Errorf("unable to read file parameter %s at %s: %w", key, rawValue, err)
   654  			}
   655  			return base64.StdEncoding.EncodeToString(bytes), nil
   656  		}
   657  	}
   658  	return rawValue, nil
   659  }
   660  
   661  func (p *Porter) resolveParameterSources(ctx context.Context, bun cnab.ExtendedBundle, installation storage.Installation) (secrets.Set, error) {
   662  	ctx, span := tracing.StartSpan(ctx)
   663  	defer span.EndSpan()
   664  
   665  	if !bun.HasParameterSources() {
   666  		span.Debug("No parameter sources defined, skipping")
   667  		return nil, nil
   668  	}
   669  
   670  	span.Debug("Resolving parameter sources...")
   671  	parameterSources, err := bun.ReadParameterSources()
   672  	if err != nil {
   673  		return nil, span.Error(err)
   674  	}
   675  
   676  	values := secrets.Set{}
   677  	for parameterName, parameterSource := range parameterSources {
   678  		span.Debugf("Resolving parameter source %s", parameterName)
   679  		for _, rawSource := range parameterSource.ListSourcesByPriority() {
   680  			var installationName string
   681  			var outputName string
   682  			switch source := rawSource.(type) {
   683  			case cnab.OutputParameterSource:
   684  				installationName = installation.Name
   685  				outputName = source.OutputName
   686  			case cnab.DependencyOutputParameterSource:
   687  				// TODO(carolynvs): does this need to take namespace into account
   688  				installationName = bun.BuildPrerequisiteInstallationName(installation.Name, source.Dependency)
   689  				outputName = source.OutputName
   690  			}
   691  
   692  			output, err := p.Installations.GetLastOutput(ctx, installation.Namespace, installationName, outputName)
   693  			if err != nil {
   694  				// When we can't find the output, skip it and let the parameter be set another way
   695  				if errors.Is(err, storage.ErrNotFound{}) {
   696  					span.Debugf("No previous output found for %s from %s/%s", outputName, installation.Namespace, installationName)
   697  					continue
   698  				}
   699  				// Otherwise, something else has happened, perhaps bad data or connectivity problems, we can't ignore it
   700  				return nil, span.Error(fmt.Errorf("could not set parameter %s from output %s of %s: %w", parameterName, outputName, installation, err))
   701  			}
   702  
   703  			if output.Key != "" {
   704  				resolved, err := p.Sanitizer.RestoreOutput(ctx, output)
   705  				if err != nil {
   706  					return nil, span.Error(fmt.Errorf("could not resolve %s's output %s: %w", installation, outputName, err))
   707  				}
   708  				output = resolved
   709  			}
   710  
   711  			param, ok := bun.Parameters[parameterName]
   712  			if !ok {
   713  				return nil, span.Error(fmt.Errorf("resolveParameterSources:  %s not defined in bundle", parameterName))
   714  			}
   715  
   716  			def, ok := bun.Definitions[param.Definition]
   717  			if !ok {
   718  				return nil, span.Error(fmt.Errorf("definition %s not defined in bundle", param.Definition))
   719  			}
   720  
   721  			if bun.IsFileType(def) {
   722  				values[parameterName] = base64.StdEncoding.EncodeToString(output.Value)
   723  			} else {
   724  				values[parameterName] = string(output.Value)
   725  			}
   726  
   727  			span.Debugf("Injected installation %s output %s as parameter %s", installation, outputName, parameterName)
   728  		}
   729  	}
   730  
   731  	return values, nil
   732  }
   733  
   734  // ParameterCreateOptions represent options for Porter's parameter create command
   735  type ParameterCreateOptions struct {
   736  	FileName   string
   737  	OutputType string
   738  }
   739  
   740  func (o *ParameterCreateOptions) Validate(args []string) error {
   741  	if len(args) > 1 {
   742  		return fmt.Errorf("only one positional argument may be specified, fileName, but multiple were received: %s", args)
   743  	}
   744  
   745  	if len(args) > 0 {
   746  		o.FileName = args[0]
   747  	}
   748  
   749  	if o.OutputType == "" && o.FileName != "" && strings.Trim(filepath.Ext(o.FileName), ".") == "" {
   750  		return errors.New("could not detect the file format from the file extension (.txt). Specify the format with --output")
   751  	}
   752  
   753  	return nil
   754  }
   755  
   756  func (p *Porter) CreateParameter(opts ParameterCreateOptions) error {
   757  	if opts.OutputType == "" {
   758  		opts.OutputType = strings.Trim(filepath.Ext(opts.FileName), ".")
   759  	}
   760  
   761  	if opts.FileName == "" {
   762  		if opts.OutputType == "" {
   763  			opts.OutputType = "yaml"
   764  		}
   765  
   766  		switch opts.OutputType {
   767  		case "json":
   768  			parameterSet, err := p.Templates.GetParameterSetJSON()
   769  			if err != nil {
   770  				return err
   771  			}
   772  			fmt.Fprintln(p.Out, string(parameterSet))
   773  
   774  			return nil
   775  		case "yaml", "yml":
   776  			parameterSet, err := p.Templates.GetParameterSetYAML()
   777  			if err != nil {
   778  				return err
   779  			}
   780  			fmt.Fprintln(p.Out, string(parameterSet))
   781  
   782  			return nil
   783  		default:
   784  			return newUnsupportedFormatError(opts.OutputType)
   785  		}
   786  
   787  	}
   788  
   789  	fmt.Fprintln(p.Err, "creating porter parameter set in the current directory")
   790  
   791  	switch opts.OutputType {
   792  	case "json":
   793  		return p.CopyTemplate(p.Templates.GetParameterSetJSON, opts.FileName)
   794  	case "yaml", "yml":
   795  		return p.CopyTemplate(p.Templates.GetParameterSetYAML, opts.FileName)
   796  	default:
   797  		return newUnsupportedFormatError(opts.OutputType)
   798  	}
   799  }
   800  
   801  // applyActionOptionsToInstallation applies the specified action (e.g.
   802  // install/upgrade) to an installation record. This consolidates parameters and
   803  // credentials into a single parameter set or credential set, ready to be resolved
   804  // immediately before the bundle is run, and modifies the specified installation
   805  // record.
   806  //
   807  // This does not resolve the parameters, that only occurs before the bundle is run.
   808  // You must sanitize the parameters before saving the installation so
   809  // that sensitive values are not saved to the database.
   810  func (p *Porter) applyActionOptionsToInstallation(ctx context.Context, ba BundleAction, inst *storage.Installation) error {
   811  	ctx, span := tracing.StartSpan(ctx)
   812  	defer span.EndSpan()
   813  
   814  	o := ba.GetOptions()
   815  
   816  	bundleRef, err := o.GetBundleReference(ctx, p)
   817  	if err != nil {
   818  		return err
   819  	}
   820  	bun := bundleRef.Definition
   821  
   822  	// Update the installation with metadata from the options
   823  	inst.TrackBundle(bundleRef.Reference)
   824  	inst.Status.Modified = time.Now()
   825  
   826  	// Remove installation parameters no longer present in the bundle
   827  	if inst.Parameters.Parameters != nil {
   828  		updatedInstParams := make(secrets.StrategyList, 0, len(inst.Parameters.Parameters))
   829  		for _, param := range inst.Parameters.Parameters {
   830  			if _, ok := bun.Parameters[param.Name]; ok {
   831  				updatedInstParams = append(updatedInstParams, param)
   832  			}
   833  		}
   834  		inst.Parameters.Parameters = updatedInstParams
   835  	}
   836  
   837  	//
   838  	// 1. Record the parameter and credential sets used on the installation
   839  	// if none were specified, reuse the previous sets from the installation
   840  	//
   841  	span.SetAttributes(
   842  		tracing.ObjectAttribute("override-parameter-sets", o.ParameterSets),
   843  		tracing.ObjectAttribute("override-credential-sets", o.CredentialIdentifiers))
   844  	if len(o.ParameterSets) > 0 {
   845  		inst.ParameterSets = o.ParameterSets
   846  	}
   847  	if len(o.CredentialIdentifiers) > 0 {
   848  		inst.CredentialSets = o.CredentialIdentifiers
   849  	}
   850  
   851  	//
   852  	// 2. Parse parameter flags from the command line and apply to the installation as overrides
   853  	//
   854  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   855  	span.SetSensitiveAttributes(tracing.ObjectAttribute("override-parameters", o.Params))
   856  	parsedOverrides, err := storage.ParseVariableAssignments(o.Params)
   857  	if err != nil {
   858  		return err
   859  	}
   860  
   861  	// Default the porter-debug param to --debug
   862  	if o.DebugMode {
   863  		parsedOverrides["porter-debug"] = "true"
   864  	} else {
   865  		// Remove porter-debug parameter from the installation parameters
   866  		for i := len(inst.Parameters.Parameters) - 1; i >= 0; i-- {
   867  			if inst.Parameters.Parameters[i].Name == "porter-debug" {
   868  				inst.Parameters.Parameters = append(inst.Parameters.Parameters[:i], inst.Parameters.Parameters[i+1:]...)
   869  				break
   870  			}
   871  		}
   872  	}
   873  
   874  	// Apply overrides on to of any pre-existing parameters that were specified previously
   875  	if len(parsedOverrides) > 0 {
   876  		for name, value := range parsedOverrides {
   877  			// Do not resolve parameters from dependencies
   878  			if strings.Contains(name, "#") {
   879  				continue
   880  			}
   881  
   882  			// Replace previous value if present
   883  			replaced := false
   884  			paramStrategy := storage.ValueStrategy(name, value)
   885  			for i, existingParam := range inst.Parameters.Parameters {
   886  				if existingParam.Name == name {
   887  					inst.Parameters.Parameters[i] = paramStrategy
   888  					replaced = true
   889  				}
   890  			}
   891  			if !replaced {
   892  				inst.Parameters.Parameters = append(inst.Parameters.Parameters, paramStrategy)
   893  			}
   894  		}
   895  
   896  		// Keep the parameter overrides sorted, so that comparisons and general troubleshooting is easier
   897  		sort.Sort(inst.Parameters.Parameters)
   898  	}
   899  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   900  	span.SetSensitiveAttributes(tracing.ObjectAttribute("merged-installation-parameters", inst.Parameters.Parameters))
   901  
   902  	//
   903  	// 3. Resolve named parameter sets
   904  	//
   905  	resolvedParams, err := p.loadParameterSets(ctx, bun, o.Namespace, inst.ParameterSets, inst.Parameters.Parameters)
   906  	if err != nil {
   907  		return fmt.Errorf("unable to process provided parameter sets: %w", err)
   908  	}
   909  
   910  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   911  	span.SetSensitiveAttributes(tracing.ObjectAttribute("resolved-parameter-sets-keys", resolvedParams))
   912  
   913  	//
   914  	// 4. Resolve the installation's internal parameter set
   915  	resolvedOverrides, err := p.Parameters.ResolveAll(ctx, inst.Parameters, inst.Parameters.Keys())
   916  	if err != nil {
   917  		return err
   918  	}
   919  
   920  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   921  	span.SetSensitiveAttributes(tracing.ObjectAttribute("resolved-installation-parameters", inst.Parameters.Parameters))
   922  
   923  	//
   924  	// 5. Apply the overrides on top of the parameter sets
   925  	//
   926  	for k, v := range resolvedOverrides {
   927  		resolvedParams[k] = v
   928  	}
   929  
   930  	for name, value := range parsedOverrides {
   931  		if strings.Contains(name, "#") {
   932  			resolvedParams[name] = value
   933  		}
   934  	}
   935  
   936  	//
   937  	// 6. Separate out params for the root bundle from the ones intended for dependencies
   938  	//    This only applies to the dep v1 implementation, in dep v2 you can't specify rando params for deps
   939  	//
   940  	o.depParams = make(map[string]string)
   941  	for k, v := range resolvedParams {
   942  		if strings.Contains(k, "#") {
   943  			o.depParams[k] = v
   944  			delete(resolvedParams, k)
   945  		}
   946  	}
   947  
   948  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   949  	span.SetSensitiveAttributes(tracing.ObjectAttribute("user-specified-parameters", resolvedParams))
   950  
   951  	//
   952  	// 7. When a parameter is not specified, fallback to a parameter source or default
   953  	//
   954  	finalParams, err := p.finalizeParameters(ctx, *inst, bun, ba.GetAction(), resolvedParams)
   955  	if err != nil {
   956  		return err
   957  	}
   958  
   959  	// This contains resolved sensitive values, so only trace it in special dev builds (nothing is traced for release builds)
   960  	span.SetSensitiveAttributes(tracing.ObjectAttribute("final-parameters", finalParams))
   961  
   962  	// Remember the final set of parameters so we don't have to resolve them more than once
   963  	o.finalParams = finalParams
   964  
   965  	// Ensure we aren't storing any secrets on the installation resource
   966  	if err = p.sanitizeInstallation(ctx, inst, bundleRef.Definition); err != nil {
   967  		return err
   968  	}
   969  
   970  	// re-validate the installation since we modified it here
   971  	return inst.Validate(ctx, p.GetSchemaCheckStrategy(ctx))
   972  }