ext/dynblock: ForEachVariablesHCLDec helper
For applications already using hcldec, a decoder specification can be used to automatically drive the recursive variable detection walk that begins with WalkForEachVariables, allowing all "for_each" and "labels" variables in a recursive block structure to be detected in a single call.
This commit is contained in:
parent
130b3c5105
commit
d6fc633aa0
@ -103,7 +103,7 @@ requires that the caller be able to look up a schema given a nested block type.
|
|||||||
For _simple_ formats where a specific block type name always has the same schema
|
For _simple_ formats where a specific block type name always has the same schema
|
||||||
regardless of context, a walk can be implemented as follows:
|
regardless of context, a walk can be implemented as follows:
|
||||||
|
|
||||||
```
|
```go
|
||||||
func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal {
|
func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal {
|
||||||
vars, children := node.Visit(schema)
|
vars, children := node.Visit(schema)
|
||||||
|
|
||||||
@ -139,6 +139,42 @@ func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hc
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Detecting Variables with `hcldec` Specifications
|
||||||
|
|
||||||
|
For applications that use the higher-level `hcldec` package to decode nested
|
||||||
|
configuration structures into `cty` values, the same specification can be used
|
||||||
|
to automatically drive the recursive variable-detection walk described above.
|
||||||
|
|
||||||
|
The helper function `ForEachVariablesHCLDec` allows an entire recursive
|
||||||
|
configuration structure to be analyzed in a single call given a `hcldec.Spec`
|
||||||
|
that describes the nested block structure. This means a `hcldec`-based
|
||||||
|
application can support dynamic blocks with only a little additional effort:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func decodeBody(body hcl.Body, spec hcldec.Spec) (cty.Value, hcl.Diagnostics) {
|
||||||
|
// Determine which variables are needed to expand dynamic blocks
|
||||||
|
neededForDynamic := dynblock.ForEachVariablesHCLDec(body, spec)
|
||||||
|
|
||||||
|
// Build a suitable EvalContext and expand dynamic blocks
|
||||||
|
dynCtx := buildEvalContext(neededForDynamic)
|
||||||
|
dynBody := dynblock.Expand(body, dynCtx)
|
||||||
|
|
||||||
|
// Determine which variables are needed to fully decode the expanded body
|
||||||
|
// This will analyze expressions that came both from static blocks in the
|
||||||
|
// original body and from blocks that were dynamically added by Expand.
|
||||||
|
neededForDecode := hcldec.Variables(dynBody, spec)
|
||||||
|
|
||||||
|
// Build a suitable EvalContext and then fully decode the body as per the
|
||||||
|
// hcldec specification.
|
||||||
|
decCtx := buildEvalContext(neededForDecode)
|
||||||
|
return hcldec.Decode(dynBody, spec, decCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildEvalContext(needed []hcl.Traversal) *hcl.EvalContext {
|
||||||
|
// (to be implemented by your application)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Performance
|
# Performance
|
||||||
|
|
||||||
This extension is going quite harshly against the grain of the HCL API, and
|
This extension is going quite harshly against the grain of the HCL API, and
|
||||||
|
33
ext/dynblock/variables_hcldec.go
Normal file
33
ext/dynblock/variables_hcldec.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package dynblock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/hashicorp/hcl2/hcldec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ForEachVariablesHCLDec is a wrapper around WalkForEachVariables that
|
||||||
|
// uses the given hcldec specification to automatically drive the recursive
|
||||||
|
// walk through nested blocks in the given body.
|
||||||
|
//
|
||||||
|
// This provides more convenient access to all of the "for_each" and "labels"
|
||||||
|
// dependencies in a body for applications that are already using hcldec
|
||||||
|
// as a more convenient way to recursively decode body contents.
|
||||||
|
func ForEachVariablesHCLDec(body hcl.Body, spec hcldec.Spec) []hcl.Traversal {
|
||||||
|
rootNode := WalkForEachVariables(body)
|
||||||
|
return walkVariablesWithHCLDec(rootNode, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkVariablesWithHCLDec(node WalkVariablesNode, spec hcldec.Spec) []hcl.Traversal {
|
||||||
|
vars, children := node.Visit(hcldec.ImpliedSchema(spec))
|
||||||
|
|
||||||
|
if len(children) > 0 {
|
||||||
|
childSpecs := hcldec.ChildBlockTypes(spec)
|
||||||
|
for _, child := range children {
|
||||||
|
if childSpec, exists := childSpecs[child.BlockTypeName]; exists {
|
||||||
|
vars = append(vars, walkVariablesWithHCLDec(child.Node, childSpec)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
package dynblock
|
package dynblock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl2/hcldec"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
@ -77,15 +79,18 @@ dynamic "a" {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rootNode := WalkForEachVariables(f.Body)
|
spec := &hcldec.BlockListSpec{
|
||||||
traversals := testWalkAndAccumVars(rootNode, &hcl.BodySchema{
|
TypeName: "a",
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Nested: &hcldec.BlockListSpec{
|
||||||
{
|
TypeName: "b",
|
||||||
Type: "a",
|
Nested: &hcldec.AttrSpec{
|
||||||
|
Name: "val",
|
||||||
|
Type: cty.String,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
traversals := ForEachVariablesHCLDec(f.Body, spec)
|
||||||
got := make([]string, len(traversals))
|
got := make([]string, len(traversals))
|
||||||
for i, traversal := range traversals {
|
for i, traversal := range traversals {
|
||||||
got[i] = traversal.RootName()
|
got[i] = traversal.RootName()
|
||||||
@ -112,39 +117,3 @@ dynamic "a" {
|
|||||||
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWalkAndAccumVars(node WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal {
|
|
||||||
vars, children := node.Visit(schema)
|
|
||||||
|
|
||||||
for _, child := range children {
|
|
||||||
var childSchema *hcl.BodySchema
|
|
||||||
switch child.BlockTypeName {
|
|
||||||
case "a":
|
|
||||||
childSchema = &hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "b",
|
|
||||||
LabelNames: []string{"key"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case "b":
|
|
||||||
childSchema = &hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "val",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Should never happen, because we have no other block types
|
|
||||||
// in our test input.
|
|
||||||
panic(fmt.Errorf("can't find schema for unknown block type %q", child.BlockTypeName))
|
|
||||||
}
|
|
||||||
|
|
||||||
vars = append(vars, testWalkAndAccumVars(child.Node, childSchema)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vars
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user