lang: Detect references when a list/set attr is defined using blocks
For compatibility with documented patterns from existing providers we are now allowing (via a pre-processing step) any attribute whose type is a list-of-object or set-of-object type to optionally be assigned using one or more blocks whose type is the attribute name. The pre-processing functionality was implemented in previous commits but we were not correctly detecting references within these blocks that are, from the perspective of the primary schema, invalid. Now we'll use an alternative implementation of variable detection that is able to apply the same schema rewriting technique we used to implement the transform and thus can find all of the references as if they were already in their final locations.
This commit is contained in:
parent
8746e9e8ad
commit
003317d7c8
|
@ -7,6 +7,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func ambiguousNames(schema *configschema.Block) map[string]struct{} {
|
func ambiguousNames(schema *configschema.Block) map[string]struct{} {
|
||||||
|
if schema == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
ambiguousNames := make(map[string]struct{})
|
ambiguousNames := make(map[string]struct{})
|
||||||
for name, attrS := range schema.Attributes {
|
for name, attrS := range schema.Attributes {
|
||||||
aty := attrS.Type
|
aty := attrS.Type
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package lang
|
package lang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/ext/dynblock"
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/lang/blocktoattr"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,14 +51,21 @@ func ReferencesInBlock(body hcl.Body, schema *configschema.Block) ([]*addrs.Refe
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
spec := schema.DecoderSpec()
|
|
||||||
|
|
||||||
// We use dynblock.VariablesHCLDec instead of hcldec.Variables here because
|
// We use blocktoattr.ExpandedVariables instead of hcldec.Variables or
|
||||||
// when we evaluate a block we'll apply the HCL dynamic block extension
|
// dynblock.VariablesHCLDec here because when we evaluate a block we'll
|
||||||
// expansion to it first, and so we need this specialized version in order
|
// first apply the dynamic block extension and _then_ the blocktoattr
|
||||||
// to properly understand what the dependencies will be once expanded.
|
// transform, and so blocktoattr.ExpandedVariables takes into account
|
||||||
// Otherwise, we'd miss references that only occur inside dynamic blocks.
|
// both of those transforms when it analyzes the body to ensure we find
|
||||||
traversals := dynblock.VariablesHCLDec(body, spec)
|
// all of the references as if they'd already moved into their final
|
||||||
|
// locations, even though we can't expand dynamic blocks yet until we
|
||||||
|
// already know which variables are required.
|
||||||
|
//
|
||||||
|
// The set of cases we want to detect here is covered by the tests for
|
||||||
|
// the plan graph builder in the main 'terraform' package, since it's
|
||||||
|
// in a better position to test this due to having mock providers etc
|
||||||
|
// available.
|
||||||
|
traversals := blocktoattr.ExpandedVariables(body, schema)
|
||||||
return References(traversals)
|
return References(traversals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,80 @@ test_thing.c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
|
||||||
|
provider := &MockProvider{
|
||||||
|
GetSchemaReturn: &ProviderSchema{
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_thing": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Computed: true},
|
||||||
|
"nested": {
|
||||||
|
Type: cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"foo": cty.String,
|
||||||
|
})),
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
components := &basicComponentFactory{
|
||||||
|
providers: map[string]providers.Factory{
|
||||||
|
"test": providers.FactoryFixed(provider),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &PlanGraphBuilder{
|
||||||
|
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
||||||
|
Components: components,
|
||||||
|
Schemas: &Schemas{
|
||||||
|
Providers: map[string]*ProviderSchema{
|
||||||
|
"test": provider.GetSchemaReturn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DisableReduce: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
||||||
|
t.Fatalf("wrong module path %q", g.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test is here to make sure we properly detect references inside
|
||||||
|
// the "nested" block that is actually defined in the schema as a
|
||||||
|
// list-of-objects attribute. This requires some special effort
|
||||||
|
// inside lang.ReferencesInBlock to make sure it searches blocks of
|
||||||
|
// type "nested" along with an attribute named "nested".
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
meta.count-boundary (EachMode fixup)
|
||||||
|
provider.test
|
||||||
|
test_thing.a
|
||||||
|
test_thing.b
|
||||||
|
provider.test
|
||||||
|
provider.test (close)
|
||||||
|
provider.test
|
||||||
|
test_thing.a
|
||||||
|
test_thing.b
|
||||||
|
root
|
||||||
|
meta.count-boundary (EachMode fixup)
|
||||||
|
provider.test (close)
|
||||||
|
test_thing.a
|
||||||
|
provider.test
|
||||||
|
test_thing.b
|
||||||
|
provider.test
|
||||||
|
test_thing.a
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPlanGraphBuilder_targetModule(t *testing.T) {
|
func TestPlanGraphBuilder_targetModule(t *testing.T) {
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-target-module-provider"),
|
Config: testModule(t, "graph-builder-plan-target-module-provider"),
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
resource "test_thing" "a" {
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_thing" "b" {
|
||||||
|
nested {
|
||||||
|
foo = test_thing.a.id
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue