From 7ffa79895e2caf5144d305a97b900134c19f1f21 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 12 Mar 2019 11:29:23 -0700 Subject: [PATCH] helper/schema: Schema.AsSingle flag This setting indicates that an attribute defined as TypeList or TypeSet should be presented to Terraform Core as a single value instead when running in Terraform v0.12 or later. It has no effect for Terraform v0.10 or v0.11. This commit just introduces the setting without any associated behavior, so it can be included in both the v0.12 and v0.11 branches. A subsequent commit only to the v0.12 branch will introduce the behavior as part of the protocol version 5 shims. --- helper/schema/schema.go | 40 +++++++++++++++++++---- helper/schema/schema_test.go | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 2ac38803aa..3e5358b0d6 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -162,13 +162,30 @@ type Schema struct { // used to wrap a complex structure, however less than one instance would // cause instability. // - // PromoteSingle, if true, will allow single elements to be standalone - // and promote them to a list. For example "foo" would be promoted to - // ["foo"] automatically. This is primarily for legacy reasons and the - // ambiguity is not recommended for new usage. Promotion is only allowed - // for primitive element types. - MaxItems int - MinItems int + // If the field Optional is set to true then MinItems is ignored and thus + // effectively zero. + // + // If MaxItems is 1, you may optionally also set AsSingle in order to have + // Terraform v0.12 or later treat a TypeList or TypeSet as if it were a + // single value. It will remain a list or set in Terraform v0.10 and v0.11. + // Enabling this for an existing attribute after you've made at least one + // v0.12-compatible provider release is a breaking change. AsSingle is + // likely to misbehave when used with deeply-nested set structures due to + // the imprecision of set diffs, so be sure to test it thoroughly, + // including updates that change the set members at all levels. AsSingle + // exists primarily to be used in conjunction with ConfigMode when forcing + // a nested resource to be treated as an attribute, so it can be considered + // an attribute of object type rather than of list/set of object. + MaxItems int + MinItems int + AsSingle bool + + // PromoteSingle originally allowed for a single element to be assigned + // where a primitive list was expected, but this no longer works from + // Terraform v0.12 onwards (Terraform Core will require a list to be set + // regardless of what this is set to) and so only applies to Terraform v0.11 + // and earlier, and so should be used only to retain this functionality + // for those still using v0.11 with a provider that formerly used this. PromoteSingle bool // The following fields are only valid for a TypeSet type. @@ -775,6 +792,15 @@ func (m schemaMap) internalValidate(topSchemaMap schemaMap, attrsOnly bool) erro } } + if v.AsSingle { + if v.MaxItems != 1 { + return fmt.Errorf("%s: MaxItems must be 1 when AsSingle is set", k) + } + if v.Type != TypeList && v.Type != TypeSet { + return fmt.Errorf("%s: AsSingle can be used only with TypeList and TypeSet schemas", k) + } + } + // Computed-only field if v.Computed && !v.Optional { if v.ValidateFunc != nil { diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 52d2a1961a..938681cb7a 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -3890,6 +3890,68 @@ func TestSchemaMap_InternalValidate(t *testing.T) { }, true, // in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute }, + + "AsSingle okay": { + map[string]*Schema{ + "block": &Schema{ + Type: TypeList, + Optional: true, + MaxItems: 1, + AsSingle: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "sub": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{}, + }, + }, + }, + }, + }, + false, + }, + + "AsSingle without MaxItems": { + map[string]*Schema{ + "block": &Schema{ + Type: TypeList, + Optional: true, + AsSingle: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "sub": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{}, + }, + }, + }, + }, + }, + true, // MaxItems must be 1 when AsSingle is set + }, + + "AsSingle on primitive type": { + map[string]*Schema{ + "block": &Schema{ + Type: TypeString, + Optional: true, + MaxItems: 1, + AsSingle: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "sub": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{}, + }, + }, + }, + }, + }, + true, // Unexpected error occurred: block: MaxItems and MinItems are only supported on lists or sets + }, } for tn, tc := range cases {