helper/schema: TypeMap of Resource is actually of TypeString
Historically helper/schema did not support non-primitive map attributes because they cannot be represented unambiguously in flatmap. When we initially implemented CoreConfigSchema here we mapped that situation to a nested block of mode NestingMap, even though that'd never worked until now, assuming that it'd be harmless because providers wouldn't be using it. It turns out that some providers are, in fact, incorrectly populating a TypeMap schema with Elem: &schema.Resource, apparently under the false assumption that it would constrain the keys allowed in the map. In practice, helper/schema has just been ignoring this and treating such attributes as map of string. (#20076) In order to preserve the behavior of these existing incorrectly-specified attribute definitions, here we mimic the helper/schema behavior by presenting as an attribute of type map(string). These attributes have also been shown in some documentation as nested blocks (with no equals sign), so that'll need to be fixed in user configurations as they upgrade to Terraform 0.12. However, the existing upgrade tool rules will take care of that as a natural consequence of the name being indicated as an attribute in the schema, rather than as a block type. This fixes #20076.
This commit is contained in:
parent
954d38e870
commit
ae0be75ae0
|
@ -39,6 +39,21 @@ func (m schemaMap) CoreConfigSchema() *configschema.Block {
|
||||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if schema.Type == TypeMap {
|
||||||
|
// For TypeMap in particular, it isn't valid for Elem to be a
|
||||||
|
// *Resource (since that would be ambiguous in flatmap) and
|
||||||
|
// so Elem is treated as a TypeString schema if so. This matches
|
||||||
|
// how the field readers treat this situation, for compatibility
|
||||||
|
// with configurations targeting Terraform 0.11 and earlier.
|
||||||
|
if _, isResource := schema.Elem.(*Resource); isResource {
|
||||||
|
sch := *schema // shallow copy
|
||||||
|
sch.Elem = &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
}
|
||||||
|
ret.Attributes[name] = sch.coreConfigSchemaAttribute()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
switch schema.Elem.(type) {
|
switch schema.Elem.(type) {
|
||||||
case *Schema, ValueType:
|
case *Schema, ValueType:
|
||||||
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
ret.Attributes[name] = schema.coreConfigSchemaAttribute()
|
||||||
|
|
|
@ -204,7 +204,18 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
testResource(&configschema.Block{
|
testResource(&configschema.Block{
|
||||||
Attributes: map[string]*configschema.Attribute{},
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
// This one becomes a string attribute because helper/schema
|
||||||
|
// doesn't actually support maps of resource. The given
|
||||||
|
// "Elem" is just ignored entirely here, which is important
|
||||||
|
// because that is also true of the helper/schema logic and
|
||||||
|
// existing providers rely on this being ignored for
|
||||||
|
// correct operation.
|
||||||
|
"map": {
|
||||||
|
Type: cty.Map(cty.String),
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
BlockTypes: map[string]*configschema.NestedBlock{
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
"list": {
|
"list": {
|
||||||
Nesting: configschema.NestingList,
|
Nesting: configschema.NestingList,
|
||||||
|
@ -217,10 +228,6 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
|
||||||
Block: configschema.Block{},
|
Block: configschema.Block{},
|
||||||
MinItems: 1, // because schema is Required
|
MinItems: 1, // because schema is Required
|
||||||
},
|
},
|
||||||
"map": {
|
|
||||||
Nesting: configschema.NestingMap,
|
|
||||||
Block: configschema.Block{},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue