diff --git a/builtin/providers/test/resource_config_mode_test.go b/builtin/providers/test/resource_config_mode_test.go index a63de5c61..743e8e9e9 100644 --- a/builtin/providers/test/resource_config_mode_test.go +++ b/builtin/providers/test/resource_config_mode_test.go @@ -43,6 +43,41 @@ resource "test_resource_config_mode" "foo" { }, resource.TestStep{ Config: strings.TrimSpace(` +resource "test_resource_config_mode" "foo" { + # Due to a preprocessing fixup we do in lang.EvalBlock, it's allowed + # to specify resource_as_attr members using one or more nested blocks + # instead of attribute syntax, if desired. This should be equivalent + # to the previous config. + # + # This allowance is made for backward-compatibility with existing providers + # before Terraform v0.12 that were expecting nested block types to also + # support attribute syntax; it should not be used for any new use-cases. + resource_as_attr { + foo = "resource_as_attr 0" + } + resource_as_attr { + foo = "resource_as_attr 1" + } + resource_as_attr_dynamic = [ + { + foo = "resource_as_attr_dynamic 0" + }, + { + }, + ] +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "2"), + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0"), + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.1.foo", "resource_as_attr 1"), + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.#", "2"), + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.0.foo", "resource_as_attr_dynamic 0"), + resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.1.foo", "default"), + ), + }, + resource.TestStep{ + Config: strings.TrimSpace(` resource "test_resource_config_mode" "foo" { resource_as_attr = [ { diff --git a/lang/eval.go b/lang/eval.go index e27ff30e1..5ad1eef2a 100644 --- a/lang/eval.go +++ b/lang/eval.go @@ -5,12 +5,12 @@ import ( "log" "strconv" - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/hcl2/ext/dynblock" "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcldec" + "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/lang/blocktoattr" "github.com/hashicorp/terraform/tfdiags" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" @@ -58,6 +58,14 @@ func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, return cty.UnknownVal(schema.ImpliedType()), diags } + // HACK: In order to remain compatible with some assumptions made in + // Terraform v0.11 and earlier about the approximate equivalence of + // attribute vs. block syntax, we do a just-in-time fixup here to allow + // any attribute in the schema that has a list-of-objects or set-of-objects + // kind to potentially be populated instead by one or more nested blocks + // whose type is the attribute name. + body = blocktoattr.FixUpBlockAttrs(body, schema) + val, evalDiags := hcldec.Decode(body, spec, ctx) diags = diags.Append(evalDiags)