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  }