From 502708b86ae27f37ebcf6ba0269ff430f15e5035 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 19 Mar 2021 02:24:49 -0700 Subject: [PATCH] Refactor hcl2_upgrade (#10787) --- command/hcl2_upgrade.go | 110 +++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/command/hcl2_upgrade.go b/command/hcl2_upgrade.go index 8f6af2046..7192c193b 100644 --- a/command/hcl2_upgrade.go +++ b/command/hcl2_upgrade.go @@ -270,18 +270,18 @@ func (uc UnhandleableArgumentError) Error() string { # Visit %s for more infos.`, uc.Call, uc.Correspondance, uc.Docs) } +func fallbackReturn(err error, s []byte) []byte { + 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...) +} + // 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 { - 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...) - } - funcErrors := &multierror.Error{ ErrorFormat: func(es []error) string { if len(es) == 1 { @@ -437,14 +437,14 @@ func transposeTemplatingCalls(s []byte) []byte { Parse(string(s)) if err != nil { - return fallbackReturn(err) + return fallbackReturn(err, s) } str := &bytes.Buffer{} // PASSTHROUGHS is a map of variable-specific golang text template fields // that should remain in the text template format. if err := tpl.Execute(str, PASSTHROUGHS); err != nil { - return fallbackReturn(err) + return fallbackReturn(err, s) } out := str.Bytes() @@ -461,14 +461,6 @@ func transposeTemplatingCalls(s []byte) []byte { // In variableTransposeTemplatingCalls the definition of aws_secretsmanager function will create a data source // with the same name as the variable. func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) { - fallbackReturn := func(err error) []byte { - 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...) - } - setIsLocal := func(a ...string) string { isLocal = true return "" @@ -510,14 +502,14 @@ func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) { Parse(string(s)) if err != nil { - return isLocal, fallbackReturn(err) + return isLocal, fallbackReturn(err, s) } str := &bytes.Buffer{} // PASSTHROUGHS is a map of variable-specific golang text template fields // that should remain in the text template format. if err := tpl.Execute(str, PASSTHROUGHS); err != nil { - return isLocal, fallbackReturn(err) + return isLocal, fallbackReturn(err, s) } return isLocal, str.Bytes() @@ -682,12 +674,49 @@ type VariableParser struct { localsOut []byte } +func makeLocal(variable *template.Variable, sensitive bool, localBody *hclwrite.Body, localsContent *hclwrite.File, hasLocals *bool) []byte { + if sensitive { + // Create Local block because this is sensitive + sensitiveLocalContent := hclwrite.NewEmptyFile() + body := sensitiveLocalContent.Body() + body.AppendNewline() + sensitiveLocalBody := body.AppendNewBlock("local", []string{variable.Key}).Body() + sensitiveLocalBody.SetAttributeValue("sensitive", cty.BoolVal(true)) + sensitiveLocalBody.SetAttributeValue("expression", hcl2shim.HCL2ValueFromConfigValue(variable.Default)) + localsVariableMap[variable.Key] = "local" + return sensitiveLocalContent.Bytes() + } + localBody.SetAttributeValue(variable.Key, hcl2shim.HCL2ValueFromConfigValue(variable.Default)) + localsVariableMap[variable.Key] = "locals" + *hasLocals = true + return []byte{} +} + +func makeVariable(variable *template.Variable, sensitive bool) []byte { + variablesContent := hclwrite.NewEmptyFile() + variablesBody := variablesContent.Body() + variablesBody.AppendNewline() + variableBody := variablesBody.AppendNewBlock("variable", []string{variable.Key}).Body() + variableBody.SetAttributeRaw("type", hclwrite.Tokens{&hclwrite.Token{Bytes: []byte("string")}}) + + if variable.Default != "" || !variable.Required { + shimmed := hcl2shim.HCL2ValueFromConfigValue(variable.Default) + variableBody.SetAttributeValue("default", shimmed) + } + if sensitive { + variableBody.SetAttributeValue("sensitive", cty.BoolVal(true)) + } + + return variablesContent.Bytes() +} + func (p *VariableParser) Parse(tpl *template.Template) error { - // OutPut Locals and Local blocks + // Output Locals and Local blocks localsContent := hclwrite.NewEmptyFile() localsBody := localsContent.Body() localsBody.AppendNewline() localBody := localsBody.AppendNewBlock("locals", nil).Body() + hasLocals := false if len(p.variablesOut) == 0 { p.variablesOut = []byte{} @@ -707,47 +736,34 @@ func (p *VariableParser) Parse(tpl *template.Template) error { }) } - hasLocals := false for _, variable := range variables { - variablesContent := hclwrite.NewEmptyFile() - variablesBody := variablesContent.Body() - variablesBody.AppendNewline() - variableBody := variablesBody.AppendNewBlock("variable", []string{variable.Key}).Body() - variableBody.SetAttributeRaw("type", hclwrite.Tokens{&hclwrite.Token{Bytes: []byte("string")}}) + // Create new HCL2 "variables" block, and populate the "value" + // field with the "Default" value from the JSON variable. - if variable.Default != "" || !variable.Required { - variableBody.SetAttributeValue("default", hcl2shim.HCL2ValueFromConfigValue(variable.Default)) - } + // Interpolate Jsonval first as an hcl variable to determine if it is + // a local. + isLocal, _ := variableTransposeTemplatingCalls([]byte(variable.Default)) sensitive := false if isSensitiveVariable(variable.Key, tpl.SensitiveVariables) { sensitive = true - variableBody.SetAttributeValue("sensitive", cty.BoolVal(true)) } - isLocal, out := variableTransposeTemplatingCalls(variablesContent.Bytes()) + // Create final HCL block and append. if isLocal { - if sensitive { - // Create Local block because this is sensitive - localContent := hclwrite.NewEmptyFile() - body := localContent.Body() - body.AppendNewline() - localBody := body.AppendNewBlock("local", []string{variable.Key}).Body() - localBody.SetAttributeValue("sensitive", cty.BoolVal(true)) - localBody.SetAttributeValue("expression", hcl2shim.HCL2ValueFromConfigValue(variable.Default)) - p.localsOut = append(p.localsOut, transposeTemplatingCalls(localContent.Bytes())...) - localsVariableMap[variable.Key] = "local" - continue + sensitiveBlocks := makeLocal(variable, sensitive, localBody, localsContent, &hasLocals) + if len(sensitiveBlocks) > 0 { + p.localsOut = append(p.localsOut, transposeTemplatingCalls(sensitiveBlocks)...) } - localBody.SetAttributeValue(variable.Key, hcl2shim.HCL2ValueFromConfigValue(variable.Default)) - localsVariableMap[variable.Key] = "locals" - hasLocals = true continue } + varbytes := makeVariable(variable, sensitive) + _, out := variableTransposeTemplatingCalls(varbytes) p.variablesOut = append(p.variablesOut, out...) } - if hasLocals { + if hasLocals == true { p.localsOut = append(p.localsOut, transposeTemplatingCalls(localsContent.Bytes())...) } + return nil }