configs/configupgrade: Upgrade expressions nested in HCL list/object

In the old world, lists and maps could be created either using functions
in HIL or list/object constructs in HCL. Here we ensure that in the HCL
case we'll apply any required expression transformations to the individual
items within HCL's compound constructs.
This commit is contained in:
Martin Atkins 2018-11-30 17:47:01 -08:00
parent e49d993d89
commit 8cf024d45a
4 changed files with 89 additions and 4 deletions

View File

@ -0,0 +1,18 @@
locals {
in_map = {
foo = "${var.baz}"
}
in_list = [
"${var.baz}",
"${var.bar}",
]
in_list_oneline = ["${var.baz}", "${var.bar}"]
in_map_of_list = {
foo = ["${var.baz}"]
}
in_list_of_map = [
{
foo = "${var.baz}"
}
]
}

View File

@ -0,0 +1,18 @@
locals {
in_map = {
foo = var.baz
}
in_list = [
var.baz,
var.bar,
]
in_list_oneline = [var.baz, var.bar]
in_map_of_list = {
foo = [var.baz]
}
in_list_of_map = [
{
foo = var.baz
},
]
}

View File

@ -0,0 +1,3 @@
terraform {
required_version = ">= 0.12"
}

View File

@ -3,6 +3,7 @@ package configupgrade
import (
"bytes"
"fmt"
"log"
"strconv"
hcl2 "github.com/hashicorp/hcl2/hcl"
@ -28,8 +29,11 @@ func upgradeExpr(val interface{}, filename string, interp bool, an *analysis) ([
switch tv := val.(type) {
case *hcl1ast.LiteralType:
litVal := tv.Token.Value()
switch tv.Token.Type {
return upgradeExpr(tv.Token, filename, interp, an)
case hcl1token.Token:
litVal := tv.Value()
switch tv.Type {
case hcl1token.STRING:
if !interp {
// Easy case, then.
@ -43,7 +47,7 @@ func upgradeExpr(val interface{}, filename string, interp bool, an *analysis) ([
Severity: hcl2.DiagError,
Summary: "Invalid interpolated string",
Detail: fmt.Sprintf("Interpolation parsing failed: %s", err),
Subject: hcl1PosRange(filename, tv.Pos()).Ptr(),
Subject: hcl1PosRange(filename, tv.Pos).Ptr(),
})
}
@ -64,16 +68,58 @@ func upgradeExpr(val interface{}, filename string, interp bool, an *analysis) ([
default:
// For everything else (NUMBER, FLOAT) we'll just pass through the given bytes verbatim.
buf.WriteString(tv.Token.Text)
buf.WriteString(tv.Text)
}
case *hcl1ast.ListType:
multiline := tv.Lbrack.Line != tv.Rbrack.Line
buf.WriteString("[")
if multiline {
buf.WriteString("\n")
}
for i, node := range tv.List {
src, moreDiags := upgradeExpr(node, filename, interp, an)
diags = diags.Append(moreDiags)
buf.Write(src)
if multiline {
buf.WriteString(",\n")
} else if i < len(tv.List)-1 {
buf.WriteString(", ")
}
}
buf.WriteString("]")
case *hcl1ast.ObjectType:
buf.WriteString("{\n")
for _, item := range tv.List.Items {
if len(item.Keys) != 1 {
diags = diags.Append(&hcl2.Diagnostic{
Severity: hcl2.DiagError,
Summary: "Invalid map element",
Detail: "A map element may not have any block-style labels.",
Subject: hcl1PosRange(filename, item.Pos()).Ptr(),
})
continue
}
keySrc, moreDiags := upgradeExpr(item.Keys[0].Token, filename, interp, an)
diags = diags.Append(moreDiags)
valueSrc, moreDiags := upgradeExpr(item.Val, filename, interp, an)
diags = diags.Append(moreDiags)
buf.Write(keySrc)
buf.WriteString(" = ")
buf.Write(valueSrc)
buf.WriteString("\n")
}
buf.WriteString("}")
case hcl1ast.Node:
// If our more-specific cases above didn't match this then we'll
// ask the hcl1printer package to print the expression out
// itself, and assume it'll still be valid in HCL2.
// (We should rarely end up here, since our cases above should
// be comprehensive.)
log.Printf("[TRACE] configupgrade: Don't know how to upgrade %T as expression, so just passing it through as-is", tv)
hcl1printer.Fprint(&buf, tv)
case *hilast.LiteralNode: