Skip to content

Commit

Permalink
decode unknown blocks to ensure they are valid
Browse files Browse the repository at this point in the history
Always attempt to decode dynamic blocks to provide validation. If the
iterator is unknown the value will be discarded, but the diagnostics are
still useful to unsure the structure is correct.
  • Loading branch information
jbardin committed Jul 7, 2021
1 parent 2eb4e9f commit abe9c89
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 24 deletions.
22 changes: 4 additions & 18 deletions ext/dynblock/expand_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,28 +201,14 @@ func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks,
}
}
} else {
// If our top-level iteration value isn't known then we're forced
// to compromise since HCL doesn't have any concept of an
// "unknown block". In this case then, we'll produce a single
// dynamic block with the iterator values set to DynamicVal,
// which at least makes the potential for a block visible
// in our result, even though it's not represented in a fully-accurate
// way.
// If our top-level iteration value isn't known then we
// substitute an unknownBody, which will cause the entire block
// to evaluate to an unknown value.
i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
block, blockDiags := spec.newBlock(i, b.forEachCtx)
diags = append(diags, blockDiags...)
if block != nil {
block.Body = b.expandChild(block.Body, i)

// We additionally force all of the leaf attribute values
// in the result to be unknown so the calling application
// can, if necessary, use that as a heuristic to detect
// when a single nested block might be standing in for
// multiple blocks yet to be expanded. This retains the
// structure of the generated body but forces all of its
// leaf attribute values to be unknown.
block.Body = unknownBody{block.Body}

block.Body = unknownBody{b.expandChild(block.Body, i)}
blocks = append(blocks, block)
}
}
Expand Down
46 changes: 46 additions & 0 deletions ext/dynblock/expand_body_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynblock

import (
"strings"
"testing"

"github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -441,6 +442,28 @@ func TestExpandUnknownBodies(t *testing.T) {
},
}),
},
{
Type: "dynamic",
Labels: []string{"invalid_list"},
LabelRanges: []hcl.Range{hcl.Range{}},
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))),
}),
Blocks: hcl.Blocks{
{
Type: "content",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"val": hcltest.MockExprTraversalSrc("each.value"),
// unexpected attributes should still produce an error
"invalid": hcltest.MockExprLiteral(cty.StringVal("static")),
}),
}),
},
},
}),
},
},
}

Expand Down Expand Up @@ -574,4 +597,27 @@ func TestExpandUnknownBodies(t *testing.T) {
}
})

t.Run("DecodeInvalidList", func(t *testing.T) {
decSpec := &hcldec.BlockListSpec{
TypeName: "invalid_list",
Nested: &hcldec.ObjectSpec{
"val": &hcldec.AttrSpec{
Name: "val",
Type: cty.String,
},
},
}

_, _, diags := hcldec.PartialDecode(dynBody, decSpec, nil)
if len(diags) != 1 {
t.Error("expected 1 extraneous argument")
}

want := `Mock body has extraneous argument "invalid"`

if !strings.Contains(diags.Error(), want) {
t.Errorf("unexpected diagnostics: %v", diags)
}
})

}
15 changes: 9 additions & 6 deletions hcldec/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe
continue
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
Expand All @@ -476,8 +479,6 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
}
Expand Down Expand Up @@ -629,6 +630,9 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
continue
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
Expand All @@ -637,8 +641,6 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
}
Expand Down Expand Up @@ -751,6 +753,9 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
continue
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
Expand All @@ -759,8 +764,6 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
}
Expand Down

0 comments on commit abe9c89

Please sign in to comment.