ext/dynblock: Make iterator variables visible to nested block for_each
Previously we were incorrectly passing down the original forEachCtx down to nested child blocks for recursive expansion. Instead, we must use the iteration-specific constructed EvalContext, which then allows any nested dynamic blocks to use the parent's iterator variable in their for_each or labels expressions, and thus unpack nested data structures into corresponding nested block structures: dynamic "parent" { for_each = [["a", "b"], []] content { dynamic "child" { for_each = parent.value content {} } } }
This commit is contained in:
parent
291f7fbe43
commit
6631d7cd0a
@ -234,7 +234,8 @@ func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks,
|
||||
}
|
||||
|
||||
func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
|
||||
ret := Expand(child, b.forEachCtx)
|
||||
chiCtx := i.EvalContext(b.forEachCtx)
|
||||
ret := Expand(child, chiCtx)
|
||||
ret.(*expandBody).iteration = i
|
||||
return ret
|
||||
}
|
||||
|
@ -145,6 +145,52 @@ func TestExpand(t *testing.T) {
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: "dynamic",
|
||||
Labels: []string{"b"},
|
||||
LabelRanges: []hcl.Range{hcl.Range{}},
|
||||
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
|
||||
"for_each": hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("dynamic c nested 0"),
|
||||
cty.StringVal("dynamic c nested 1"),
|
||||
}),
|
||||
})),
|
||||
"iterator": hcltest.MockExprVariable("dyn_b"),
|
||||
}),
|
||||
Blocks: hcl.Blocks{
|
||||
{
|
||||
Type: "content",
|
||||
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||
Blocks: hcl.Blocks{
|
||||
{
|
||||
Type: "dynamic",
|
||||
Labels: []string{"c"},
|
||||
LabelRanges: []hcl.Range{hcl.Range{}},
|
||||
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
|
||||
"for_each": hcltest.MockExprTraversalSrc("dyn_b.value"),
|
||||
}),
|
||||
Blocks: hcl.Blocks{
|
||||
{
|
||||
Type: "content",
|
||||
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
|
||||
"val0": hcltest.MockExprTraversalSrc("c.value"),
|
||||
"val1": hcltest.MockExprTraversalSrc("dyn_b.key"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: "a",
|
||||
Labels: []string{"static1"},
|
||||
@ -268,6 +314,16 @@ func TestExpand(t *testing.T) {
|
||||
"val1": cty.StringVal("dynamic b 1"),
|
||||
}),
|
||||
}),
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"val0": cty.StringVal("dynamic c nested 0"),
|
||||
"val1": cty.StringVal("foo"),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"val0": cty.StringVal("dynamic c nested 1"),
|
||||
"val1": cty.StringVal("foo"),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
if !got.RawEquals(want) {
|
||||
|
@ -41,11 +41,14 @@ func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Bloc
|
||||
eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx)
|
||||
diags = append(diags, eachDiags...)
|
||||
|
||||
if !eachVal.CanIterateElements() {
|
||||
if !eachVal.CanIterateElements() && eachVal.Type() != cty.DynamicPseudoType {
|
||||
// We skip this error for DynamicPseudoType because that means we either
|
||||
// have a null (which is checked immediately below) or an unknown
|
||||
// (which is handled in the expandBody Content methods).
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid dynamic for_each value",
|
||||
Detail: fmt.Sprintf("Cannot use a value of type %s in for_each. An iterable collection is required.", eachVal.Type()),
|
||||
Detail: fmt.Sprintf("Cannot use a %s value in for_each. An iterable collection is required.", eachVal.Type().FriendlyName()),
|
||||
Subject: eachAttr.Expr.Range().Ptr(),
|
||||
Expression: eachAttr.Expr,
|
||||
EvalContext: b.forEachCtx,
|
||||
|
@ -30,12 +30,14 @@ func (i *iteration) Object() cty.Value {
|
||||
|
||||
func (i *iteration) EvalContext(base *hcl.EvalContext) *hcl.EvalContext {
|
||||
new := base.NewChild()
|
||||
new.Variables = map[string]cty.Value{}
|
||||
|
||||
for name, otherIt := range i.Inherited {
|
||||
new.Variables[name] = otherIt.Object()
|
||||
if i != nil {
|
||||
new.Variables = map[string]cty.Value{}
|
||||
for name, otherIt := range i.Inherited {
|
||||
new.Variables[name] = otherIt.Object()
|
||||
}
|
||||
new.Variables[i.IteratorName] = i.Object()
|
||||
}
|
||||
new.Variables[i.IteratorName] = i.Object()
|
||||
|
||||
return new
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user