-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix(terraform): evaluateStep
to correctly set EvalContext
for multiple instances of blocks
#8555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
be674f7
b43cac8
6921256
04605e2
0103bd8
ce933c9
6fe1164
332c4ad
8f8e652
7271d20
271dd05
fbaa5b9
a16013e
91ded49
31084ec
bac0ac4
f96d147
a6d4c97
d2fff87
3ac5bfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package parser | ||
|
||
import "github.com/zclconf/go-cty/cty" | ||
|
||
// insertTupleElement inserts a value into a tuple at the specified index. | ||
// If the idx is outside the bounds of the list, it grows the tuple to | ||
// the new size, and fills in `cty.NilVal` for the missing elements. | ||
// | ||
// This function will not panic. If the list value is not a list, it will | ||
// be replaced with an empty list. | ||
func insertTupleElement(list cty.Value, idx int, val cty.Value) cty.Value { | ||
if list.IsNull() || !list.Type().IsTupleType() { | ||
// better than a panic | ||
list = cty.EmptyTupleVal | ||
} | ||
|
||
if idx < 0 { | ||
// Nothing to do? | ||
return list | ||
} | ||
|
||
newList := make([]cty.Value, max(idx+1, list.LengthInt())) | ||
for i := 0; i < len(newList); i++ { | ||
newList[i] = cty.NilVal // Always insert a nil by default | ||
|
||
if i < list.LengthInt() { // keep the original | ||
newList[i] = list.Index(cty.NumberIntVal(int64(i))) | ||
} | ||
|
||
if i == idx { // add the new value | ||
newList[i] = val | ||
} | ||
} | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return cty.TupleVal(newList) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package parser | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
func Test_insertTupleElement(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
start cty.Value | ||
index int | ||
value cty.Value | ||
want cty.Value | ||
}{ | ||
{ | ||
name: "empty", | ||
start: cty.Value{}, | ||
index: 0, | ||
value: cty.NilVal, | ||
want: cty.TupleVal([]cty.Value{cty.NilVal}), | ||
}, | ||
{ | ||
name: "empty to length", | ||
start: cty.Value{}, | ||
index: 2, | ||
value: cty.NilVal, | ||
want: cty.TupleVal([]cty.Value{cty.NilVal, cty.NilVal, cty.NilVal}), | ||
}, | ||
{ | ||
name: "insert to empty", | ||
start: cty.EmptyTupleVal, | ||
index: 1, | ||
value: cty.NumberIntVal(5), | ||
want: cty.TupleVal([]cty.Value{cty.NilVal, cty.NumberIntVal(5)}), | ||
}, | ||
{ | ||
name: "insert to existing", | ||
start: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
index: 1, | ||
value: cty.NumberIntVal(5), | ||
want: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5), cty.NumberIntVal(3)}), | ||
}, | ||
{ | ||
name: "insert to existing, extends", | ||
start: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
index: 4, | ||
value: cty.NumberIntVal(5), | ||
want: cty.TupleVal([]cty.Value{ | ||
cty.NumberIntVal(1), cty.NumberIntVal(2), | ||
cty.NumberIntVal(3), cty.NilVal, | ||
cty.NumberIntVal(5), | ||
}), | ||
}, | ||
{ | ||
name: "mixed list", | ||
start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
index: 1, | ||
value: cty.BoolVal(true), | ||
want: cty.TupleVal([]cty.Value{ | ||
cty.StringVal("a"), cty.BoolVal(true), cty.NumberIntVal(3), | ||
}), | ||
}, | ||
{ | ||
name: "replace end", | ||
start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
index: 2, | ||
value: cty.StringVal("end"), | ||
want: cty.TupleVal([]cty.Value{ | ||
cty.StringVal("a"), cty.NumberIntVal(2), cty.StringVal("end"), | ||
}), | ||
}, | ||
|
||
// Some bad arguments | ||
{ | ||
name: "negative index", | ||
start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
index: -1, | ||
value: cty.BoolVal(true), | ||
want: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), | ||
}, | ||
{ | ||
name: "non-list", | ||
start: cty.BoolVal(true), | ||
index: 1, | ||
value: cty.BoolVal(true), | ||
want: cty.TupleVal([]cty.Value{cty.NilVal, cty.BoolVal(true)}), | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
tt := tt | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. capturing loop variable is no longer required https://go.dev/blog/loopvar-preview There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL ❤️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Emyrk can we remove this then? |
||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
got := insertTupleElement(tt.start, tt.index, tt.value) | ||
require.True(t, got.Equals(tt.want).True(), "expected %s, got %s", tt.want, got) | ||
}) | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -524,7 +524,6 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { | |
values := make(map[string]cty.Value) | ||
|
||
for _, b := range blocksOfType { | ||
|
||
switch b.Type() { | ||
case "variable": // variables are special in that their value comes from the "default" attribute | ||
val, err := e.evaluateVariable(b) | ||
|
@@ -552,6 +551,11 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { | |
continue | ||
} | ||
|
||
// Data blocks should all be loaded into the top level 'values' | ||
// object. The hierarchy of the map is: | ||
// values = map[<type>]map[<name>] = Block | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Where Block is the block's attributes as a cty.Object. | ||
|
||
blockMap, ok := values[b.Labels()[0]] | ||
if !ok { | ||
values[b.Labels()[0]] = cty.ObjectVal(make(map[string]cty.Value)) | ||
|
@@ -563,7 +567,29 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { | |
valueMap = make(map[string]cty.Value) | ||
} | ||
|
||
// If the block reference has a key, that means there is multiple instances of | ||
// this block. Example if `count` is used on a block, key will be a 'cty.Number', | ||
// and the saved value should be a tuple. | ||
ref := b.Reference() | ||
rawKey := ref.RawKey() | ||
if rawKey.Type().Equals(cty.Number) { | ||
existing := valueMap[ref.NameLabel()] | ||
asInt, _ := rawKey.AsBigFloat().Int64() | ||
|
||
valueMap[ref.NameLabel()] = insertTupleElement(existing, int(asInt), b.Values()) | ||
} | ||
|
||
// The default behavior, that being no key or an unimplemented key type, is to | ||
// save the block as the name value. The name label value contains the key. | ||
// | ||
// Eg: If the block has the index '4', the string [4] is contained | ||
// within 'b.Labels()[1]' | ||
// TODO: This is always done to maintain backwards compatibility. | ||
// I think this can be omitted if the switch statement above | ||
// sets the appropriate tuple value in the valueMap. | ||
valueMap[b.Labels()[1]] = b.Values() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really think if this In an abundance of caution, I kept the previous context setting as well. The previous context is incorrect for an hcl traversal though. So I feel like it was never being hit. How the code would look if we remove the current behavior entirely. if rawKey.Type().Equals(cty.Number) {
existing := valueMap[ref.NameLabel()]
asInt, _ := rawKey.AsBigFloat().Int64()
valueMap[ref.NameLabel()] = insertTupleElement(existing, int(asInt), b.Values())
} else {
valueMap[b.Labels()[1]] = b.Values()
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the old behavior should be removed after adding the handling of all cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nikpivkin by "all the cases", do you mean handling |
||
|
||
// Update the map of all blocks with the same type. | ||
values[b.Labels()[0]] = cty.ObjectVal(valueMap) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// The order of references and their alphabetical order is important. | ||
// d -> b -> c | ||
|
||
data "d" "foo"{ | ||
count = 1 | ||
value = "Index ${count.index}" | ||
} | ||
|
||
data "b" "foo" { | ||
count = 1 | ||
value = data.d.foo[0].value | ||
} | ||
|
||
data "c" "cfoo" { | ||
count = 1 | ||
value = data.b.foo[0].value | ||
} | ||
|
||
|
||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.