diff --git a/command/hcl2_upgrade.go b/command/hcl2_upgrade.go index 9da3dc645..13148c430 100644 --- a/command/hcl2_upgrade.go +++ b/command/hcl2_upgrade.go @@ -64,10 +64,19 @@ const ( # once they also need to be in the same folder. 'packer inspect folder/' # will describe to you what is in that folder. +# Avoid mixing go templating calls ( for example ` + "```{{ upper(`string`) }}```" + ` ) +# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be +# executed together and the outcome will be unknown. +` + inputVarHeader = ` # All generated input variables will be of 'string' type as this is how Packer JSON # views them; you can change their type later on. Read the variables type # constraints documentation # https://www.packer.io/docs/from-1.5/variables#type-constraints for more info. +` + + packerBlockHeader = ` +# See https://www.packer.io/docs/from-1.5/blocks/packer for more info ` sourcesHeader = ` @@ -112,10 +121,22 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra core := hdl.(*CoreWrapper).Core if err := core.Initialize(); err != nil { - c.Ui.Error(fmt.Sprintf("Initialization error, continuing: %v", err)) + c.Ui.Error(fmt.Sprintf("Ignoring following initialization error: %v", err)) } tpl := core.Template + // Packer section + if tpl.MinVersion != "" { + out.Write([]byte(packerBlockHeader)) + fileContent := hclwrite.NewEmptyFile() + body := fileContent.Body() + packerBody := body.AppendNewBlock("packer", nil).Body() + packerBody.SetAttributeValue("required_version", cty.StringVal(fmt.Sprintf(">= %s", tpl.MinVersion))) + out.Write(fileContent.Bytes()) + } + + out.Write([]byte(inputVarHeader)) + // Output variables section variables := []*template.Variable{} @@ -266,12 +287,29 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra return 0 } +type UnhandleableArgumentError struct { + Call string + Correspondance string + Docs string +} + +func (uc UnhandleableArgumentError) Error() string { + return fmt.Sprintf(`unhandled %q call: +# there is no way to automatically upgrade the %[1]q call. +# Please manually upgrade to %s +# Visit %s for more infos.`, uc.Call, uc.Correspondance, uc.Docs) +} + // transposeTemplatingCalls executes parts of blocks as go template files and replaces // their result with their hcl2 variant. If something goes wrong the template // containing the go template string is returned. func transposeTemplatingCalls(s []byte) []byte { fallbackReturn := func(err error) []byte { - return append([]byte(fmt.Sprintf("\n#could not parse template for following block: %q\n", err)), s...) + if strings.Contains(err.Error(), "unhandled") { + return append([]byte(fmt.Sprintf("\n# %s\n", err)), s...) + } + + return append([]byte(fmt.Sprintf("\n# could not parse template for following block: %q\n", err)), s...) } funcMap := texttemplate.FuncMap{ "timestamp": func() string { @@ -289,9 +327,71 @@ func transposeTemplatingCalls(s []byte) []byte { "build": func(a string) string { return fmt.Sprintf("${build.%s}", a) }, + "template_dir": func() string { + return fmt.Sprintf("${path.root}") + }, + "pwd": func() string { + return fmt.Sprintf("${path.cwd}") + }, + "packer_version": func() string { + return fmt.Sprintf("${packer.version}") + }, + "uuid": func() string { + return fmt.Sprintf("${uuidv4()}") + }, + "lower": func(_ string) (string, error) { + return "", UnhandleableArgumentError{ + "lower", + "`lower(var.example)`", + "https://www.packer.io/docs/from-1.5/functions/string/lower", + } + }, + "upper": func(_ string) (string, error) { + return "", UnhandleableArgumentError{ + "upper", + "`upper(var.example)`", + "https://www.packer.io/docs/from-1.5/functions/string/upper", + } + }, + "split": func(_, _ string, _ int) (string, error) { + return "", UnhandleableArgumentError{ + "split", + "`split(separator, string)`", + "https://www.packer.io/docs/from-1.5/functions/string/split", + } + }, + "replace": func(_, _, _ string, _ int) (string, error) { + return "", UnhandleableArgumentError{ + "replace", + "`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`", + "https://www.packer.io/docs/from-1.5/functions/string/replace or https://www.packer.io/docs/from-1.5/functions/string/regex_replace", + } + }, + "replace_all": func(_, _, _ string) (string, error) { + return "", UnhandleableArgumentError{ + "replace_all", + "`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`", + "https://www.packer.io/docs/from-1.5/functions/string/replace or https://www.packer.io/docs/from-1.5/functions/string/regex_replace", + } + }, + "clean_resource_name": func(_ string) (string, error) { + return "", UnhandleableArgumentError{ + "clean_resource_name", + "use custom validation rules, `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`", + "https://packer.io/docs/from-1.5/variables#custom-validation-rules" + + " , https://www.packer.io/docs/from-1.5/functions/string/replace" + + " or https://www.packer.io/docs/from-1.5/functions/string/regex_replace", + } + }, + "build_name": func() string { + return fmt.Sprintf("${build.name}") + }, + "build_type": func() string { + return fmt.Sprintf("${build.type}") + }, } - tpl, err := texttemplate.New("generated"). + tpl, err := texttemplate.New("hcl2_upgrade"). Funcs(funcMap). Parse(string(s)) diff --git a/command/test-fixtures/hcl2_upgrade_basic/expected.pkr.hcl b/command/test-fixtures/hcl2_upgrade_basic/expected.pkr.hcl index ffb88fd7c..97b18111f 100644 --- a/command/test-fixtures/hcl2_upgrade_basic/expected.pkr.hcl +++ b/command/test-fixtures/hcl2_upgrade_basic/expected.pkr.hcl @@ -7,6 +7,15 @@ # once they also need to be in the same folder. 'packer inspect folder/' # will describe to you what is in that folder. +# Avoid mixing go templating calls ( for example ```{{ upper(`string`) }}``` ) +# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be +# executed together and the outcome will be unknown. + +# See https://www.packer.io/docs/from-1.5/blocks/packer for more info +packer { + required_version = ">= 1.6.0" +} + # All generated input variables will be of 'string' type as this is how Packer JSON # views them; you can change their type later on. Read the variables type # constraints documentation @@ -27,6 +36,12 @@ variable "aws_secret_key" { sensitive = true } +variable "secret_account" { + type = string + default = "🤷" + sensitive = true +} + # "timestamp" template function replacement locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } @@ -77,14 +92,61 @@ build { sources = ["source.amazon-ebs.autogenerated_1"] provisioner "shell" { - inline = ["echo ${var.secret_account}", "echo ${build.ID}", "echo ${build.SSHPrivateKey}", "sleep 100000"] + except = ["amazon-ebs"] + inline = ["echo ${var.secret_account}", "echo ${build.ID}", "echo ${build.SSHPublicKey} | head -c 14", "echo ${path.root} is not ${path.cwd}", "echo ${packer.version}", "echo ${uuidv4()}"] max_retries = "5" - only = ["amazon-ebs"] - timeout = "5s" + } + + # template: hcl2_upgrade:2:38: executing "hcl2_upgrade" at : error calling clean_resource_name: unhandled "clean_resource_name" call: + # there is no way to automatically upgrade the "clean_resource_name" call. + # Please manually upgrade to use custom validation rules, `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)` + # Visit https://packer.io/docs/from-1.5/variables#custom-validation-rules , https://www.packer.io/docs/from-1.5/functions/string/replace or https://www.packer.io/docs/from-1.5/functions/string/regex_replace for more infos. + provisioner "shell" { + inline = ["echo mybuild-{{isotime | clean_resource_name}}"] + } + + # template: hcl2_upgrade:2:35: executing "hcl2_upgrade" at : error calling lower: unhandled "lower" call: + # there is no way to automatically upgrade the "lower" call. + # Please manually upgrade to `lower(var.example)` + # Visit https://www.packer.io/docs/from-1.5/functions/string/lower for more infos. + provisioner "shell" { + inline = ["echo {{ `SOMETHING` | lower }}"] + } + + # template: hcl2_upgrade:2:35: executing "hcl2_upgrade" at : error calling upper: unhandled "upper" call: + # there is no way to automatically upgrade the "upper" call. + # Please manually upgrade to `upper(var.example)` + # Visit https://www.packer.io/docs/from-1.5/functions/string/upper for more infos. + provisioner "shell" { + inline = ["echo {{ `something` | upper }}"] + } + + # template: hcl2_upgrade:2:21: executing "hcl2_upgrade" at : error calling split: unhandled "split" call: + # there is no way to automatically upgrade the "split" call. + # Please manually upgrade to `split(separator, string)` + # Visit https://www.packer.io/docs/from-1.5/functions/string/split for more infos. + provisioner "shell" { + inline = ["echo {{ split `some-string` `-` 0 }}"] + } + + # template: hcl2_upgrade:2:21: executing "hcl2_upgrade" at : error calling replace_all: unhandled "replace_all" call: + # there is no way to automatically upgrade the "replace_all" call. + # Please manually upgrade to `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)` + # Visit https://www.packer.io/docs/from-1.5/functions/string/replace or https://www.packer.io/docs/from-1.5/functions/string/regex_replace for more infos. + provisioner "shell" { + inline = ["echo {{ replace_all `-` `/` build_name }}"] + } + + # template: hcl2_upgrade:2:21: executing "hcl2_upgrade" at : error calling replace: unhandled "replace" call: + # there is no way to automatically upgrade the "replace" call. + # Please manually upgrade to `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)` + # Visit https://www.packer.io/docs/from-1.5/functions/string/replace or https://www.packer.io/docs/from-1.5/functions/string/regex_replace for more infos. + provisioner "shell" { + inline = ["echo {{ replace `some-string` `-` `/` 1 }}"] } provisioner "shell-local" { - except = ["amazon-ebs"] inline = ["sleep 100000"] + only = ["amazon-ebs"] timeout = "5s" } post-processor "amazon-import" { diff --git a/command/test-fixtures/hcl2_upgrade_basic/input.json b/command/test-fixtures/hcl2_upgrade_basic/input.json index cd4fbb6ae..b6f9a9302 100644 --- a/command/test-fixtures/hcl2_upgrade_basic/input.json +++ b/command/test-fixtures/hcl2_upgrade_basic/input.json @@ -1,5 +1,7 @@ { + "min_packer_version": "1.6.0", "variables": { + "secret_account": "🤷", "aws_region": null, "aws_secret_key": "", "aws_access_key": "" @@ -7,6 +9,7 @@ "sensitive-variables": [ "aws_secret_key", "aws_access_key", + "secret_account", "potato" ], "builders": [ @@ -62,21 +65,58 @@ "provisioners": [ { "type": "shell", - "only": [ + "except": [ "amazon-ebs" ], "max_retries": 5, - "timeout": "5s", "inline": [ "echo {{ user `secret_account` }}", "echo {{ build `ID` }}", - "echo {{ build `SSHPrivateKey` }}", - "sleep 100000" + "echo {{ build `SSHPublicKey` }} | head -c 14", + "echo {{ template_dir }} is not {{ pwd }}", + "echo {{ packer_version }}", + "echo {{ uuid }}" + ] + }, + { + "type": "shell", + "inline": [ + "echo mybuild-{{isotime | clean_resource_name}}" + ] + }, + { + "type": "shell", + "inline": [ + "echo {{ `SOMETHING` | lower }}" + ] + }, + { + "type": "shell", + "inline": [ + "echo {{ `something` | upper }}" + ] + }, + { + "type": "shell", + "inline": [ + "echo {{ split `some-string` `-` 0 }}" + ] + }, + { + "type": "shell", + "inline": [ + "echo {{ replace_all `-` `/` build_name }}" + ] + }, + { + "type": "shell", + "inline": [ + "echo {{ replace `some-string` `-` `/` 1 }}" ] }, { "type": "shell-local", - "except": [ + "only": [ "amazon-ebs" ], "timeout": "5s",