Skip to content

Commit

Permalink
ext/dynblock: Make iterator variables visible to nested block for_each
Browse files Browse the repository at this point in the history
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 {}
        }
      }
    }
  • Loading branch information
apparentlymart committed Dec 20, 2018
1 parent 291f7fb commit 6631d7c
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 7 deletions.
3 changes: 2 additions & 1 deletion ext/dynblock/expand_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
56 changes: 56 additions & 0 deletions ext/dynblock/expand_body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions ext/dynblock/expand_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions ext/dynblock/iteration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 6631d7c

Please sign in to comment.