From 0993c976fab3d11dcb9db85f9f40ae8a7efb0ed4 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 22 Mar 2021 02:56:30 -0700 Subject: [PATCH] hcl2_upgrade escaped quotes fix (#10794) * clean up extra quoting that can cause text template failures. when everyone else abandons you, regex will always be there. * LINTING --- command/hcl2_upgrade.go | 56 ++++++++++++++++++- command/hcl2_upgrade_test.go | 1 + .../hcl2_upgrade/escaping/expected.pkr.hcl | 31 ++++++++++ .../hcl2_upgrade/escaping/input.json | 21 +++++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 command/test-fixtures/hcl2_upgrade/escaping/expected.pkr.hcl create mode 100644 command/test-fixtures/hcl2_upgrade/escaping/input.json diff --git a/command/hcl2_upgrade.go b/command/hcl2_upgrade.go index 7192c193b..5273e3996 100644 --- a/command/hcl2_upgrade.go +++ b/command/hcl2_upgrade.go @@ -8,7 +8,9 @@ import ( "os" "path/filepath" "reflect" + "regexp" "sort" + "strconv" "strings" texttemplate "text/template" @@ -437,7 +439,22 @@ func transposeTemplatingCalls(s []byte) []byte { Parse(string(s)) if err != nil { - return fallbackReturn(err, s) + if strings.Contains(err.Error(), "unexpected \"\\\\\" in operand") { + // This error occurs if the operand in the text template used + // escaped quoting \" instead of bactick quoting ` + // Create a regex to do a string replace on this block, to fix + // quoting. + q := fixQuoting(string(s)) + unquoted := []byte(q) + tpl, err = texttemplate.New("hcl2_upgrade"). + Funcs(funcMap). + Parse(string(unquoted)) + if err != nil { + return fallbackReturn(err, unquoted) + } + } else { + return fallbackReturn(err, s) + } } str := &bytes.Buffer{} @@ -502,7 +519,22 @@ func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) { Parse(string(s)) if err != nil { - return isLocal, fallbackReturn(err, s) + if strings.Contains(err.Error(), "unexpected \"\\\\\" in operand") { + // This error occurs if the operand in the text template used + // escaped quoting \" instead of bactick quoting ` + // Create a regex to do a string replace on this block, to fix + // quoting. + q := fixQuoting(string(s)) + unquoted := []byte(q) + tpl, err = texttemplate.New("hcl2_upgrade"). + Funcs(funcMap). + Parse(string(unquoted)) + if err != nil { + return isLocal, fallbackReturn(err, unquoted) + } + } else { + return isLocal, fallbackReturn(err, s) + } } str := &bytes.Buffer{} @@ -1247,3 +1279,23 @@ var PASSTHROUGHS = map[string]string{"NVME_Present": "{{ .NVME_Present }}", "ProviderVagrantfile": "{{ .ProviderVagrantfile }}", "Sound_Present": "{{ .Sound_Present }}", } + +func fixQuoting(old string) string { + // This regex captures golang template functions that use escaped quotes: + // {{ env \"myvar\" }} + re := regexp.MustCompile(`{{\s*\w*\s*(\\".*\\")\s*}}`) + + body := re.ReplaceAllFunc([]byte(old), func(s []byte) []byte { + // Get the capture group + group := re.ReplaceAllString(string(s), `$1`) + + unquoted, err := strconv.Unquote(fmt.Sprintf("\"%s\"", group)) + if err != nil { + return s + } + return []byte(strings.Replace(string(s), group, unquoted, 1)) + + }) + + return string(body) +} diff --git a/command/hcl2_upgrade_test.go b/command/hcl2_upgrade_test.go index fea33f365..35fabe816 100644 --- a/command/hcl2_upgrade_test.go +++ b/command/hcl2_upgrade_test.go @@ -29,6 +29,7 @@ func Test_hcl2_upgrade(t *testing.T) { {folder: "variables-only", flags: []string{}}, {folder: "variables-with-variables", flags: []string{}}, {folder: "complete-variables-with-template-engine", flags: []string{}}, + {folder: "escaping", flags: []string{}}, } for _, tc := range tc { diff --git a/command/test-fixtures/hcl2_upgrade/escaping/expected.pkr.hcl b/command/test-fixtures/hcl2_upgrade/escaping/expected.pkr.hcl new file mode 100644 index 000000000..c0010aa75 --- /dev/null +++ b/command/test-fixtures/hcl2_upgrade/escaping/expected.pkr.hcl @@ -0,0 +1,31 @@ + +variable "conf" { + type = string + default = "${env("ONE")}-${env("ANOTHER")}-${env("BACKTICKED")}" +} + +variable "manyspaces" { + type = string + default = "${env("ASDFASDF")}" +} + +variable "nospaces" { + type = string + default = "${env("SOMETHING")}" +} + +locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } +# The "legacy_isotime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions. + +source "null" "autogenerated_1" { + communicator = "none" +} + +build { + sources = ["source.null.autogenerated_1"] + + provisioner "shell-local" { + inline = ["echo ${var.conf}-${local.timestamp}-${legacy_isotime("01-02-2006")}"] + } + +} diff --git a/command/test-fixtures/hcl2_upgrade/escaping/input.json b/command/test-fixtures/hcl2_upgrade/escaping/input.json new file mode 100644 index 000000000..654ba61b2 --- /dev/null +++ b/command/test-fixtures/hcl2_upgrade/escaping/input.json @@ -0,0 +1,21 @@ +{ + "variables": { + "conf": "{{ env \"ONE\" }}-{{env \"ANOTHER\"}}-{{ env `BACKTICKED` }}", + "nospaces": "{{env \"SOMETHING\"}}", + "manyspaces": "{{ env \"ASDFASDF\"}}" + }, + "builders": [ + { + "type": "null", + "communicator": "none" + } + ], + "provisioners": [ + { + "type": "shell-local", + "inline": [ + "echo {{user \"conf\"}}-{{timestamp}}-{{isotime \"01-02-2006\"}}" + ] + } + ] +} \ No newline at end of file