f9f92da699
Our API previously had a function only for retrieving the variables used in the for_each and labels arguments used during an Expand call, and expected callers to then interrogate the resulting expanded block to find the other variables required to fully decode the content. That approach is insufficient for any application that needs to know the full set of required variables before any evaluation begins, such as when a dependency graph will be constructed to allow a topological traversal through blocks while evaluating. Now we have WalkVariables, which finds both the variables used to expand _and_ the variables within any blocks. This also renames WalkForEachVariables to WalkExpandVariables since that name is more accurate with the addition of the "label" argument into the expand-time dependency set. There is also a hcldec-based helper wrapper for each of those, allowing single-shot analysis of blocks for applications that use hcldec. This is a breaking change to the dynblock package API, because the old WalkForEachVariables and ForEachVariablesHCLDec functions are no longer present.
156 lines
4.2 KiB
Go
156 lines
4.2 KiB
Go
package dynblock
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl2/hcldec"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
|
)
|
|
|
|
func TestVariables(t *testing.T) {
|
|
const src = `
|
|
|
|
# We have some references to things inside the "val" attribute inside each
|
|
# of our "b" blocks, which should be included in the result of WalkVariables
|
|
# but not WalkExpandVariables.
|
|
|
|
a {
|
|
dynamic "b" {
|
|
for_each = [for i, v in some_list_0: "${i}=${v},${baz}"]
|
|
labels = ["${b.value} ${something_else_0}"]
|
|
content {
|
|
val = "${b.value} ${something_else_1}"
|
|
}
|
|
}
|
|
}
|
|
|
|
dynamic "a" {
|
|
for_each = some_list_1
|
|
|
|
content {
|
|
b "foo" {
|
|
val = "${a.value} ${something_else_2}"
|
|
}
|
|
|
|
dynamic "b" {
|
|
for_each = some_list_2
|
|
iterator = dyn_b
|
|
labels = ["${a.value} ${dyn_b.value} ${b} ${something_else_3}"]
|
|
content {
|
|
val = "${a.value} ${dyn_b.value} ${something_else_4}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dynamic "a" {
|
|
for_each = some_list_3
|
|
iterator = dyn_a
|
|
|
|
content {
|
|
b "foo" {
|
|
val = "${dyn_a.value} ${something_else_5}"
|
|
}
|
|
|
|
dynamic "b" {
|
|
for_each = some_list_4
|
|
labels = ["${dyn_a.value} ${b.value} ${a} ${something_else_6}"]
|
|
content {
|
|
val = "${dyn_a.value} ${b.value} ${something_else_7}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
f, diags := hclsyntax.ParseConfig([]byte(src), "", hcl.Pos{})
|
|
if len(diags) != 0 {
|
|
t.Errorf("unexpected diagnostics during parse")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
return
|
|
}
|
|
|
|
spec := &hcldec.BlockListSpec{
|
|
TypeName: "a",
|
|
Nested: &hcldec.BlockMapSpec{
|
|
TypeName: "b",
|
|
LabelNames: []string{"key"},
|
|
Nested: &hcldec.AttrSpec{
|
|
Name: "val",
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("WalkVariables", func(t *testing.T) {
|
|
traversals := VariablesHCLDec(f.Body, spec)
|
|
got := make([]string, len(traversals))
|
|
for i, traversal := range traversals {
|
|
got[i] = traversal.RootName()
|
|
}
|
|
|
|
// The block structure is traversed one level at a time, so the ordering
|
|
// here is reflecting first a pass of the root, then the first child
|
|
// under the root, then the first child under that, etc.
|
|
want := []string{
|
|
"some_list_1",
|
|
"some_list_3",
|
|
"some_list_0",
|
|
"baz",
|
|
"something_else_0",
|
|
"something_else_1", // Would not be included for WalkExpandVariables because it only appears in content
|
|
"some_list_2",
|
|
"b", // This is correct because it is referenced in a context where the iterator is overridden to be dyn_b
|
|
"something_else_3",
|
|
"something_else_2", // Would not be included for WalkExpandVariables because it only appears in content
|
|
"something_else_4", // Would not be included for WalkExpandVariables because it only appears in content
|
|
"some_list_4",
|
|
"a", // This is correct because it is referenced in a context where the iterator is overridden to be dyn_a
|
|
"something_else_6",
|
|
"something_else_5", // Would not be included for WalkExpandVariables because it only appears in content
|
|
"something_else_7", // Would not be included for WalkExpandVariables because it only appears in content
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
|
}
|
|
})
|
|
|
|
t.Run("WalkExpandVariables", func(t *testing.T) {
|
|
traversals := ExpandVariablesHCLDec(f.Body, spec)
|
|
got := make([]string, len(traversals))
|
|
for i, traversal := range traversals {
|
|
got[i] = traversal.RootName()
|
|
}
|
|
|
|
// The block structure is traversed one level at a time, so the ordering
|
|
// here is reflecting first a pass of the root, then the first child
|
|
// under the root, then the first child under that, etc.
|
|
want := []string{
|
|
"some_list_1",
|
|
"some_list_3",
|
|
"some_list_0",
|
|
"baz",
|
|
"something_else_0",
|
|
"some_list_2",
|
|
"b", // This is correct because it is referenced in a context where the iterator is overridden to be dyn_b
|
|
"something_else_3",
|
|
"some_list_4",
|
|
"a", // This is correct because it is referenced in a context where the iterator is overridden to be dyn_a
|
|
"something_else_6",
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
|
}
|
|
})
|
|
}
|