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 {
|
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
|
ret.(*expandBody).iteration = i
|
||||||
return ret
|
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",
|
Type: "a",
|
||||||
Labels: []string{"static1"},
|
Labels: []string{"static1"},
|
||||||
@ -268,6 +314,16 @@ func TestExpand(t *testing.T) {
|
|||||||
"val1": cty.StringVal("dynamic b 1"),
|
"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) {
|
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)
|
eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx)
|
||||||
diags = append(diags, eachDiags...)
|
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{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid dynamic for_each value",
|
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(),
|
Subject: eachAttr.Expr.Range().Ptr(),
|
||||||
Expression: eachAttr.Expr,
|
Expression: eachAttr.Expr,
|
||||||
EvalContext: b.forEachCtx,
|
EvalContext: b.forEachCtx,
|
||||||
|
@ -30,12 +30,14 @@ func (i *iteration) Object() cty.Value {
|
|||||||
|
|
||||||
func (i *iteration) EvalContext(base *hcl.EvalContext) *hcl.EvalContext {
|
func (i *iteration) EvalContext(base *hcl.EvalContext) *hcl.EvalContext {
|
||||||
new := base.NewChild()
|
new := base.NewChild()
|
||||||
new.Variables = map[string]cty.Value{}
|
|
||||||
|
|
||||||
|
if i != nil {
|
||||||
|
new.Variables = map[string]cty.Value{}
|
||||||
for name, otherIt := range i.Inherited {
|
for name, otherIt := range i.Inherited {
|
||||||
new.Variables[name] = otherIt.Object()
|
new.Variables[name] = otherIt.Object()
|
||||||
}
|
}
|
||||||
new.Variables[i.IteratorName] = i.Object()
|
new.Variables[i.IteratorName] = i.Object()
|
||||||
|
}
|
||||||
|
|
||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user