diff --git a/helper/schema/core_schema.go b/helper/schema/core_schema.go index 9578baaf5..20146ec12 100644 --- a/helper/schema/core_schema.go +++ b/helper/schema/core_schema.go @@ -125,6 +125,20 @@ func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock { // blocks, but we can fake it by requiring at least one item. ret.MinItems = 1 } + if s.Optional && s.MinItems > 0 { + // Historically helper/schema would ignore MinItems if Optional were + // set, so we must mimic this behavior here to ensure that providers + // relying on that undocumented behavior can continue to operate as + // they did before. + ret.MinItems = 0 + } + if s.Computed && !s.Optional { + // MinItems/MaxItems are meaningless for computed nested blocks, since + // they are never set by the user anyway. This ensures that we'll never + // generate weird errors about them. + ret.MinItems = 0 + ret.MaxItems = 0 + } return ret } diff --git a/helper/schema/core_schema_test.go b/helper/schema/core_schema_test.go index 44dc9a8e0..d07c897e2 100644 --- a/helper/schema/core_schema_test.go +++ b/helper/schema/core_schema_test.go @@ -224,6 +224,90 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) { }, }), }, + "sub-resource collections minitems+optional": { + // This particular case is an odd one where the provider gives + // conflicting information about whether a sub-resource is required, + // by marking it as optional but also requiring one item. + // Historically the optional-ness "won" here, and so we must + // honor that for compatibility with providers that relied on this + // undocumented interaction. + map[string]*Schema{ + "list": { + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{}, + }, + MinItems: 1, + MaxItems: 1, + }, + "set": { + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{}, + }, + MinItems: 1, + MaxItems: 1, + }, + }, + testResource(&configschema.Block{ + Attributes: map[string]*configschema.Attribute{}, + BlockTypes: map[string]*configschema.NestedBlock{ + "list": { + Nesting: configschema.NestingList, + Block: configschema.Block{}, + MinItems: 0, + MaxItems: 1, + }, + "set": { + Nesting: configschema.NestingSet, + Block: configschema.Block{}, + MinItems: 0, + MaxItems: 1, + }, + }, + }), + }, + "sub-resource collections minitems+computed": { + map[string]*Schema{ + "list": { + Type: TypeList, + Computed: true, + Elem: &Resource{ + Schema: map[string]*Schema{}, + }, + MinItems: 1, + MaxItems: 1, + }, + "set": { + Type: TypeSet, + Computed: true, + Elem: &Resource{ + Schema: map[string]*Schema{}, + }, + MinItems: 1, + MaxItems: 1, + }, + }, + testResource(&configschema.Block{ + Attributes: map[string]*configschema.Attribute{}, + BlockTypes: map[string]*configschema.NestedBlock{ + "list": { + Nesting: configschema.NestingList, + Block: configschema.Block{}, + MinItems: 0, + MaxItems: 0, + }, + "set": { + Nesting: configschema.NestingSet, + Block: configschema.Block{}, + MinItems: 0, + MaxItems: 0, + }, + }, + }), + }, "nested attributes and blocks": { map[string]*Schema{ "foo": {