configupgrade: Upgrade indexing of splat syntax
This commit is contained in:
parent
1f5cadeec0
commit
42ba7a3e00
|
@ -0,0 +1,16 @@
|
||||||
|
resource "test_instance" "first_many" {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "one" {
|
||||||
|
image = "${test_instance.first_many.*.id[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "splat_of_one" {
|
||||||
|
image = "${test_instance.one.*.id[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "second_many" {
|
||||||
|
count = "${length(test_instance.first_many)}"
|
||||||
|
security_groups = "${test_instance.first_many.*.id[count.index]}"
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
resource "test_instance" "first_many" {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "one" {
|
||||||
|
image = test_instance.first_many[0].id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "splat_of_one" {
|
||||||
|
image = test_instance.one.*.id[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "second_many" {
|
||||||
|
count = length(test_instance.first_many)
|
||||||
|
security_groups = test_instance.first_many[count.index].id
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
terraform {
|
||||||
|
required_version = ">= 0.12"
|
||||||
|
}
|
|
@ -248,41 +248,9 @@ Value:
|
||||||
// safe to do so.
|
// safe to do so.
|
||||||
parts := strings.Split(tv.Name, ".")
|
parts := strings.Split(tv.Name, ".")
|
||||||
|
|
||||||
// First we need to deal with the .count pseudo-attributes that 0.11 and
|
transformed := transformCountPseudoAttribute(&buf, parts, an)
|
||||||
// prior allowed for resources. These no longer exist, because they
|
if transformed {
|
||||||
// don't do anything we can't do with the length(...) function.
|
break Value
|
||||||
if len(parts) > 0 {
|
|
||||||
var rAddr addrs.Resource
|
|
||||||
switch parts[0] {
|
|
||||||
case "data":
|
|
||||||
if len(parts) == 4 && parts[3] == "count" {
|
|
||||||
rAddr.Mode = addrs.DataResourceMode
|
|
||||||
rAddr.Type = parts[1]
|
|
||||||
rAddr.Name = parts[2]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if len(parts) == 3 && parts[2] == "count" {
|
|
||||||
rAddr.Mode = addrs.ManagedResourceMode
|
|
||||||
rAddr.Type = parts[0]
|
|
||||||
rAddr.Name = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to check if the thing being referenced is actually an
|
|
||||||
// existing resource, because other three-part traversals might
|
|
||||||
// coincidentally end with "count".
|
|
||||||
if hasCount, exists := an.ResourceHasCount[rAddr]; exists {
|
|
||||||
if hasCount {
|
|
||||||
buf.WriteString("length(")
|
|
||||||
buf.WriteString(rAddr.String())
|
|
||||||
buf.WriteString(")")
|
|
||||||
} else {
|
|
||||||
// If the resource does not have count, the .count
|
|
||||||
// attr would've always returned 1 before.
|
|
||||||
buf.WriteString("1")
|
|
||||||
}
|
|
||||||
break Value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parts = upgradeTraversalParts(parts, an) // might add/remove/change parts
|
parts = upgradeTraversalParts(parts, an) // might add/remove/change parts
|
||||||
|
@ -293,42 +261,7 @@ Value:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
first, remain := parts[0], parts[1:]
|
printHilTraversalPartsAsHcl2(&buf, parts)
|
||||||
buf.WriteString(first)
|
|
||||||
seenSplat := false
|
|
||||||
for _, part := range remain {
|
|
||||||
if part == "*" {
|
|
||||||
seenSplat = true
|
|
||||||
buf.WriteString(".*")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other special cases apply only if we've not previously
|
|
||||||
// seen a splat expression marker, since attribute vs. index
|
|
||||||
// syntax have different interpretations after a simple splat.
|
|
||||||
if !seenSplat {
|
|
||||||
if v, err := strconv.Atoi(part); err == nil {
|
|
||||||
// Looks like it's old-style index traversal syntax foo.0.bar
|
|
||||||
// so we'll replace with canonical index syntax foo[0].bar.
|
|
||||||
fmt.Fprintf(&buf, "[%d]", v)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !hcl2syntax.ValidIdentifier(part) {
|
|
||||||
// This should be rare since HIL's identifier syntax is _close_
|
|
||||||
// to HCL2's, but we'll get here if one of the intervening
|
|
||||||
// parts is not a valid identifier in isolation, since HIL
|
|
||||||
// did not consider these to be separate identifiers.
|
|
||||||
// e.g. foo.1bar would be invalid in HCL2; must instead be foo["1bar"].
|
|
||||||
buf.WriteByte('[')
|
|
||||||
printQuotedString(&buf, part)
|
|
||||||
buf.WriteByte(']')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte('.')
|
|
||||||
buf.WriteString(part)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *hilast.Arithmetic:
|
case *hilast.Arithmetic:
|
||||||
op, exists := hilArithmeticOpSyms[tv.Op]
|
op, exists := hilArithmeticOpSyms[tv.Op]
|
||||||
|
@ -560,14 +493,74 @@ Value:
|
||||||
buf.Write(falseSrc)
|
buf.Write(falseSrc)
|
||||||
|
|
||||||
case *hilast.Index:
|
case *hilast.Index:
|
||||||
targetSrc, exprDiags := upgradeExpr(tv.Target, filename, true, an)
|
target, ok := tv.Target.(*hilast.VariableAccess)
|
||||||
diags = diags.Append(exprDiags)
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("Index node with unsupported target type (%T)", tv.Target))
|
||||||
|
}
|
||||||
|
parts := strings.Split(target.Name, ".")
|
||||||
|
|
||||||
keySrc, exprDiags := upgradeExpr(tv.Key, filename, true, an)
|
keySrc, exprDiags := upgradeExpr(tv.Key, filename, true, an)
|
||||||
diags = diags.Append(exprDiags)
|
diags = diags.Append(exprDiags)
|
||||||
buf.Write(targetSrc)
|
|
||||||
buf.WriteString("[")
|
transformed := transformCountPseudoAttribute(&buf, parts, an)
|
||||||
buf.Write(keySrc)
|
if transformed {
|
||||||
buf.WriteString("]")
|
break Value
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = upgradeTraversalParts(parts, an) // might add/remove/change parts
|
||||||
|
|
||||||
|
vDiags := validateHilAddress(target.Name, filename)
|
||||||
|
if len(vDiags) > 0 {
|
||||||
|
diags = diags.Append(vDiags)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
first, remain := parts[0], parts[1:]
|
||||||
|
|
||||||
|
var rAddr addrs.Resource
|
||||||
|
switch parts[0] {
|
||||||
|
case "data":
|
||||||
|
if len(parts) == 5 && parts[3] == "*" {
|
||||||
|
rAddr.Mode = addrs.DataResourceMode
|
||||||
|
rAddr.Type = parts[1]
|
||||||
|
rAddr.Name = parts[2]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if len(parts) == 4 && parts[2] == "*" {
|
||||||
|
rAddr.Mode = addrs.ManagedResourceMode
|
||||||
|
rAddr.Type = parts[0]
|
||||||
|
rAddr.Name = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check if the thing being referenced has count
|
||||||
|
// to retain backward compatibility
|
||||||
|
hasCount := false
|
||||||
|
if v, exists := an.ResourceHasCount[rAddr]; exists {
|
||||||
|
hasCount = v
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSplat := false
|
||||||
|
|
||||||
|
buf.WriteString(first)
|
||||||
|
for _, part := range remain {
|
||||||
|
// Attempt to convert old-style splat indexing to new one
|
||||||
|
// e.g. res.label.*.attr[idx] to res.label[idx].attr
|
||||||
|
if part == "*" && hasCount {
|
||||||
|
hasSplat = true
|
||||||
|
buf.WriteString(fmt.Sprintf("[%s]", keySrc))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte('.')
|
||||||
|
buf.WriteString(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasSplat {
|
||||||
|
buf.WriteString("[")
|
||||||
|
buf.Write(keySrc)
|
||||||
|
buf.WriteString("]")
|
||||||
|
}
|
||||||
|
|
||||||
case *hilast.Output:
|
case *hilast.Output:
|
||||||
if len(tv.Exprs) == 1 {
|
if len(tv.Exprs) == 1 {
|
||||||
|
@ -658,6 +651,85 @@ func getResourceLabel(parts []string) (string, bool) {
|
||||||
return parts[1], true
|
return parts[1], true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transformCountPseudoAttribute deals with the .count pseudo-attributes
|
||||||
|
// that 0.11 and prior allowed for resources. These no longer exist,
|
||||||
|
// because they don't do anything we can't do with the length(...) function.
|
||||||
|
func transformCountPseudoAttribute(buf *bytes.Buffer, parts []string, an *analysis) (transformed bool) {
|
||||||
|
if len(parts) > 0 {
|
||||||
|
var rAddr addrs.Resource
|
||||||
|
switch parts[0] {
|
||||||
|
case "data":
|
||||||
|
if len(parts) == 4 && parts[3] == "count" {
|
||||||
|
rAddr.Mode = addrs.DataResourceMode
|
||||||
|
rAddr.Type = parts[1]
|
||||||
|
rAddr.Name = parts[2]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if len(parts) == 3 && parts[2] == "count" {
|
||||||
|
rAddr.Mode = addrs.ManagedResourceMode
|
||||||
|
rAddr.Type = parts[0]
|
||||||
|
rAddr.Name = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We need to check if the thing being referenced is actually an
|
||||||
|
// existing resource, because other three-part traversals might
|
||||||
|
// coincidentally end with "count".
|
||||||
|
if hasCount, exists := an.ResourceHasCount[rAddr]; exists {
|
||||||
|
if hasCount {
|
||||||
|
buf.WriteString("length(")
|
||||||
|
buf.WriteString(rAddr.String())
|
||||||
|
buf.WriteString(")")
|
||||||
|
} else {
|
||||||
|
// If the resource does not have count, the .count
|
||||||
|
// attr would've always returned 1 before.
|
||||||
|
buf.WriteString("1")
|
||||||
|
}
|
||||||
|
transformed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func printHilTraversalPartsAsHcl2(buf *bytes.Buffer, parts []string) {
|
||||||
|
first, remain := parts[0], parts[1:]
|
||||||
|
buf.WriteString(first)
|
||||||
|
seenSplat := false
|
||||||
|
for _, part := range remain {
|
||||||
|
if part == "*" {
|
||||||
|
seenSplat = true
|
||||||
|
buf.WriteString(".*")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other special cases apply only if we've not previously
|
||||||
|
// seen a splat expression marker, since attribute vs. index
|
||||||
|
// syntax have different interpretations after a simple splat.
|
||||||
|
if !seenSplat {
|
||||||
|
if v, err := strconv.Atoi(part); err == nil {
|
||||||
|
// Looks like it's old-style index traversal syntax foo.0.bar
|
||||||
|
// so we'll replace with canonical index syntax foo[0].bar.
|
||||||
|
fmt.Fprintf(buf, "[%d]", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !hcl2syntax.ValidIdentifier(part) {
|
||||||
|
// This should be rare since HIL's identifier syntax is _close_
|
||||||
|
// to HCL2's, but we'll get here if one of the intervening
|
||||||
|
// parts is not a valid identifier in isolation, since HIL
|
||||||
|
// did not consider these to be separate identifiers.
|
||||||
|
// e.g. foo.1bar would be invalid in HCL2; must instead be foo["1bar"].
|
||||||
|
buf.WriteByte('[')
|
||||||
|
printQuotedString(buf, part)
|
||||||
|
buf.WriteByte(']')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte('.')
|
||||||
|
buf.WriteString(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func upgradeHeredocBody(buf *bytes.Buffer, val *hilast.Output, filename string, an *analysis) tfdiags.Diagnostics {
|
func upgradeHeredocBody(buf *bytes.Buffer, val *hilast.Output, filename string, an *analysis) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package configupgrade
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -286,6 +287,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
flag.Parse()
|
||||||
if testing.Verbose() {
|
if testing.Verbose() {
|
||||||
// if we're verbose, use the logging requested by TF_LOG
|
// if we're verbose, use the logging requested by TF_LOG
|
||||||
logging.SetOutput()
|
logging.SetOutput()
|
||||||
|
|
Loading…
Reference in New Issue