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, ¶mSet) 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, ¶ms) 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 }