github.com/hashicorp/packer@v1.14.3/packer/core.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package packer 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "log" 10 "regexp" 11 "sort" 12 "strconv" 13 "strings" 14 15 ttmp "text/template" 16 17 "github.com/google/go-cmp/cmp" 18 multierror "github.com/hashicorp/go-multierror" 19 version "github.com/hashicorp/go-version" 20 hcl "github.com/hashicorp/hcl/v2" 21 "github.com/hashicorp/packer-plugin-sdk/didyoumean" 22 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 23 "github.com/hashicorp/packer-plugin-sdk/template" 24 "github.com/hashicorp/packer-plugin-sdk/template/interpolate" 25 plugingetter "github.com/hashicorp/packer/packer/plugin-getter" 26 packerversion "github.com/hashicorp/packer/version" 27 ) 28 29 // Core is the main executor of Packer. If Packer is being used as a 30 // library, this is the struct you'll want to instantiate to get anything done. 31 type Core struct { 32 Template *template.Template 33 34 components ComponentFinder 35 variables map[string]string 36 builds map[string]*template.Builder 37 version string 38 secrets []string 39 40 except []string 41 only []string 42 } 43 44 // CoreConfig is the structure for initializing a new Core. Once a CoreConfig 45 // is used to initialize a Core, it shouldn't be re-used or modified again. 46 type CoreConfig struct { 47 Components ComponentFinder 48 Template *template.Template 49 Variables map[string]string 50 SensitiveVariables []string 51 Version string 52 53 // These are set by command-line flags 54 Except []string 55 Only []string 56 } 57 58 // The function type used to lookup Builder implementations. 59 type BuilderFunc func(name string) (packersdk.Builder, error) 60 61 // The function type used to lookup Hook implementations. 62 type HookFunc func(name string) (packersdk.Hook, error) 63 64 // The function type used to lookup PostProcessor implementations. 65 type PostProcessorFunc func(name string) (packersdk.PostProcessor, error) 66 67 // The function type used to lookup Provisioner implementations. 68 type ProvisionerFunc func(name string) (packersdk.Provisioner, error) 69 70 type BasicStore interface { 71 Has(name string) bool 72 List() (names []string) 73 } 74 75 type BuilderStore interface { 76 BasicStore 77 Start(name string) (packersdk.Builder, error) 78 } 79 80 type BuilderSet interface { 81 BuilderStore 82 Set(name string, starter func() (packersdk.Builder, error)) 83 } 84 85 type ProvisionerStore interface { 86 BasicStore 87 Start(name string) (packersdk.Provisioner, error) 88 } 89 90 type ProvisionerSet interface { 91 ProvisionerStore 92 Set(name string, starter func() (packersdk.Provisioner, error)) 93 } 94 95 type PostProcessorStore interface { 96 BasicStore 97 Start(name string) (packersdk.PostProcessor, error) 98 } 99 100 type PostProcessorSet interface { 101 PostProcessorStore 102 Set(name string, starter func() (packersdk.PostProcessor, error)) 103 } 104 105 type DatasourceStore interface { 106 BasicStore 107 Start(name string) (packersdk.Datasource, error) 108 } 109 110 type DatasourceSet interface { 111 DatasourceStore 112 Set(name string, starter func() (packersdk.Datasource, error)) 113 } 114 115 // ComponentFinder is a struct that contains the various function 116 // pointers necessary to look up components of Packer such as builders, 117 // commands, etc. 118 type ComponentFinder struct { 119 Hook HookFunc 120 PluginConfig *PluginConfig 121 } 122 123 // NewCore creates a new Core. 124 func NewCore(c *CoreConfig) *Core { 125 core := &Core{ 126 Template: c.Template, 127 components: c.Components, 128 variables: c.Variables, 129 version: c.Version, 130 only: c.Only, 131 except: c.Except, 132 } 133 return core 134 } 135 136 // DetectPluginBinaries is used to load required plugins from the template, 137 // since it is unsupported in JSON, this is essentially a no-op. 138 func (c *Core) DetectPluginBinaries() hcl.Diagnostics { 139 var diags hcl.Diagnostics 140 141 err := c.components.PluginConfig.Discover() 142 if err != nil { 143 diags = diags.Append(&hcl.Diagnostic{ 144 Severity: hcl.DiagError, 145 Summary: "Failed to discover installed plugins", 146 Detail: err.Error(), 147 }) 148 } 149 150 return diags 151 } 152 153 func (c *Core) Initialize(_ InitializeOptions) hcl.Diagnostics { 154 err := c.initialize() 155 if err != nil { 156 return hcl.Diagnostics{ 157 &hcl.Diagnostic{ 158 Detail: err.Error(), 159 Severity: hcl.DiagError, 160 }, 161 } 162 } 163 return nil 164 } 165 166 func (core *Core) initialize() error { 167 if err := core.validate(); err != nil { 168 return err 169 } 170 if err := core.init(); err != nil { 171 return err 172 } 173 for _, secret := range core.secrets { 174 packersdk.LogSecretFilter.Set(secret) 175 } 176 177 // Go through and interpolate all the build names. We should be able 178 // to do this at this point with the variables. 179 core.builds = make(map[string]*template.Builder) 180 for _, b := range core.Template.Builders { 181 v, err := interpolate.Render(b.Name, core.Context()) 182 if err != nil { 183 return fmt.Errorf( 184 "Error interpolating builder '%s': %s", 185 b.Name, err) 186 } 187 188 core.builds[v] = b 189 } 190 191 return nil 192 } 193 194 func (c *Core) PluginRequirements() (plugingetter.Requirements, hcl.Diagnostics) { 195 return nil, hcl.Diagnostics{ 196 &hcl.Diagnostic{ 197 Summary: "Packer plugins currently only works with HCL2 configuration templates", 198 Detail: "Please manually install plugins with the plugins command or use a HCL2 configuration that will do that for you.", 199 Severity: hcl.DiagError, 200 }, 201 } 202 } 203 204 // BuildNames returns the builds that are available in this configured core. 205 func (c *Core) BuildNames(only, except []string) []string { 206 207 sort.Strings(only) 208 sort.Strings(except) 209 c.except = except 210 c.only = only 211 212 r := make([]string, 0, len(c.builds)) 213 for n := range c.builds { 214 onlyPos := sort.SearchStrings(only, n) 215 foundInOnly := onlyPos < len(only) && only[onlyPos] == n 216 if len(only) > 0 && !foundInOnly { 217 continue 218 } 219 220 if pos := sort.SearchStrings(except, n); pos < len(except) && except[pos] == n { 221 continue 222 } 223 r = append(r, n) 224 } 225 sort.Strings(r) 226 227 return r 228 } 229 230 func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName string) (CoreBuildProvisioner, error) { 231 // Get the provisioner 232 cbp := CoreBuildProvisioner{} 233 234 if !c.components.PluginConfig.Provisioners.Has(rawP.Type) { 235 err := fmt.Errorf( 236 "The provisioner %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ 237 "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ 238 "https://developer.hashicorp.com/packer/integrations?filter=%s", 239 rawP.Type, 240 strings.Split(rawP.Type, "-")[0], 241 ) 242 243 if sugg := didyoumean.NameSuggestion(rawP.Type, c.components.PluginConfig.Provisioners.List()); sugg != "" { 244 err = fmt.Errorf("Did you mean to use %q?", sugg) 245 } 246 247 return cbp, err 248 } 249 250 provisioner, err := c.components.PluginConfig.Provisioners.Start(rawP.Type) 251 if err != nil { 252 return cbp, fmt.Errorf( 253 "error initializing provisioner '%s': %s", 254 rawP.Type, err) 255 } 256 // Seems unlikely that a provisioner doesn't start successfully without error 257 if provisioner == nil { 258 return cbp, fmt.Errorf( 259 "provisioner failed to be started and did not error: %s", rawP.Type) 260 } 261 262 // Get the configuration 263 config := make([]interface{}, 1, 2) 264 config[0] = rawP.Config 265 if rawP.Override != nil { 266 if override, ok := rawP.Override[rawName]; ok { 267 config = append(config, override) 268 } 269 } 270 // If we're pausing, we wrap the provisioner in a special pauser. 271 if rawP.PauseBefore != 0 { 272 provisioner = &PausedProvisioner{ 273 PauseBefore: rawP.PauseBefore, 274 Provisioner: provisioner, 275 } 276 } else if rawP.Timeout != 0 { 277 provisioner = &TimeoutProvisioner{ 278 Timeout: rawP.Timeout, 279 Provisioner: provisioner, 280 } 281 } 282 maxRetries := 0 283 if rawP.MaxRetries != "" { 284 renderedMaxRetries, err := interpolate.Render(rawP.MaxRetries, c.Context()) 285 if err != nil { 286 return cbp, fmt.Errorf("failed to interpolate `max_retries`: %s", err.Error()) 287 } 288 maxRetries, err = strconv.Atoi(renderedMaxRetries) 289 if err != nil { 290 return cbp, fmt.Errorf("`max_retries` must be a valid integer: %s", err.Error()) 291 } 292 } 293 if maxRetries != 0 { 294 provisioner = &RetriedProvisioner{ 295 MaxRetries: maxRetries, 296 Provisioner: provisioner, 297 } 298 } 299 300 if rawP.Type == "hcp-sbom" { 301 provisioner = &SBOMInternalProvisioner{ 302 Provisioner: provisioner, 303 } 304 } 305 306 cbp = CoreBuildProvisioner{ 307 PType: rawP.Type, 308 Provisioner: provisioner, 309 config: config, 310 } 311 312 return cbp, nil 313 } 314 315 // This is used for json templates to launch the build plugins. 316 // They will be prepared via b.Prepare() later. 317 func (c *Core) GetBuilds(opts GetBuildsOptions) ([]*CoreBuild, hcl.Diagnostics) { 318 buildNames := c.BuildNames(opts.Only, opts.Except) 319 builds := []*CoreBuild{} 320 diags := hcl.Diagnostics{} 321 for _, n := range buildNames { 322 b, err := c.Build(n) 323 if err != nil { 324 diags = append(diags, &hcl.Diagnostic{ 325 Severity: hcl.DiagError, 326 Summary: fmt.Sprintf("Failed to initialize build %q", n), 327 Detail: err.Error(), 328 }) 329 continue 330 } 331 332 // Now that build plugin has been launched, call Prepare() 333 log.Printf("Preparing build: %s", b.Name()) 334 b.SetDebug(opts.Debug) 335 b.SetForce(opts.Force) 336 b.SetOnError(opts.OnError) 337 338 warnings, err := b.Prepare() 339 if err != nil { 340 diags = append(diags, &hcl.Diagnostic{ 341 Severity: hcl.DiagError, 342 Summary: fmt.Sprintf("Failed to prepare build: %q", n), 343 Detail: err.Error(), 344 }) 345 continue 346 } 347 348 // Only append builds to list if the Prepare() is successful. 349 builds = append(builds, b) 350 351 if len(warnings) > 0 { 352 for _, warning := range warnings { 353 diags = append(diags, &hcl.Diagnostic{ 354 Severity: hcl.DiagWarning, 355 Summary: fmt.Sprintf("Warning when preparing build: %q", n), 356 Detail: warning, 357 }) 358 } 359 } 360 } 361 return builds, diags 362 } 363 364 // Build returns the Build object for the given name. 365 func (c *Core) Build(n string) (*CoreBuild, error) { 366 // Setup the builder 367 configBuilder, ok := c.builds[n] 368 if !ok { 369 return nil, fmt.Errorf("no such build found: %s", n) 370 } 371 // BuilderStore = config.Builders, gathered in loadConfig() in main.go 372 // For reference, the builtin BuilderStore is generated in 373 // packer/config.go in the Discover() func. 374 375 if !c.components.PluginConfig.Builders.Has(configBuilder.Type) { 376 err := fmt.Errorf( 377 "The builder %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ 378 "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ 379 "https://developer.hashicorp.com/packer/integrations?filter=%s", 380 configBuilder.Type, 381 strings.Split(configBuilder.Type, "-")[0], 382 ) 383 384 if sugg := didyoumean.NameSuggestion(configBuilder.Type, c.components.PluginConfig.Builders.List()); sugg != "" { 385 err = fmt.Errorf("Did you mean to use %q?", sugg) 386 } 387 388 return nil, err 389 } 390 391 // the Start command launches the builder plugin of the given type without 392 // calling Prepare() or passing any build-specific details. 393 builder, err := c.components.PluginConfig.Builders.Start(configBuilder.Type) 394 if err != nil { 395 return nil, fmt.Errorf( 396 "error initializing builder '%s': %s", 397 configBuilder.Type, err) 398 } 399 if builder == nil { 400 return nil, fmt.Errorf( 401 "builder type not found: %s", configBuilder.Type) 402 } 403 404 // rawName is the uninterpolated name that we use for various lookups 405 rawName := configBuilder.Name 406 407 // Setup the provisioners for this build 408 provisioners := make([]CoreBuildProvisioner, 0, len(c.Template.Provisioners)) 409 for _, rawP := range c.Template.Provisioners { 410 // If we're skipping this, then ignore it 411 if rawP.OnlyExcept.Skip(rawName) { 412 continue 413 } 414 cbp, err := c.generateCoreBuildProvisioner(rawP, rawName) 415 if err != nil { 416 return nil, err 417 } 418 419 provisioners = append(provisioners, cbp) 420 } 421 422 var cleanupProvisioner CoreBuildProvisioner 423 if c.Template.CleanupProvisioner != nil { 424 // This is a special instantiation of the shell-local provisioner that 425 // is only run on error at end of provisioning step before other step 426 // cleanup occurs. 427 cleanupProvisioner, err = c.generateCoreBuildProvisioner(c.Template.CleanupProvisioner, rawName) 428 if err != nil { 429 return nil, err 430 } 431 } 432 433 // Setup the post-processors 434 postProcessors := make([][]CoreBuildPostProcessor, 0, len(c.Template.PostProcessors)) 435 for _, rawPs := range c.Template.PostProcessors { 436 current := make([]CoreBuildPostProcessor, 0, len(rawPs)) 437 for _, rawP := range rawPs { 438 if rawP.Skip(rawName) { 439 continue 440 } 441 // -except skips post-processor & build 442 foundExcept := false 443 for _, except := range c.except { 444 if except != "" && except == rawP.Name { 445 foundExcept = true 446 } 447 } 448 if foundExcept { 449 break 450 } 451 452 if !c.components.PluginConfig.PostProcessors.Has(rawP.Type) { 453 err := fmt.Errorf( 454 "The post-processor %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ 455 "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ 456 "https://developer.hashicorp.com/packer/integrations?filter=%s", 457 rawP.Type, 458 strings.Split(rawP.Type, "-")[0], 459 ) 460 461 if sugg := didyoumean.NameSuggestion(rawP.Type, c.components.PluginConfig.PostProcessors.List()); sugg != "" { 462 err = fmt.Errorf("Did you mean to use %q?", sugg) 463 } 464 465 return nil, err 466 } 467 468 // Get the post-processor 469 postProcessor, err := c.components.PluginConfig.PostProcessors.Start(rawP.Type) 470 if err != nil { 471 return nil, fmt.Errorf( 472 "error initializing post-processor '%s': %s", 473 rawP.Type, err) 474 } 475 if postProcessor == nil { 476 return nil, fmt.Errorf( 477 "post-processor type not found: %s", rawP.Type) 478 } 479 480 current = append(current, CoreBuildPostProcessor{ 481 PostProcessor: postProcessor, 482 PType: rawP.Type, 483 PName: rawP.Name, 484 config: rawP.Config, 485 KeepInputArtifact: rawP.KeepInputArtifact, 486 }) 487 } 488 489 // If we have no post-processors in this chain, just continue. 490 if len(current) == 0 { 491 continue 492 } 493 494 postProcessors = append(postProcessors, current) 495 } 496 497 var sensitiveVars []string 498 for _, sensitive := range c.Template.SensitiveVariables { 499 sensitiveVars = append(sensitiveVars, sensitive.Key) 500 } 501 502 // TODO hooks one day 503 504 // Return a structure that contains the plugins, their types, variables, and 505 // the raw builder config loaded from the json template 506 cb := &CoreBuild{ 507 Type: n, 508 Builder: builder, 509 BuilderConfig: configBuilder.Config, 510 BuilderType: configBuilder.Type, 511 PostProcessors: postProcessors, 512 Provisioners: provisioners, 513 CleanupProvisioner: cleanupProvisioner, 514 TemplatePath: c.Template.Path, 515 Variables: c.variables, 516 SensitiveVars: sensitiveVars, 517 } 518 519 //configBuilder.Name is left uninterpolated so we must check against 520 // the interpolated name. 521 if configBuilder.Type != configBuilder.Name { 522 cb.BuildName = configBuilder.Type 523 } 524 525 return cb, nil 526 } 527 528 // Context returns an interpolation context. 529 func (c *Core) Context() *interpolate.Context { 530 return &interpolate.Context{ 531 TemplatePath: c.Template.Path, 532 UserVariables: c.variables, 533 CorePackerVersionString: packerversion.FormattedVersion(), 534 } 535 } 536 537 var ConsoleHelp = strings.TrimSpace(` 538 Packer console JSON Mode. 539 The Packer console allows you to experiment with Packer interpolations. 540 You may access variables in the Packer config you called the console with. 541 542 Type in the interpolation to test and hit <enter> to see the result. 543 544 "variables" will dump all available variables and their values. 545 546 "{{timestamp}}" will output the timestamp, for example "1559855090". 547 548 To exit the console, type "exit" and hit <enter>, or use Control-C. 549 550 /!\ If you would like to start console in hcl2 mode without a config you can 551 use the --config-type=hcl2 option. 552 `) 553 554 func (c *Core) EvaluateExpression(line string) (string, bool, hcl.Diagnostics) { 555 switch { 556 case line == "": 557 return "", false, nil 558 case line == "exit": 559 return "", true, nil 560 case line == "help": 561 return ConsoleHelp, false, nil 562 case line == "variables": 563 varsstring := "\n" 564 for k, v := range c.Context().UserVariables { 565 varsstring += fmt.Sprintf("%s: %+v,\n", k, v) 566 } 567 568 return varsstring, false, nil 569 default: 570 ctx := c.Context() 571 rendered, err := interpolate.Render(line, ctx) 572 var diags hcl.Diagnostics 573 if err != nil { 574 diags = append(diags, &hcl.Diagnostic{ 575 Summary: "Interpolation error", 576 Detail: err.Error(), 577 }) 578 } 579 return rendered, false, diags 580 } 581 } 582 583 func (c *Core) InspectConfig(opts InspectConfigOptions) int { 584 585 // Convenience... 586 ui := opts.Ui 587 tpl := c.Template 588 ui.Say("Packer Inspect: JSON mode") 589 590 // Description 591 if tpl.Description != "" { 592 ui.Say("Description:\n") 593 ui.Say(tpl.Description + "\n") 594 } 595 596 // Variables 597 if len(tpl.Variables) == 0 { 598 ui.Say("Variables:\n") 599 ui.Say(" <No variables>") 600 } else { 601 requiredHeader := false 602 for k, v := range tpl.Variables { 603 for _, sensitive := range tpl.SensitiveVariables { 604 if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { 605 v.Default = "<sensitive>" 606 } 607 } 608 if v.Required { 609 if !requiredHeader { 610 requiredHeader = true 611 ui.Say("Required variables:\n") 612 } 613 614 ui.Machine("template-variable", k, v.Default, "1") 615 ui.Say(" " + k) 616 } 617 } 618 619 if requiredHeader { 620 ui.Say("") 621 } 622 623 ui.Say("Optional variables and their defaults:\n") 624 keys := make([]string, 0, len(tpl.Variables)) 625 max := 0 626 for k := range tpl.Variables { 627 keys = append(keys, k) 628 if len(k) > max { 629 max = len(k) 630 } 631 } 632 633 sort.Strings(keys) 634 635 for _, k := range keys { 636 v := tpl.Variables[k] 637 if v.Required { 638 continue 639 } 640 for _, sensitive := range tpl.SensitiveVariables { 641 if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { 642 v.Default = "<sensitive>" 643 } 644 } 645 646 padding := strings.Repeat(" ", max-len(k)) 647 output := fmt.Sprintf(" %s%s = %s", k, padding, v.Default) 648 649 ui.Machine("template-variable", k, v.Default, "0") 650 ui.Say(output) 651 } 652 } 653 654 ui.Say("") 655 656 // Builders 657 ui.Say("Builders:\n") 658 if len(tpl.Builders) == 0 { 659 ui.Say(" <No builders>") 660 } else { 661 keys := make([]string, 0, len(tpl.Builders)) 662 max := 0 663 for k := range tpl.Builders { 664 keys = append(keys, k) 665 if len(k) > max { 666 max = len(k) 667 } 668 } 669 670 sort.Strings(keys) 671 672 for _, k := range keys { 673 v := tpl.Builders[k] 674 padding := strings.Repeat(" ", max-len(k)) 675 output := fmt.Sprintf(" %s%s", k, padding) 676 if v.Name != v.Type { 677 output = fmt.Sprintf("%s (%s)", output, v.Type) 678 } 679 680 ui.Machine("template-builder", k, v.Type) 681 ui.Say(output) 682 683 } 684 } 685 686 ui.Say("") 687 688 // Provisioners 689 ui.Say("Provisioners:\n") 690 if len(tpl.Provisioners) == 0 { 691 ui.Say(" <No provisioners>") 692 } else { 693 for _, v := range tpl.Provisioners { 694 ui.Machine("template-provisioner", v.Type) 695 ui.Say(fmt.Sprintf(" %s", v.Type)) 696 } 697 } 698 699 ui.Say("\nNote: If your build names contain user variables or template\n" + 700 "functions such as 'timestamp', these are processed at build time,\n" + 701 "and therefore only show in their raw form here.") 702 703 return 0 704 } 705 706 func (c *Core) FixConfig(opts FixConfigOptions) hcl.Diagnostics { 707 var diags hcl.Diagnostics 708 709 // Remove once we have support for the Inplace FixConfigMode 710 if opts.Mode != Diff { 711 diags = append(diags, &hcl.Diagnostic{ 712 Severity: hcl.DiagError, 713 Summary: fmt.Sprintf("FixConfig only supports template diff; FixConfigMode %d not supported", opts.Mode), 714 }) 715 716 return diags 717 } 718 719 var rawTemplateData map[string]interface{} 720 input := make(map[string]interface{}) 721 templateData := make(map[string]interface{}) 722 if err := json.Unmarshal(c.Template.RawContents, &rawTemplateData); err != nil { 723 diags = append(diags, &hcl.Diagnostic{ 724 Severity: hcl.DiagError, 725 Summary: fmt.Sprintf("unable to read the contents of the JSON configuration file: %s", err), 726 Detail: err.Error(), 727 }) 728 return diags 729 } 730 // Hold off on Diff for now - need to think about displaying to user. 731 // delete empty top-level keys since the fixers seem to add them 732 // willy-nilly 733 for k := range input { 734 ml, ok := input[k].([]map[string]interface{}) 735 if !ok { 736 continue 737 } 738 if len(ml) == 0 { 739 delete(input, k) 740 } 741 } 742 // marshal/unmarshal to make comparable to templateData 743 var fixedData map[string]interface{} 744 // Guaranteed to be valid json, so we can ignore errors 745 j, _ := json.Marshal(input) 746 if err := json.Unmarshal(j, &fixedData); err != nil { 747 diags = append(diags, &hcl.Diagnostic{ 748 Severity: hcl.DiagError, 749 Summary: fmt.Sprintf("unable to read the contents of the JSON configuration file: %s", err), 750 Detail: err.Error(), 751 }) 752 753 return diags 754 } 755 756 if diff := cmp.Diff(templateData, fixedData); diff != "" { 757 diags = append(diags, &hcl.Diagnostic{ 758 Severity: hcl.DiagError, 759 Summary: "Fixable configuration found.\nPlease run `packer fix` to get your build to run correctly.\nSee debug log for more information.", 760 Detail: diff, 761 }) 762 } 763 return diags 764 } 765 766 // validate does a full validation of the template. 767 // 768 // This will automatically call template.validate() in addition to doing 769 // richer semantic checks around variables and so on. 770 func (c *Core) validate() error { 771 // First validate the template in general, we can't do anything else 772 // unless the template itself is valid. 773 if err := c.Template.Validate(); err != nil { 774 return err 775 } 776 777 // Validate the minimum version is satisfied 778 if c.Template.MinVersion != "" { 779 versionActual, err := version.NewVersion(c.version) 780 if err != nil { 781 // This shouldn't happen since we set it via the compiler 782 panic(err) 783 } 784 785 versionMin, err := version.NewVersion(c.Template.MinVersion) 786 if err != nil { 787 return fmt.Errorf( 788 "min_version is invalid: %s", err) 789 } 790 791 if versionActual.LessThan(versionMin) { 792 return fmt.Errorf( 793 "This template requires Packer version %s or higher; using %s", 794 versionMin, 795 versionActual) 796 } 797 } 798 799 // Validate variables are set 800 var err error 801 for n, v := range c.Template.Variables { 802 if v.Required { 803 if _, ok := c.variables[n]; !ok { 804 err = multierror.Append(err, fmt.Errorf( 805 "required variable not set: %s", n)) 806 } 807 } 808 } 809 810 // TODO: validate all builders exist 811 // TODO: ^^ provisioner 812 // TODO: ^^ post-processor 813 814 return err 815 } 816 817 func isDoneInterpolating(v string) (bool, error) { 818 // Check for whether the var contains any more references to `user`, wrapped 819 // in interpolation syntax. 820 filter := `{{\s*user\s*\x60.*\x60\s*}}` 821 matched, err := regexp.MatchString(filter, v) 822 if err != nil { 823 return false, fmt.Errorf("Can't tell if interpolation is done: %s", err) 824 } 825 if matched { 826 // not done interpolating; there's still a call to "user" in a template 827 // engine 828 return false, nil 829 } 830 // No more calls to "user" as a template engine, so we're done. 831 return true, nil 832 } 833 834 func (c *Core) renderVarsRecursively() (*interpolate.Context, error) { 835 ctx := c.Context() 836 ctx.EnableEnv = true 837 ctx.UserVariables = make(map[string]string) 838 shouldRetry := true 839 changed := false 840 failedInterpolation := "" 841 842 // Why this giant loop? User variables can be recursively defined. For 843 // example: 844 // "variables": { 845 // "foo": "bar", 846 // "baz": "{{user `foo`}}baz", 847 // "bang": "bang{{user `baz`}}" 848 // }, 849 // In this situation, we cannot guarantee that we've added "foo" to 850 // UserVariables before we try to interpolate "baz" the first time. We need 851 // to have the option to loop back over in order to add the properly 852 // interpolated "baz" to the UserVariables map. 853 // Likewise, we'd need to loop up to two times to properly add "bang", 854 // since that depends on "baz" being set, which depends on "foo" being set. 855 856 // We break out of the while loop either if all our variables have been 857 // interpolated or if after 100 loops we still haven't succeeded in 858 // interpolating them. Please don't actually nest your variables in 100 859 // layers of other variables. Please. 860 861 // c.Template.Variables is populated by variables defined within the Template 862 // itself 863 // c.variables is populated by variables read in from the command line and 864 // var-files. 865 // We need to read the keys from both, then loop over all of them to figure 866 // out the appropriate interpolations. 867 868 repeatMap := make(map[string]string) 869 allKeys := make([]string, 0) 870 871 // load in template variables 872 for k, v := range c.Template.Variables { 873 repeatMap[k] = v.Default 874 allKeys = append(allKeys, k) 875 } 876 877 // overwrite template variables with command-line-read variables 878 for k, v := range c.variables { 879 repeatMap[k] = v 880 allKeys = append(allKeys, k) 881 } 882 883 // sort map to force the following loop to be deterministic. 884 sort.Strings(allKeys) 885 type keyValue struct { 886 Key string 887 Value string 888 } 889 sortedMap := make([]keyValue, 0, len(repeatMap)) 890 for _, k := range allKeys { 891 sortedMap = append(sortedMap, keyValue{k, repeatMap[k]}) 892 } 893 894 // Regex to exclude any build function variable or template variable 895 // from interpolating earlier 896 // E.g.: {{ .HTTPIP }} won't interpolate now 897 renderFilter := "{{(\\s|)\\.(.*?)(\\s|)}}" 898 899 for i := 0; i < 100; i++ { 900 shouldRetry = false 901 changed = false 902 deleteKeys := []string{} 903 // First, loop over the variables in the template 904 for _, kv := range sortedMap { 905 // Interpolate the default 906 renderedV, err := interpolate.RenderRegex(kv.Value, ctx, renderFilter) 907 switch err := err.(type) { 908 case nil: 909 // We only get here if interpolation has succeeded, so something is 910 // different in this loop than in the last one. 911 changed = true 912 c.variables[kv.Key] = renderedV 913 ctx.UserVariables = c.variables 914 // Remove fully-interpolated variables from the map, and flag 915 // variables that still need interpolating for a repeat. 916 done, err := isDoneInterpolating(kv.Value) 917 if err != nil { 918 return ctx, err 919 } 920 if done { 921 deleteKeys = append(deleteKeys, kv.Key) 922 } else { 923 shouldRetry = true 924 } 925 case ttmp.ExecError: 926 if strings.Contains(err.Error(), interpolate.ErrVariableNotSetString) { 927 shouldRetry = true 928 failedInterpolation = fmt.Sprintf(`"%s": "%s"; error: %s`, kv.Key, kv.Value, err) 929 } else { 930 return ctx, err 931 } 932 default: 933 return ctx, fmt.Errorf( 934 // unexpected interpolation error: abort the run 935 "error interpolating default value for '%s': %s", 936 kv.Key, err) 937 } 938 } 939 if !shouldRetry { 940 break 941 } 942 943 // Clear completed vars from sortedMap before next loop. Do this one 944 // key at a time because the indices are gonna change ever time you 945 // delete from the map. 946 for _, k := range deleteKeys { 947 for ind, kv := range sortedMap { 948 if kv.Key == k { 949 sortedMap = append(sortedMap[:ind], sortedMap[ind+1:]...) 950 break 951 } 952 } 953 } 954 } 955 956 if !changed && shouldRetry { 957 return ctx, fmt.Errorf("Failed to interpolate %s: Please make sure that "+ 958 "the variable you're referencing has been defined; Packer treats "+ 959 "all variables used to interpolate other user variables as "+ 960 "required.", failedInterpolation) 961 } 962 963 return ctx, nil 964 } 965 966 func (c *Core) init() error { 967 if c.variables == nil { 968 c.variables = make(map[string]string) 969 } 970 // Go through the variables and interpolate the environment and 971 // user variables 972 ctx, err := c.renderVarsRecursively() 973 if err != nil { 974 return err 975 } 976 for _, v := range c.Template.SensitiveVariables { 977 secret := ctx.UserVariables[v.Key] 978 c.secrets = append(c.secrets, secret) 979 } 980 981 return nil 982 }