From 87786484ea85f0f102456c305667cf8f2544ef91 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 26 Mar 2019 17:27:14 -0700 Subject: [PATCH] lang/eval: Apply attr-as-nested-block fixup in EvalBlock For any block content we evaluate dynamically via this API, we'll make a special allowance for users to optionally write members of a list attribute instead as a sequence of nested blocks, thus allowing some existing provider features that were assuming this capability to continue to support it after v0.12. This should not be used for any new provider features, and should ideally be eventually phased out so that there aren't two similar-but-slightly-different syntaxes for saying the same thing. --- .../test/resource_config_mode_test.go | 35 +++++++++++++++++++ lang/eval.go | 12 +++++-- 2 files changed, 45 insertions(+), 2 deletions(-) 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)