summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorMartin Atkins <mart@degeneration.co.uk>2018-01-21 18:24:00 -0800
committerMartin Atkins <mart@degeneration.co.uk>2018-01-27 09:10:18 -0800
commitd6fc633aa01bb8ae988ce5d013f79c4b97bfdd96 (patch)
tree02ce89b0ffabaac93e1117a8bb10988fe583041a /ext
parent130b3c5105e02268861d448a32ead57e99af3fcd (diff)
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.
Diffstat (limited to 'ext')
-rw-r--r--ext/dynblock/README.md38
-rw-r--r--ext/dynblock/variables_hcldec.go33
-rw-r--r--ext/dynblock/variables_test.go55
3 files changed, 82 insertions, 44 deletions
diff --git a/ext/dynblock/README.md b/ext/dynblock/README.md
index 91e22e0..2b24fdb 100644
--- a/ext/dynblock/README.md
+++ b/ext/dynblock/README.md
@@ -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
regardless of context, a walk can be implemented as follows:
-```
+```go
func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal {
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
This extension is going quite harshly against the grain of the HCL API, and
diff --git a/ext/dynblock/variables_hcldec.go b/ext/dynblock/variables_hcldec.go
new file mode 100644
index 0000000..480873a
--- /dev/null
+++ b/ext/dynblock/variables_hcldec.go
@@ -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
+}
diff --git a/ext/dynblock/variables_test.go b/ext/dynblock/variables_test.go
index 372c61a..83748ef 100644
--- a/ext/dynblock/variables_test.go
+++ b/ext/dynblock/variables_test.go
@@ -1,10 +1,12 @@
package dynblock
import (
- "fmt"
"reflect"
"testing"
+ "github.com/hashicorp/hcl2/hcldec"
+ "github.com/zclconf/go-cty/cty"
+
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl2/hcl"
@@ -77,15 +79,18 @@ dynamic "a" {
return
}
- rootNode := WalkForEachVariables(f.Body)
- traversals := testWalkAndAccumVars(rootNode, &hcl.BodySchema{
- Blocks: []hcl.BlockHeaderSchema{
- {
- Type: "a",
+ spec := &hcldec.BlockListSpec{
+ TypeName: "a",
+ Nested: &hcldec.BlockListSpec{
+ TypeName: "b",
+ Nested: &hcldec.AttrSpec{
+ Name: "val",
+ Type: cty.String,
},
},
- })
+ }
+ traversals := ForEachVariablesHCLDec(f.Body, spec)
got := make([]string, len(traversals))
for i, traversal := range traversals {
got[i] = traversal.RootName()
@@ -112,39 +117,3 @@ dynamic "a" {
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
-}