github.com/hashicorp/packer@v1.14.3/command/hcl2_upgrade.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"regexp"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	texttemplate "text/template"
    19  	"text/template/parse"
    20  
    21  	"github.com/hashicorp/go-multierror"
    22  	"github.com/hashicorp/hcl/v2/hclwrite"
    23  	hcl2shim "github.com/hashicorp/packer-plugin-sdk/hcl2helper"
    24  	"github.com/hashicorp/packer-plugin-sdk/template"
    25  	"github.com/hashicorp/packer/packer"
    26  	"github.com/mitchellh/mapstructure"
    27  	"github.com/posener/complete"
    28  	"github.com/zclconf/go-cty/cty"
    29  )
    30  
    31  const (
    32  	hcl2UpgradeFileHeader = `# This file was autogenerated by the 'packer hcl2_upgrade' command. We
    33  # recommend double checking that everything is correct before going forward. We
    34  # also recommend treating this file as disposable. The HCL2 blocks in this
    35  # file can be moved to other files. For example, the variable blocks could be
    36  # moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
    37  # suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
    38  # once they also need to be in the same folder. 'packer inspect folder/'
    39  # will describe to you what is in that folder.
    40  
    41  # Avoid mixing go templating calls ( for example ` + "```{{ upper(`string`) }}```" + ` )
    42  # and HCL2 calls (for example '${ var.string_value_example }' ). They won't be
    43  # executed together and the outcome will be unknown.
    44  `
    45  	inputVarHeader = `
    46  # All generated input variables will be of 'string' type as this is how Packer JSON
    47  # views them; you can change their type later on. Read the variables type
    48  # constraints documentation
    49  # https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.`
    50  	localsVarHeader = `
    51  # All locals variables are generated from variables that uses expressions
    52  # that are not allowed in HCL2 variables.
    53  # Read the documentation for locals blocks here:
    54  # https://www.packer.io/docs/templates/hcl_templates/blocks/locals`
    55  	packerBlockHeader = `
    56  # See https://www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
    57  `
    58  
    59  	sourcesHeader = `
    60  # source blocks are generated from your builders; a source can be referenced in
    61  # build blocks. A build block runs provisioner and post-processors on a
    62  # source. Read the documentation for source blocks here:
    63  # https://www.packer.io/docs/templates/hcl_templates/blocks/source`
    64  
    65  	buildHeader = `
    66  # a build block invokes sources and runs provisioning steps on them. The
    67  # documentation for build blocks can be found here:
    68  # https://www.packer.io/docs/templates/hcl_templates/blocks/build
    69  `
    70  
    71  	amazonAmiDataHeader = `
    72  # The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
    73  # from this block can be referenced in source and locals blocks.
    74  # Read the documentation for data blocks here:
    75  # https://www.packer.io/docs/templates/hcl_templates/blocks/data
    76  # Read the documentation for the Amazon AMI Data Source here:
    77  # https://www.packer.io/plugins/datasources/amazon/ami`
    78  
    79  	amazonSecretsManagerDataHeader = `
    80  # The amazon-secretsmanager data block is generated from your aws_secretsmanager template function; a data
    81  # from this block can be referenced in source and locals blocks.
    82  # Read the documentation for data blocks here:
    83  # https://www.packer.io/docs/templates/hcl_templates/blocks/data
    84  # Read the documentation for the Amazon Secrets Manager Data Source here:
    85  # https://www.packer.io/plugins/datasources/amazon/secretsmanager`
    86  )
    87  
    88  var (
    89  	amazonSecretsManagerMap = map[string]map[string]interface{}{}
    90  	localsVariableMap       = map[string]string{}
    91  	timestamp               = false
    92  	isotime                 = false
    93  	strftime                = false
    94  )
    95  
    96  // knownPlugins represent the HashiCorp maintained plugins the we can confidently
    97  // construct a required plugins block for.
    98  var knownPlugins = map[string]string{
    99  	"amazon":        "github.com/hashicorp/amazon",
   100  	"ansible":       "github.com/hashicorp/ansible",
   101  	"azure":         "github.com/hashicorp/azure",
   102  	"docker":        "github.com/hashicorp/docker",
   103  	"googlecompute": "github.com/hashicorp/googlecompute",
   104  	"qemu":          "github.com/hashicorp/qemu",
   105  	"vagrant":       "github.com/hashicorp/vagrant",
   106  	"virtualbox":    "github.com/hashicorp/virtualbox",
   107  	"vmware":        "github.com/hashicorp/vmware",
   108  	"vsphere":       "github.com/hashicorp/vsphere",
   109  }
   110  
   111  // unknownPluginName represents any plugin not in knownPlugins or bundled into Packer
   112  const unknownPluginName string = "unknown"
   113  
   114  type HCL2UpgradeCommand struct {
   115  	Meta
   116  }
   117  
   118  func (c *HCL2UpgradeCommand) Run(args []string) int {
   119  	ctx, cleanup := handleTermInterrupt(c.Ui)
   120  	defer cleanup()
   121  
   122  	cfg, ret := c.ParseArgs(args)
   123  	if ret != 0 {
   124  		return ret
   125  	}
   126  
   127  	return c.RunContext(ctx, cfg)
   128  }
   129  
   130  func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) {
   131  	var cfg HCL2UpgradeArgs
   132  	flags := c.Meta.FlagSet("hcl2_upgrade")
   133  	flags.Usage = func() { c.Ui.Say(c.Help()) }
   134  	cfg.AddFlagSets(flags)
   135  	if err := flags.Parse(args); err != nil {
   136  		return &cfg, 1
   137  	}
   138  	args = flags.Args()
   139  	if len(args) != 1 {
   140  		flags.Usage()
   141  		return &cfg, 1
   142  	}
   143  	cfg.Path = args[0]
   144  	if cfg.OutputFile == "" {
   145  		cfg.OutputFile = cfg.Path + ".pkr.hcl"
   146  	}
   147  	return &cfg, 0
   148  }
   149  
   150  type BlockParser interface {
   151  	Parse(*template.Template) error
   152  	Write(*bytes.Buffer)
   153  }
   154  
   155  func (c *HCL2UpgradeCommand) RunContext(_ context.Context, cla *HCL2UpgradeArgs) int {
   156  	var output io.Writer
   157  	if err := os.MkdirAll(filepath.Dir(cla.OutputFile), 0755); err != nil {
   158  		c.Ui.Error(fmt.Sprintf("Failed to create output directory: %v", err))
   159  		return 1
   160  	}
   161  	if f, err := os.Create(cla.OutputFile); err == nil {
   162  		output = f
   163  		defer f.Close()
   164  	} else {
   165  		c.Ui.Error(fmt.Sprintf("Failed to create output file: %v", err))
   166  		return 1
   167  	}
   168  
   169  	if cla.WithAnnotations {
   170  		if _, err := output.Write([]byte(hcl2UpgradeFileHeader)); err != nil {
   171  			c.Ui.Error(fmt.Sprintf("Failed to write to file: %v", err))
   172  			return 1
   173  		}
   174  	}
   175  
   176  	hdl, ret := c.GetConfigFromJSON(&cla.MetaArgs)
   177  	if ret != 0 {
   178  		c.Ui.Error("Failed to get config from JSON")
   179  		return 1
   180  	}
   181  
   182  	core := hdl.(*packer.Core)
   183  	if err := core.Initialize(packer.InitializeOptions{
   184  		// Note: this is always true here as the DAG is only usable for
   185  		// HCL2 configs, so since the command only works on JSON templates,
   186  		// we can safely use the phased approach, which changes nothing.
   187  		UseSequential: true,
   188  	}); err != nil {
   189  		c.Ui.Error(fmt.Sprintf("Ignoring following initialization error: %v", err))
   190  	}
   191  	tpl := core.Template
   192  
   193  	// Parse blocks
   194  	packerBlock := &PackerParser{
   195  		WithAnnotations: cla.WithAnnotations,
   196  	}
   197  	if err := packerBlock.Parse(tpl); err != nil {
   198  		c.Ui.Error(fmt.Sprintf("Ignoring following Parse error: %v", err))
   199  		ret = 1
   200  	}
   201  
   202  	variables := &VariableParser{
   203  		WithAnnotations: cla.WithAnnotations,
   204  	}
   205  	if err := variables.Parse(tpl); err != nil {
   206  		c.Ui.Error(fmt.Sprintf("Ignoring following variables.Parse error: %v", err))
   207  		ret = 1
   208  	}
   209  
   210  	locals := &LocalsParser{
   211  		LocalsOut:       variables.localsOut,
   212  		WithAnnotations: cla.WithAnnotations,
   213  	}
   214  	if err := locals.Parse(tpl); err != nil {
   215  		c.Ui.Error(fmt.Sprintf("Ignoring following locals.Parse error: %v", err))
   216  		ret = 1
   217  	}
   218  
   219  	builders := []*template.Builder{}
   220  	{
   221  		// sort builders to avoid map's randomness
   222  		for _, builder := range tpl.Builders {
   223  			builders = append(builders, builder)
   224  		}
   225  	}
   226  	sort.Slice(builders, func(i, j int) bool {
   227  		return builders[i].Type+builders[i].Name < builders[j].Type+builders[j].Name
   228  	})
   229  
   230  	amazonAmiDatasource := &AmazonAmiDatasourceParser{
   231  		Builders:        builders,
   232  		WithAnnotations: cla.WithAnnotations,
   233  	}
   234  	if err := amazonAmiDatasource.Parse(tpl); err != nil {
   235  		c.Ui.Error(fmt.Sprintf("Ignoring following amazonAmiDatasource.Parse error: %v", err))
   236  		ret = 1
   237  	}
   238  
   239  	sources := &SourceParser{
   240  		Builders:        builders,
   241  		BuilderPlugins:  c.Meta.CoreConfig.Components.PluginConfig.Builders,
   242  		WithAnnotations: cla.WithAnnotations,
   243  	}
   244  	if err := sources.Parse(tpl); err != nil {
   245  		c.Ui.Error(fmt.Sprintf("Ignoring following sources.Parse error: %v", err))
   246  		ret = 1
   247  	}
   248  
   249  	build := &BuildParser{
   250  		Builders:        builders,
   251  		WithAnnotations: cla.WithAnnotations,
   252  	}
   253  	if err := build.Parse(tpl); err != nil {
   254  		c.Ui.Error(fmt.Sprintf("Ignoring following build.Parse error: %v", err))
   255  		ret = 1
   256  	}
   257  
   258  	amazonSecretsDatasource := &AmazonSecretsDatasourceParser{
   259  		WithAnnotations: cla.WithAnnotations,
   260  	}
   261  	if err := amazonSecretsDatasource.Parse(tpl); err != nil {
   262  		c.Ui.Error(fmt.Sprintf("Ignoring following amazonSecretsDatasource.Parse error: %v", err))
   263  		ret = 1
   264  	}
   265  
   266  	// Write file
   267  	out := &bytes.Buffer{}
   268  	for _, block := range []BlockParser{
   269  		packerBlock,
   270  		variables,
   271  		amazonSecretsDatasource,
   272  		amazonAmiDatasource,
   273  		locals,
   274  		sources,
   275  		build,
   276  	} {
   277  		block.Write(out)
   278  	}
   279  
   280  	if _, err := output.Write(hclwrite.Format(out.Bytes())); err != nil {
   281  		c.Ui.Error(fmt.Sprintf("Failed to write to file: %v", err))
   282  		return 1
   283  	}
   284  
   285  	c.Ui.Say(fmt.Sprintf("Successfully created %s. Exit %d", cla.OutputFile, ret))
   286  	return ret
   287  }
   288  
   289  type UnhandleableArgumentError struct {
   290  	Call           string
   291  	Correspondance string
   292  	Docs           string
   293  }
   294  
   295  func (uc UnhandleableArgumentError) Error() string {
   296  	return fmt.Sprintf(`unhandled %q call:
   297  # there is no way to automatically upgrade the %[1]q call.
   298  # Please manually upgrade to %s
   299  # Visit %s for more infos.`, uc.Call, uc.Correspondance, uc.Docs)
   300  }
   301  
   302  func fallbackReturn(err error, s []byte) []byte {
   303  	if strings.Contains(err.Error(), "unhandled") {
   304  		return append([]byte(fmt.Sprintf("\n# %s\n", err)), s...)
   305  	}
   306  
   307  	return append([]byte(fmt.Sprintf("\n# could not parse template for following block: %q\n", err)), s...)
   308  }
   309  
   310  // reTemplate writes a new template to `out` and escapes all unknown variables
   311  // so that we don't interpret them later on when interpreting the template
   312  func reTemplate(nd parse.Node, out io.Writer, funcs texttemplate.FuncMap) error {
   313  	switch node := nd.(type) {
   314  	case *parse.ActionNode:
   315  		// Leave pipes as-is
   316  		if len(node.Pipe.Cmds) > 1 {
   317  			fmt.Fprintf(out, "%s", node.String())
   318  			return nil
   319  		}
   320  
   321  		cmd := node.Pipe.Cmds[0]
   322  		args := cmd.Args
   323  		if len(args) > 1 {
   324  			// Function calls with parameters are left aside
   325  			fmt.Fprintf(out, "%s", node.String())
   326  			return nil
   327  		}
   328  
   329  		_, ok := funcs[args[0].String()]
   330  		if ok {
   331  			// Known functions left as-is
   332  			fmt.Fprintf(out, "%s", node.String())
   333  			return nil
   334  		}
   335  
   336  		// Escape anything that isn't in the func map
   337  		fmt.Fprintf(out, "{{ \"{{\" }} %s {{ \"}}\" }}", cmd.String())
   338  
   339  	// TODO maybe node.Pipe.Decls? Though in Packer templates they're not
   340  	// supported officially so they can be left aside for now
   341  	case *parse.ListNode:
   342  		for _, child := range node.Nodes {
   343  			err := reTemplate(child, out, funcs)
   344  			if err != nil {
   345  				return err
   346  			}
   347  		}
   348  	case *parse.TextNode:
   349  		_, err := fmt.Fprintf(out, "%s", node.Text)
   350  		if err != nil {
   351  			return err
   352  		}
   353  	default:
   354  		return fmt.Errorf("unhandled node type %s", reflect.TypeOf(nd))
   355  	}
   356  	return nil
   357  }
   358  
   359  // transposeTemplatingCalls executes parts of blocks as go template files and replaces
   360  // their result with their hcl2 variant. If something goes wrong the template
   361  // containing the go template string is returned.
   362  func transposeTemplatingCalls(s []byte) []byte {
   363  	funcErrors := &multierror.Error{
   364  		ErrorFormat: func(es []error) string {
   365  			if len(es) == 1 {
   366  				return fmt.Sprintf("# 1 error occurred upgrading the following block:\n\t# %s\n", es[0])
   367  			}
   368  
   369  			points := make([]string, len(es))
   370  			for i, err := range es {
   371  				if i == len(es)-1 {
   372  					points[i] = fmt.Sprintf("# %s", err)
   373  					continue
   374  				}
   375  				points[i] = fmt.Sprintf("# %s\n", err)
   376  			}
   377  
   378  			return fmt.Sprintf(
   379  				"# %d errors occurred upgrading the following block:\n\t%s",
   380  				len(es), strings.Join(points, "\n\t"))
   381  		},
   382  	}
   383  
   384  	funcMap := texttemplate.FuncMap{
   385  		"aws_secretsmanager": func(a ...string) string {
   386  			if len(a) == 2 {
   387  				for key, config := range amazonSecretsManagerMap {
   388  					nameOk := config["name"] == a[0]
   389  					keyOk := config["key"] == a[1]
   390  					if nameOk && keyOk {
   391  						return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", key)
   392  					}
   393  				}
   394  				id := fmt.Sprintf("autogenerated_%d", len(amazonSecretsManagerMap)+1)
   395  				amazonSecretsManagerMap[id] = map[string]interface{}{
   396  					"name": a[0],
   397  					"key":  a[1],
   398  				}
   399  				return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", id)
   400  			}
   401  			for key, config := range amazonSecretsManagerMap {
   402  				nameOk := config["name"] == a[0]
   403  				if nameOk {
   404  					return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", key)
   405  				}
   406  			}
   407  			id := fmt.Sprintf("autogenerated_%d", len(amazonSecretsManagerMap)+1)
   408  			amazonSecretsManagerMap[id] = map[string]interface{}{
   409  				"name": a[0],
   410  			}
   411  			return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", id)
   412  		},
   413  		"timestamp": func() string {
   414  			timestamp = true
   415  			return "${local.timestamp}"
   416  		},
   417  		"isotime": func(a ...string) string {
   418  			if len(a) == 0 {
   419  				// returns rfc3339 formatted string.
   420  				return "${timestamp()}"
   421  			}
   422  			// otherwise a valid isotime func has one input.
   423  			isotime = true
   424  			return fmt.Sprintf("${legacy_isotime(\"%s\")}", a[0])
   425  
   426  		},
   427  		"strftime": func(a ...string) string {
   428  			if len(a) == 0 {
   429  				// returns rfc3339 formatted string.
   430  				return "${timestamp()}"
   431  			}
   432  			strftime = true
   433  			return fmt.Sprintf("${legacy_strftime(\"%s\")}", a[0])
   434  		},
   435  		"user": func(in string) string {
   436  			if _, ok := localsVariableMap[in]; ok {
   437  				// variable is now a local
   438  				return fmt.Sprintf("${local.%s}", in)
   439  			}
   440  			return fmt.Sprintf("${var.%s}", in)
   441  		},
   442  		"env": func(in string) string {
   443  			return fmt.Sprintf("${env(%q)}", in)
   444  		},
   445  		"build": func(a string) string {
   446  			return fmt.Sprintf("${build.%s}", a)
   447  		},
   448  		"data": func(a string) string {
   449  			return fmt.Sprintf("${data.%s}", a)
   450  		},
   451  		"template_dir": func() string {
   452  			return "${path.root}"
   453  		},
   454  		"pwd": func() string {
   455  			return "${path.cwd}"
   456  		},
   457  		"packer_version": func() string {
   458  			return "${packer.version}"
   459  		},
   460  		"uuid": func() string {
   461  			return "${uuidv4()}"
   462  		},
   463  		"lower": func(a string) (string, error) {
   464  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   465  				"lower",
   466  				"`lower(var.example)`",
   467  				"https://www.packer.io/docs/templates/hcl_templates/functions/string/lower",
   468  			})
   469  			return fmt.Sprintf("{{ lower `%s` }}", a), nil
   470  		},
   471  		"upper": func(a string) (string, error) {
   472  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   473  				"upper",
   474  				"`upper(var.example)`",
   475  				"https://www.packer.io/docs/templates/hcl_templates/functions/string/upper",
   476  			})
   477  			return fmt.Sprintf("{{ upper `%s` }}", a), nil
   478  		},
   479  		"split": func(a, b string, n int) (string, error) {
   480  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   481  				"split",
   482  				"`split(separator, string)`",
   483  				"https://www.packer.io/docs/templates/hcl_templates/functions/string/split",
   484  			})
   485  			return fmt.Sprintf("{{ split `%s` `%s` %d }}", a, b, n), nil
   486  		},
   487  		"replace": func(a, b string, n int, c string) (string, error) {
   488  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   489  				"replace",
   490  				"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`",
   491  				"https://www.packer.io/docs/templates/hcl_templates/functions/string/replace or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace",
   492  			})
   493  			return fmt.Sprintf("{{ replace `%s` `%s` `%s` %d }}", a, b, c, n), nil
   494  		},
   495  		"replace_all": func(a, b, c string) (string, error) {
   496  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   497  				"replace_all",
   498  				"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`",
   499  				"https://www.packer.io/docs/templates/hcl_templates/functions/string/replace or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace",
   500  			})
   501  			return fmt.Sprintf("{{ replace_all `%s` `%s` `%s` }}", a, b, c), nil
   502  		},
   503  		"clean_resource_name": func(a string) (string, error) {
   504  			funcErrors = multierror.Append(funcErrors, UnhandleableArgumentError{
   505  				"clean_resource_name",
   506  				"use custom validation rules, `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`",
   507  				"https://packer.io/docs/templates/hcl_templates/variables#custom-validation-rules" +
   508  					" , https://www.packer.io/docs/templates/hcl_templates/functions/string/replace" +
   509  					" or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace",
   510  			})
   511  			return fmt.Sprintf("{{ clean_resource_name `%s` }}", a), nil
   512  		},
   513  		"build_name": func() string {
   514  			return "${build.name}"
   515  		},
   516  		"build_type": func() string {
   517  			return "${build.type}"
   518  		},
   519  	}
   520  
   521  	tpl, err := texttemplate.New("hcl2_upgrade").
   522  		Funcs(funcMap).
   523  		Parse(string(s))
   524  
   525  	if err != nil {
   526  		if strings.Contains(err.Error(), "unexpected \"\\\\\" in operand") {
   527  			// This error occurs if the operand in the text template used
   528  			// escaped quoting \" instead of bactick quoting `
   529  			// Create a regex to do a string replace on this block, to fix
   530  			// quoting.
   531  			q := fixQuoting(string(s))
   532  			unquoted := []byte(q)
   533  			tpl, err = texttemplate.New("hcl2_upgrade").
   534  				Funcs(funcMap).
   535  				Parse(string(unquoted))
   536  			if err != nil {
   537  				return fallbackReturn(err, unquoted)
   538  			}
   539  		} else {
   540  			return fallbackReturn(err, s)
   541  		}
   542  	}
   543  
   544  	retempl := &bytes.Buffer{}
   545  	if err := reTemplate(tpl.Root, retempl, funcMap); err != nil {
   546  		return fallbackReturn(err, s)
   547  	}
   548  
   549  	tpl, err = texttemplate.New("hcl2_upgrade").
   550  		Funcs(funcMap).
   551  		Parse(retempl.String())
   552  
   553  	str := &bytes.Buffer{}
   554  
   555  	if err := tpl.Execute(str, nil); err != nil {
   556  		return fallbackReturn(err, s)
   557  	}
   558  
   559  	out := str.Bytes()
   560  
   561  	if funcErrors.Len() > 0 {
   562  		return append([]byte(fmt.Sprintf("\n%s", funcErrors)), out...)
   563  	}
   564  	return out
   565  }
   566  
   567  // variableTransposeTemplatingCalls executes parts of blocks as go template files and replaces
   568  // their result with their hcl2 variant for variables block only. If something goes wrong the template
   569  // containing the go template string is returned.
   570  // In variableTransposeTemplatingCalls the definition of aws_secretsmanager function will create a data source
   571  // with the same name as the variable.
   572  func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) {
   573  	setIsLocal := func(a ...string) string {
   574  		isLocal = true
   575  		return ""
   576  	}
   577  
   578  	// Make locals from variables using valid template engine,
   579  	// expect the ones using only 'env'
   580  	// ref: https://www.packer.io/docs/templates/legacy_json_templates/engine#template-engine
   581  	funcMap := texttemplate.FuncMap{
   582  		"aws_secretsmanager": setIsLocal,
   583  		"timestamp":          setIsLocal,
   584  		"isotime":            setIsLocal,
   585  		"strftime":           setIsLocal,
   586  		"user":               setIsLocal,
   587  		"env": func(in string) string {
   588  			return fmt.Sprintf("${env(%q)}", in)
   589  		},
   590  		"template_dir":   setIsLocal,
   591  		"pwd":            setIsLocal,
   592  		"packer_version": setIsLocal,
   593  		"uuid":           setIsLocal,
   594  		"lower":          setIsLocal,
   595  		"upper":          setIsLocal,
   596  		"split": func(_, _ string, _ int) (string, error) {
   597  			isLocal = true
   598  			return "", nil
   599  		},
   600  		"replace": func(_, _ string, _ int, _ string) (string, error) {
   601  			isLocal = true
   602  			return "", nil
   603  		},
   604  		"replace_all": func(_, _, _ string) (string, error) {
   605  			isLocal = true
   606  			return "", nil
   607  		},
   608  	}
   609  
   610  	tpl, err := texttemplate.New("hcl2_upgrade").
   611  		Funcs(funcMap).
   612  		Parse(string(s))
   613  
   614  	if err != nil {
   615  		if strings.Contains(err.Error(), "unexpected \"\\\\\" in operand") {
   616  			// This error occurs if the operand in the text template used
   617  			// escaped quoting \" instead of bactick quoting `
   618  			// Create a regex to do a string replace on this block, to fix
   619  			// quoting.
   620  			q := fixQuoting(string(s))
   621  			unquoted := []byte(q)
   622  			tpl, err = texttemplate.New("hcl2_upgrade").
   623  				Funcs(funcMap).
   624  				Parse(string(unquoted))
   625  			if err != nil {
   626  				return isLocal, fallbackReturn(err, unquoted)
   627  			}
   628  		} else {
   629  			return isLocal, fallbackReturn(err, s)
   630  		}
   631  	}
   632  
   633  	retempl := &bytes.Buffer{}
   634  	if err := reTemplate(tpl.Root, retempl, funcMap); err != nil {
   635  		return isLocal, fallbackReturn(err, s)
   636  	}
   637  
   638  	tpl, err = texttemplate.New("hcl2_upgrade").
   639  		Funcs(funcMap).
   640  		Parse(retempl.String())
   641  
   642  	str := &bytes.Buffer{}
   643  	if err := tpl.Execute(str, nil); err != nil {
   644  		return isLocal, fallbackReturn(err, s)
   645  	}
   646  
   647  	return isLocal, str.Bytes()
   648  }
   649  
   650  // referencedUserVariables executes parts of blocks as go template files finding user variables referenced
   651  // within the template. This function should be called once to extract those variables referenced via the {{user `...`}}
   652  // template function. The resulting map will contain variables defined in the JSON variables property, and some that
   653  // are declared via var-files; to avoid duplicates the results of this function should be reconciled against tpl.Variables.
   654  func referencedUserVariables(s []byte) map[string]*template.Variable {
   655  	userVars := make([]string, 0)
   656  	funcMap := texttemplate.FuncMap{
   657  		"user": func(in string) string {
   658  			userVars = append(userVars, in)
   659  			return ""
   660  		},
   661  	}
   662  
   663  	tpl, err := texttemplate.New("hcl2_upgrade").
   664  		Funcs(funcMap).
   665  		Parse(string(s))
   666  	if err != nil {
   667  		return nil
   668  	}
   669  
   670  	if err := tpl.Execute(&bytes.Buffer{}, nil); err != nil {
   671  		return nil
   672  	}
   673  
   674  	vars := make(map[string]*template.Variable)
   675  	for _, v := range userVars {
   676  		vars[v] = &template.Variable{
   677  			Key:      v,
   678  			Required: true,
   679  		}
   680  	}
   681  	return vars
   682  }
   683  
   684  func jsonBodyToHCL2Body(out *hclwrite.Body, kvs map[string]interface{}) {
   685  	ks := []string{}
   686  	for k := range kvs {
   687  		ks = append(ks, k)
   688  	}
   689  	sort.Strings(ks)
   690  
   691  	for _, k := range ks {
   692  		value := kvs[k]
   693  
   694  		switch value := value.(type) {
   695  		case map[string]interface{}:
   696  			var mostComplexElem interface{}
   697  			for _, randomElem := range value {
   698  				if k == "linux_options" || k == "network_interface" || k == "shared_image_gallery" {
   699  					break
   700  				}
   701  				// HACK: we take the most complex element of that map because
   702  				// in HCL2, map of objects can be bodies, for example:
   703  				// map containing object: source_ami_filter {} ( body )
   704  				// simple string/string map: tags = {} ) ( attribute )
   705  				//
   706  				// if we could not find an object in this map then it's most
   707  				// likely a plain map and so we guess it should be and
   708  				// attribute. Though now if value refers to something that is
   709  				// an object but only contains a string or a bool; we could
   710  				// generate a faulty object. For example a (somewhat invalid)
   711  				// source_ami_filter where only `most_recent` is set.
   712  				switch randomElem.(type) {
   713  				case string, int, float64, bool:
   714  					if mostComplexElem != nil {
   715  						continue
   716  					}
   717  					mostComplexElem = randomElem
   718  				default:
   719  					mostComplexElem = randomElem
   720  				}
   721  			}
   722  
   723  			switch mostComplexElem.(type) {
   724  			case string, int, float64, bool:
   725  				out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
   726  			default:
   727  				nestedBlockBody := out.AppendNewBlock(k, nil).Body()
   728  				jsonBodyToHCL2Body(nestedBlockBody, value)
   729  			}
   730  		case map[string]string, map[string]int, map[string]float64:
   731  			out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
   732  		case []interface{}:
   733  			if len(value) == 0 {
   734  				continue
   735  			}
   736  
   737  			var mostComplexElem interface{}
   738  			for _, randomElem := range value {
   739  				// HACK: we take the most complex element of that slice because
   740  				// in hcl2 slices of plain types can be arrays, for example:
   741  				// simple string type: owners = ["0000000000"]
   742  				// object: launch_block_device_mappings {}
   743  				switch randomElem.(type) {
   744  				case string, int, float64, bool:
   745  					if mostComplexElem != nil {
   746  						continue
   747  					}
   748  					mostComplexElem = randomElem
   749  				default:
   750  					mostComplexElem = randomElem
   751  				}
   752  			}
   753  			switch mostComplexElem.(type) {
   754  			case map[string]interface{}:
   755  				// this is an object in a slice; so we unwrap it. We
   756  				// could try to remove any 's' suffix in the key, but
   757  				// this might not work everywhere.
   758  				for i := range value {
   759  					value := value[i].(map[string]interface{})
   760  					nestedBlockBody := out.AppendNewBlock(k, nil).Body()
   761  					jsonBodyToHCL2Body(nestedBlockBody, value)
   762  				}
   763  				continue
   764  			default:
   765  				out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
   766  			}
   767  		default:
   768  			out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
   769  		}
   770  	}
   771  }
   772  
   773  func isSensitiveVariable(key string, vars []*template.Variable) bool {
   774  	for _, v := range vars {
   775  		if v.Key == key {
   776  			return true
   777  		}
   778  	}
   779  	return false
   780  }
   781  
   782  func (*HCL2UpgradeCommand) Help() string {
   783  	helpText := `
   784  Usage: packer hcl2_upgrade [options] TEMPLATE
   785  
   786    Will transform your JSON template into an HCL2 configuration.
   787  
   788  Options:
   789  
   790    -output-file=path    Set output file name. By default this will be the
   791                         TEMPLATE name with ".pkr.hcl" appended to it. To be a
   792                         valid Packer HCL template, it must have the suffix
   793                         ".pkr.hcl"
   794    -with-annotations    Add helper annotation comments to the file to help new
   795                         HCL2 users understand the template format.
   796  `
   797  
   798  	return strings.TrimSpace(helpText)
   799  }
   800  
   801  func (*HCL2UpgradeCommand) Synopsis() string {
   802  	return "transform a JSON template into an HCL2 configuration"
   803  }
   804  
   805  func (*HCL2UpgradeCommand) AutocompleteArgs() complete.Predictor {
   806  	return complete.PredictNothing
   807  }
   808  
   809  func (*HCL2UpgradeCommand) AutocompleteFlags() complete.Flags {
   810  	return complete.Flags{}
   811  }
   812  
   813  // Specific blocks parser responsible to parse and write the block
   814  
   815  type PackerParser struct {
   816  	WithAnnotations bool
   817  	out             []byte
   818  }
   819  
   820  func (p *PackerParser) Parse(tpl *template.Template) error {
   821  	reqPlugins, err := p.generateRequiredPluginsBlock(tpl)
   822  	if err != nil {
   823  		return err
   824  	}
   825  
   826  	if tpl.MinVersion == "" && reqPlugins == nil {
   827  		return nil
   828  	}
   829  
   830  	fileContent := hclwrite.NewEmptyFile()
   831  	body := fileContent.Body()
   832  	packerBody := body.AppendNewBlock("packer", nil).Body()
   833  
   834  	if tpl.MinVersion != "" {
   835  		packerBody.SetAttributeValue("required_version", cty.StringVal(fmt.Sprintf(">= %s", tpl.MinVersion)))
   836  	}
   837  
   838  	if reqPlugins != nil {
   839  		packerBody.AppendBlock(reqPlugins)
   840  	}
   841  
   842  	p.out = fileContent.Bytes()
   843  
   844  	return nil
   845  }
   846  
   847  func gatherPluginsFromTemplate(tpl *template.Template) []string {
   848  	plugins := map[string]struct{}{}
   849  
   850  	for _, b := range tpl.Builders {
   851  		name := knownPluginComponent(b.Type)
   852  		if name == unknownPluginName {
   853  			continue
   854  		}
   855  		plugins[knownPlugins[name]] = struct{}{}
   856  	}
   857  
   858  	for _, p := range tpl.Provisioners {
   859  		name := knownPluginComponent(p.Type)
   860  		if name == unknownPluginName {
   861  			continue
   862  		}
   863  		plugins[knownPlugins[name]] = struct{}{}
   864  	}
   865  
   866  	for _, pps := range tpl.PostProcessors {
   867  		for _, pp := range pps {
   868  			name := knownPluginComponent(pp.Type)
   869  			if name == unknownPluginName {
   870  				continue
   871  			}
   872  			plugins[knownPlugins[name]] = struct{}{}
   873  		}
   874  	}
   875  
   876  	if len(plugins) == 0 {
   877  		return nil
   878  	}
   879  
   880  	retPlugins := make([]string, 0, len(plugins))
   881  	for plugin := range plugins {
   882  		retPlugins = append(retPlugins, plugin)
   883  	}
   884  
   885  	sort.Strings(retPlugins)
   886  
   887  	return retPlugins
   888  }
   889  
   890  func (p *PackerParser) generateRequiredPluginsBlock(tpl *template.Template) (*hclwrite.Block, error) {
   891  	plugins := gatherPluginsFromTemplate(tpl)
   892  	if len(plugins) == 0 {
   893  		return nil, nil
   894  	}
   895  
   896  	reqPlugins := hclwrite.NewBlock("required_plugins", nil)
   897  	for _, plugin := range plugins {
   898  		pluginBlock := cty.ObjectVal(map[string]cty.Value{
   899  			"source":  cty.StringVal(plugin),
   900  			"version": cty.StringVal("~> 1"),
   901  		})
   902  		reqPlugins.Body().SetAttributeValue(strings.Replace(plugin, "github.com/hashicorp/", "", 1), pluginBlock)
   903  	}
   904  
   905  	return reqPlugins, nil
   906  }
   907  
   908  func (p *PackerParser) Write(out *bytes.Buffer) {
   909  	if len(p.out) > 0 {
   910  		if p.WithAnnotations {
   911  			out.Write([]byte(packerBlockHeader))
   912  		}
   913  		out.Write(p.out)
   914  	}
   915  }
   916  
   917  type VariableParser struct {
   918  	WithAnnotations bool
   919  	variablesOut    []byte
   920  	localsOut       []byte
   921  }
   922  
   923  func makeLocal(variable *template.Variable, sensitive bool, localBody *hclwrite.Body, localsContent *hclwrite.File, hasLocals *bool) []byte {
   924  	if sensitive {
   925  		// Create Local block because this is sensitive
   926  		sensitiveLocalContent := hclwrite.NewEmptyFile()
   927  		body := sensitiveLocalContent.Body()
   928  		body.AppendNewline()
   929  		sensitiveLocalBody := body.AppendNewBlock("local", []string{variable.Key}).Body()
   930  		sensitiveLocalBody.SetAttributeValue("sensitive", cty.BoolVal(true))
   931  		sensitiveLocalBody.SetAttributeValue("expression", hcl2shim.HCL2ValueFromConfigValue(variable.Default))
   932  		localsVariableMap[variable.Key] = "local"
   933  		return sensitiveLocalContent.Bytes()
   934  	}
   935  	localBody.SetAttributeValue(variable.Key, hcl2shim.HCL2ValueFromConfigValue(variable.Default))
   936  	localsVariableMap[variable.Key] = "locals"
   937  	*hasLocals = true
   938  	return []byte{}
   939  }
   940  
   941  func makeVariable(variable *template.Variable, sensitive bool) []byte {
   942  	variablesContent := hclwrite.NewEmptyFile()
   943  	variablesBody := variablesContent.Body()
   944  	variablesBody.AppendNewline()
   945  	variableBody := variablesBody.AppendNewBlock("variable", []string{variable.Key}).Body()
   946  	variableBody.SetAttributeRaw("type", hclwrite.Tokens{&hclwrite.Token{Bytes: []byte("string")}})
   947  
   948  	if variable.Default != "" || !variable.Required {
   949  		shimmed := hcl2shim.HCL2ValueFromConfigValue(variable.Default)
   950  		variableBody.SetAttributeValue("default", shimmed)
   951  	}
   952  	if sensitive {
   953  		variableBody.SetAttributeValue("sensitive", cty.BoolVal(true))
   954  	}
   955  
   956  	return variablesContent.Bytes()
   957  }
   958  
   959  func (p *VariableParser) Parse(tpl *template.Template) error {
   960  	// Output Locals and Local blocks
   961  	localsContent := hclwrite.NewEmptyFile()
   962  	localsBody := localsContent.Body()
   963  	localsBody.AppendNewline()
   964  	localBody := localsBody.AppendNewBlock("locals", nil).Body()
   965  	hasLocals := false
   966  
   967  	if len(p.variablesOut) == 0 {
   968  		p.variablesOut = []byte{}
   969  	}
   970  	if len(p.localsOut) == 0 {
   971  		p.localsOut = []byte{}
   972  	}
   973  
   974  	if len(tpl.Variables) == 0 {
   975  		tpl.Variables = make(map[string]*template.Variable)
   976  	}
   977  	// JSON supports variable declaration via var-files.
   978  	// User variables that might be defined in a var-file
   979  	// but not in the actual JSON template should be accounted for.
   980  	userVars := referencedUserVariables(tpl.RawContents)
   981  	for name, variable := range userVars {
   982  		if _, ok := tpl.Variables[name]; ok {
   983  			continue
   984  		}
   985  		tpl.Variables[name] = variable
   986  	}
   987  
   988  	variables := []*template.Variable{}
   989  	{
   990  		// sort variables to avoid map's randomness
   991  		for _, variable := range tpl.Variables {
   992  			variables = append(variables, variable)
   993  		}
   994  		sort.Slice(variables, func(i, j int) bool {
   995  			return variables[i].Key < variables[j].Key
   996  		})
   997  	}
   998  
   999  	for _, variable := range variables {
  1000  		// Create new HCL2 "variables" block, and populate the "value"
  1001  		// field with the "Default" value from the JSON variable.
  1002  
  1003  		// Interpolate Jsonval first as an hcl variable to determine if it is
  1004  		// a local. Variables referencing some form of variable expression must be defined as a local in HCL2,
  1005  		// as variables in HCL2 must have a known value at parsing time.
  1006  		isLocal, _ := variableTransposeTemplatingCalls([]byte(variable.Default))
  1007  		sensitive := false
  1008  		if isSensitiveVariable(variable.Key, tpl.SensitiveVariables) {
  1009  			sensitive = true
  1010  		}
  1011  		// Create final HCL block and append.
  1012  		if isLocal {
  1013  			sensitiveBlocks := makeLocal(variable, sensitive, localBody, localsContent, &hasLocals)
  1014  			if len(sensitiveBlocks) > 0 {
  1015  				p.localsOut = append(p.localsOut, transposeTemplatingCalls(sensitiveBlocks)...)
  1016  			}
  1017  			continue
  1018  		}
  1019  		varbytes := makeVariable(variable, sensitive)
  1020  		_, out := variableTransposeTemplatingCalls(varbytes)
  1021  		p.variablesOut = append(p.variablesOut, out...)
  1022  	}
  1023  
  1024  	if hasLocals == true {
  1025  		p.localsOut = append(p.localsOut, transposeTemplatingCalls(localsContent.Bytes())...)
  1026  	}
  1027  
  1028  	return nil
  1029  }
  1030  
  1031  func (p *VariableParser) Write(out *bytes.Buffer) {
  1032  	if len(p.variablesOut) > 0 {
  1033  		if p.WithAnnotations {
  1034  			out.Write([]byte(inputVarHeader))
  1035  		}
  1036  		out.Write(p.variablesOut)
  1037  	}
  1038  }
  1039  
  1040  type LocalsParser struct {
  1041  	WithAnnotations bool
  1042  	LocalsOut       []byte
  1043  }
  1044  
  1045  func (p *LocalsParser) Parse(tpl *template.Template) error {
  1046  	// Locals where parsed with Variables
  1047  	return nil
  1048  }
  1049  
  1050  func (p *LocalsParser) Write(out *bytes.Buffer) {
  1051  	if timestamp {
  1052  		_, _ = out.Write([]byte("\n"))
  1053  		if p.WithAnnotations {
  1054  			fmt.Fprintln(out, `# "timestamp" template function replacement`)
  1055  		}
  1056  		fmt.Fprintln(out, `locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }`)
  1057  	}
  1058  	if isotime {
  1059  		fmt.Fprintln(out, `# The "legacy_isotime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions.`)
  1060  	}
  1061  	if strftime {
  1062  		fmt.Fprintln(out, `# The "legacy_strftime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions.`)
  1063  	}
  1064  	if len(p.LocalsOut) > 0 {
  1065  		if p.WithAnnotations {
  1066  			out.Write([]byte(localsVarHeader))
  1067  		}
  1068  		out.Write(p.LocalsOut)
  1069  	}
  1070  }
  1071  
  1072  type AmazonSecretsDatasourceParser struct {
  1073  	WithAnnotations bool
  1074  	out             []byte
  1075  }
  1076  
  1077  func (p *AmazonSecretsDatasourceParser) Parse(_ *template.Template) error {
  1078  	if p.out == nil {
  1079  		p.out = []byte{}
  1080  	}
  1081  
  1082  	keys := make([]string, 0, len(amazonSecretsManagerMap))
  1083  	for k := range amazonSecretsManagerMap {
  1084  		keys = append(keys, k)
  1085  	}
  1086  	sort.Strings(keys)
  1087  
  1088  	for _, dataSourceName := range keys {
  1089  		datasourceContent := hclwrite.NewEmptyFile()
  1090  		body := datasourceContent.Body()
  1091  		body.AppendNewline()
  1092  		datasourceBody := body.AppendNewBlock("data", []string{"amazon-secretsmanager", dataSourceName}).Body()
  1093  		jsonBodyToHCL2Body(datasourceBody, amazonSecretsManagerMap[dataSourceName])
  1094  		p.out = append(p.out, datasourceContent.Bytes()...)
  1095  	}
  1096  
  1097  	return nil
  1098  }
  1099  
  1100  func (p *AmazonSecretsDatasourceParser) Write(out *bytes.Buffer) {
  1101  	if len(p.out) > 0 {
  1102  		if p.WithAnnotations {
  1103  			out.Write([]byte(amazonSecretsManagerDataHeader))
  1104  		}
  1105  		out.Write(p.out)
  1106  	}
  1107  }
  1108  
  1109  type AmazonAmiDatasourceParser struct {
  1110  	Builders        []*template.Builder
  1111  	WithAnnotations bool
  1112  	out             []byte
  1113  }
  1114  
  1115  func (p *AmazonAmiDatasourceParser) Parse(_ *template.Template) error {
  1116  	if p.out == nil {
  1117  		p.out = []byte{}
  1118  	}
  1119  
  1120  	amazonAmiFilters := []map[string]interface{}{}
  1121  	i := 1
  1122  	for _, builder := range p.Builders {
  1123  		if strings.HasPrefix(builder.Type, "amazon-") {
  1124  			if sourceAmiFilter, ok := builder.Config["source_ami_filter"]; ok {
  1125  				sourceAmiFilterCfg := map[string]interface{}{}
  1126  				if err := mapstructure.Decode(sourceAmiFilter, &sourceAmiFilterCfg); err != nil {
  1127  					return fmt.Errorf("Failed to write amazon-ami data source: %v", err)
  1128  				}
  1129  
  1130  				sourceAmiFilterCfg, err := copyAWSAccessConfig(sourceAmiFilterCfg, builder.Config)
  1131  				if err != nil {
  1132  					return err
  1133  				}
  1134  
  1135  				duplicate := false
  1136  				dataSourceName := fmt.Sprintf("autogenerated_%d", i)
  1137  				for j, filter := range amazonAmiFilters {
  1138  					if reflect.DeepEqual(filter, sourceAmiFilterCfg) {
  1139  						duplicate = true
  1140  						dataSourceName = fmt.Sprintf("autogenerated_%d", j+1)
  1141  						continue
  1142  					}
  1143  				}
  1144  
  1145  				// This is a hack...
  1146  				// Use templating so that it could be correctly transformed later into a data resource
  1147  				sourceAmiDataRef := fmt.Sprintf("{{ data `amazon-ami.%s.id` }}", dataSourceName)
  1148  
  1149  				if duplicate {
  1150  					delete(builder.Config, "source_ami_filter")
  1151  					builder.Config["source_ami"] = sourceAmiDataRef
  1152  					continue
  1153  				}
  1154  
  1155  				amazonAmiFilters = append(amazonAmiFilters, sourceAmiFilterCfg)
  1156  				delete(builder.Config, "source_ami_filter")
  1157  				builder.Config["source_ami"] = sourceAmiDataRef
  1158  				i++
  1159  
  1160  				datasourceContent := hclwrite.NewEmptyFile()
  1161  				body := datasourceContent.Body()
  1162  				body.AppendNewline()
  1163  				sourceBody := body.AppendNewBlock("data", []string{"amazon-ami", dataSourceName}).Body()
  1164  				jsonBodyToHCL2Body(sourceBody, sourceAmiFilterCfg)
  1165  				p.out = append(p.out, transposeTemplatingCalls(datasourceContent.Bytes())...)
  1166  			}
  1167  		}
  1168  	}
  1169  	return nil
  1170  }
  1171  
  1172  type AssumeRoleConfig struct {
  1173  	AssumeRoleARN               string            `mapstructure:"role_arn" required:"false"`
  1174  	AssumeRoleDurationSeconds   int               `mapstructure:"duration_seconds" required:"false"`
  1175  	AssumeRoleExternalID        string            `mapstructure:"external_id" required:"false"`
  1176  	AssumeRolePolicy            string            `mapstructure:"policy" required:"false"`
  1177  	AssumeRolePolicyARNs        []string          `mapstructure:"policy_arns" required:"false"`
  1178  	AssumeRoleSessionName       string            `mapstructure:"session_name" required:"false"`
  1179  	AssumeRoleTags              map[string]string `mapstructure:"tags" required:"false"`
  1180  	AssumeRoleTransitiveTagKeys []string          `mapstructure:"transitive_tag_keys" required:"false"`
  1181  }
  1182  
  1183  type VaultAWSEngineOptions struct {
  1184  	Name       string `mapstructure:"name"`
  1185  	RoleARN    string `mapstructure:"role_arn"`
  1186  	TTL        string `mapstructure:"ttl" required:"false"`
  1187  	EngineName string `mapstructure:"engine_name"`
  1188  }
  1189  
  1190  type AWSPollingConfig struct {
  1191  	MaxAttempts  int `mapstructure:"max_attempts" required:"false"`
  1192  	DelaySeconds int `mapstructure:"delay_seconds" required:"false"`
  1193  }
  1194  
  1195  type AwsAccessConfig struct {
  1196  	AccessKey             string                `mapstructure:"access_key" required:"true"`
  1197  	AssumeRole            AssumeRoleConfig      `mapstructure:"assume_role" required:"false"`
  1198  	CustomEndpointEc2     string                `mapstructure:"custom_endpoint_ec2" required:"false"`
  1199  	CredsFilename         string                `mapstructure:"shared_credentials_file" required:"false"`
  1200  	DecodeAuthZMessages   bool                  `mapstructure:"decode_authorization_messages" required:"false"`
  1201  	InsecureSkipTLSVerify bool                  `mapstructure:"insecure_skip_tls_verify" required:"false"`
  1202  	MaxRetries            int                   `mapstructure:"max_retries" required:"false"`
  1203  	MFACode               string                `mapstructure:"mfa_code" required:"false"`
  1204  	ProfileName           string                `mapstructure:"profile" required:"false"`
  1205  	RawRegion             string                `mapstructure:"region" required:"true"`
  1206  	SecretKey             string                `mapstructure:"secret_key" required:"true"`
  1207  	SkipMetadataApiCheck  bool                  `mapstructure:"skip_metadata_api_check"`
  1208  	SkipCredsValidation   bool                  `mapstructure:"skip_credential_validation"`
  1209  	Token                 string                `mapstructure:"token" required:"false"`
  1210  	VaultAWSEngine        VaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false"`
  1211  	PollingConfig         *AWSPollingConfig     `mapstructure:"aws_polling" required:"false"`
  1212  }
  1213  
  1214  func copyAWSAccessConfig(sourceAmi map[string]interface{}, builder map[string]interface{}) (map[string]interface{}, error) {
  1215  	// Transform access config to a map
  1216  	accessConfigMap := map[string]interface{}{}
  1217  	if err := mapstructure.Decode(AwsAccessConfig{}, &accessConfigMap); err != nil {
  1218  		return sourceAmi, err
  1219  	}
  1220  
  1221  	for k := range accessConfigMap {
  1222  		// Copy only access config present in the builder
  1223  		if v, ok := builder[k]; ok {
  1224  			sourceAmi[k] = v
  1225  		}
  1226  	}
  1227  
  1228  	return sourceAmi, nil
  1229  }
  1230  
  1231  func (p *AmazonAmiDatasourceParser) Write(out *bytes.Buffer) {
  1232  	if len(p.out) > 0 {
  1233  		if p.WithAnnotations {
  1234  			out.Write([]byte(amazonAmiDataHeader))
  1235  		}
  1236  		out.Write(p.out)
  1237  	}
  1238  }
  1239  
  1240  type SourceParser struct {
  1241  	Builders        []*template.Builder
  1242  	BuilderPlugins  packer.BuilderSet
  1243  	WithAnnotations bool
  1244  	out             []byte
  1245  }
  1246  
  1247  func (p *SourceParser) Parse(tpl *template.Template) error {
  1248  	if p.out == nil {
  1249  		p.out = []byte{}
  1250  	}
  1251  
  1252  	var unknownBuilders []string
  1253  	for i, builderCfg := range p.Builders {
  1254  		sourcesContent := hclwrite.NewEmptyFile()
  1255  		body := sourcesContent.Body()
  1256  		body.AppendNewline()
  1257  		if !p.BuilderPlugins.Has(builderCfg.Type) && knownPluginComponent(builderCfg.Type) == unknownPluginName {
  1258  			unknownBuilders = append(unknownBuilders, builderCfg.Type)
  1259  		}
  1260  		if builderCfg.Name == "" || builderCfg.Name == builderCfg.Type {
  1261  			builderCfg.Name = fmt.Sprintf("autogenerated_%d", i+1)
  1262  		}
  1263  		builderCfg.Name = strings.ReplaceAll(strings.TrimSpace(builderCfg.Name), " ", "_")
  1264  
  1265  		sourceBody := body.AppendNewBlock("source", []string{builderCfg.Type, builderCfg.Name}).Body()
  1266  
  1267  		jsonBodyToHCL2Body(sourceBody, builderCfg.Config)
  1268  
  1269  		p.out = append(p.out, transposeTemplatingCalls(sourcesContent.Bytes())...)
  1270  	}
  1271  	// TODO update to output to stderr as opposed to having the command exit 1
  1272  	if len(unknownBuilders) > 0 {
  1273  		return fmt.Errorf("unknown builder type(s): %v\n", unknownBuilders)
  1274  	}
  1275  
  1276  	return nil
  1277  }
  1278  
  1279  func (p *SourceParser) Write(out *bytes.Buffer) {
  1280  	if len(p.out) > 0 {
  1281  		if p.WithAnnotations {
  1282  			out.Write([]byte(sourcesHeader))
  1283  		}
  1284  		out.Write(p.out)
  1285  	}
  1286  }
  1287  
  1288  type BuildParser struct {
  1289  	Builders        []*template.Builder
  1290  	WithAnnotations bool
  1291  
  1292  	provisioners   BlockParser
  1293  	postProcessors BlockParser
  1294  	out            []byte
  1295  }
  1296  
  1297  func (p *BuildParser) Parse(tpl *template.Template) error {
  1298  	if len(p.Builders) == 0 {
  1299  		return nil
  1300  	}
  1301  
  1302  	buildContent := hclwrite.NewEmptyFile()
  1303  	buildBody := buildContent.Body()
  1304  	if tpl.Description != "" {
  1305  		buildBody.SetAttributeValue("description", cty.StringVal(tpl.Description))
  1306  		buildBody.AppendNewline()
  1307  	}
  1308  
  1309  	sourceNames := []string{}
  1310  	for _, builder := range p.Builders {
  1311  		sourceNames = append(sourceNames, fmt.Sprintf("source.%s.%s", builder.Type, builder.Name))
  1312  	}
  1313  	buildBody.SetAttributeValue("sources", hcl2shim.HCL2ValueFromConfigValue(sourceNames))
  1314  	buildBody.AppendNewline()
  1315  	p.out = buildContent.Bytes()
  1316  
  1317  	p.provisioners = &ProvisionerParser{
  1318  		WithAnnotations: p.WithAnnotations,
  1319  	}
  1320  	if err := p.provisioners.Parse(tpl); err != nil {
  1321  		return err
  1322  	}
  1323  
  1324  	p.postProcessors = &PostProcessorParser{
  1325  		WithAnnotations: p.WithAnnotations,
  1326  	}
  1327  	if err := p.postProcessors.Parse(tpl); err != nil {
  1328  		return err
  1329  	}
  1330  
  1331  	return nil
  1332  }
  1333  
  1334  func (p *BuildParser) Write(out *bytes.Buffer) {
  1335  	if len(p.out) > 0 {
  1336  		if p.WithAnnotations {
  1337  			out.Write([]byte(buildHeader))
  1338  		} else {
  1339  			_, _ = out.Write([]byte("\n"))
  1340  		}
  1341  		_, _ = out.Write([]byte("build {\n"))
  1342  		out.Write(p.out)
  1343  		p.provisioners.Write(out)
  1344  		p.postProcessors.Write(out)
  1345  		_, _ = out.Write([]byte("}\n"))
  1346  	}
  1347  }
  1348  
  1349  type ProvisionerParser struct {
  1350  	WithAnnotations bool
  1351  	out             []byte
  1352  }
  1353  
  1354  func (p *ProvisionerParser) Parse(tpl *template.Template) error {
  1355  	if p.out == nil {
  1356  		p.out = []byte{}
  1357  	}
  1358  	for _, provisioner := range tpl.Provisioners {
  1359  		contentBytes := writeProvisioner("provisioner", provisioner)
  1360  		p.out = append(p.out, transposeTemplatingCalls(contentBytes)...)
  1361  	}
  1362  
  1363  	if tpl.CleanupProvisioner != nil {
  1364  		contentBytes := writeProvisioner("error-cleanup-provisioner", tpl.CleanupProvisioner)
  1365  		p.out = append(p.out, transposeTemplatingCalls(contentBytes)...)
  1366  	}
  1367  	return nil
  1368  }
  1369  
  1370  func writeProvisioner(typeName string, provisioner *template.Provisioner) []byte {
  1371  	provisionerContent := hclwrite.NewEmptyFile()
  1372  	body := provisionerContent.Body()
  1373  	block := body.AppendNewBlock(typeName, []string{provisioner.Type})
  1374  
  1375  	cfg := provisioner.Config
  1376  	if cfg == nil {
  1377  		cfg = map[string]interface{}{}
  1378  	}
  1379  
  1380  	if len(provisioner.Except) > 0 {
  1381  		cfg["except"] = provisioner.Except
  1382  	}
  1383  	if len(provisioner.Only) > 0 {
  1384  		cfg["only"] = provisioner.Only
  1385  	}
  1386  	if provisioner.MaxRetries != "" {
  1387  		cfg["max_retries"] = provisioner.MaxRetries
  1388  	}
  1389  	if provisioner.Timeout > 0 {
  1390  		cfg["timeout"] = provisioner.Timeout.String()
  1391  	}
  1392  	if provisioner.PauseBefore > 0 {
  1393  		cfg["pause_before"] = provisioner.PauseBefore.String()
  1394  	}
  1395  	body.AppendNewline()
  1396  	jsonBodyToHCL2Body(block.Body(), cfg)
  1397  	return provisionerContent.Bytes()
  1398  }
  1399  
  1400  func (p *ProvisionerParser) Write(out *bytes.Buffer) {
  1401  	if len(p.out) > 0 {
  1402  		out.Write(p.out)
  1403  	}
  1404  }
  1405  
  1406  type PostProcessorParser struct {
  1407  	WithAnnotations bool
  1408  	out             []byte
  1409  }
  1410  
  1411  func (p *PostProcessorParser) Parse(tpl *template.Template) error {
  1412  	if p.out == nil {
  1413  		p.out = []byte{}
  1414  	}
  1415  	for _, pps := range tpl.PostProcessors {
  1416  		postProcessorContent := hclwrite.NewEmptyFile()
  1417  		body := postProcessorContent.Body()
  1418  
  1419  		switch len(pps) {
  1420  		case 0:
  1421  			continue
  1422  		case 1:
  1423  		default:
  1424  			body = body.AppendNewBlock("post-processors", nil).Body()
  1425  		}
  1426  		for _, pp := range pps {
  1427  			ppBody := body.AppendNewBlock("post-processor", []string{pp.Type}).Body()
  1428  			if pp.KeepInputArtifact != nil {
  1429  				ppBody.SetAttributeValue("keep_input_artifact", cty.BoolVal(*pp.KeepInputArtifact))
  1430  			}
  1431  			cfg := pp.Config
  1432  			if cfg == nil {
  1433  				cfg = map[string]interface{}{}
  1434  			}
  1435  
  1436  			if len(pp.Except) > 0 {
  1437  				cfg["except"] = pp.Except
  1438  			}
  1439  			if len(pp.Only) > 0 {
  1440  				cfg["only"] = pp.Only
  1441  			}
  1442  			if pp.Name != "" && pp.Name != pp.Type {
  1443  				cfg["name"] = pp.Name
  1444  			}
  1445  			jsonBodyToHCL2Body(ppBody, cfg)
  1446  		}
  1447  
  1448  		p.out = append(p.out, transposeTemplatingCalls(postProcessorContent.Bytes())...)
  1449  	}
  1450  	return nil
  1451  }
  1452  
  1453  func (p *PostProcessorParser) Write(out *bytes.Buffer) {
  1454  	if len(p.out) > 0 {
  1455  		out.Write(p.out)
  1456  	}
  1457  }
  1458  
  1459  func fixQuoting(old string) string {
  1460  	// This regex captures golang template functions that use escaped quotes:
  1461  	// {{ env \"myvar\" }}
  1462  	// {{ split `some-string` \"-\" 0 }}
  1463  	re := regexp.MustCompile(`{{\s*\w*(\s*(\\".*\\")\s*)+\w*\s*}}`)
  1464  
  1465  	body := re.ReplaceAllFunc([]byte(old), func(s []byte) []byte {
  1466  		// Get the capture group
  1467  		group := re.ReplaceAllString(string(s), `$1`)
  1468  
  1469  		unquoted, err := strconv.Unquote(fmt.Sprintf("\"%s\"", group))
  1470  		if err != nil {
  1471  			return s
  1472  		}
  1473  		return []byte(strings.Replace(string(s), group, unquoted, 1))
  1474  
  1475  	})
  1476  
  1477  	return string(body)
  1478  }
  1479  
  1480  func knownPluginComponent(component string) string {
  1481  	for prefix := range knownPlugins {
  1482  		if strings.HasPrefix(component, prefix) {
  1483  			return prefix
  1484  		}
  1485  	}
  1486  	return unknownPluginName
  1487  }