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.
This commit is contained in:
Martin Atkins 2019-03-12 11:29:23 -07:00
parent 9d0d564ec7
commit 1c8150428f
2 changed files with 84 additions and 0 deletions

View File

@ -187,8 +187,21 @@ type Schema struct {
//
// 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
@ -811,6 +824,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 {

View File

@ -3924,6 +3924,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 {