helper/plugin: Implement Schema.SkipCoreTypeCheck

The previous commit added this flag but did not implement it. Here we
implement it by adjusting the shape of schema we return to Terraform Core
to mark the attribute as untyped and then ensure that gets handled
correctly on the SDK side.
This commit is contained in:
Martin Atkins 2019-03-21 11:59:53 -07:00
parent 7f860dc83e
commit 135121562e
5 changed files with 200 additions and 83 deletions

View File

@ -27,6 +27,21 @@ func testResourceConfigMode() *schema.Resource {
}, },
}, },
}, },
"resource_as_attr_dynamic": {
Type: schema.TypeList,
ConfigMode: schema.SchemaConfigModeAttr,
SkipCoreTypeCheck: true,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
Default: "default",
},
},
},
},
}, },
} }
} }
@ -37,13 +52,14 @@ func testResourceConfigModeCreate(d *schema.ResourceData, meta interface{}) erro
} }
func testResourceConfigModeRead(d *schema.ResourceData, meta interface{}) error { func testResourceConfigModeRead(d *schema.ResourceData, meta interface{}) error {
const k = "resource_as_attr" for _, k := range []string{"resource_as_attr", "resource_as_attr_dynamic"} {
if l, ok := d.Get(k).([]interface{}); !ok { if l, ok := d.Get(k).([]interface{}); !ok {
return fmt.Errorf("%s should appear as []interface{}, not %T", k, l) return fmt.Errorf("%s should appear as []interface{}, not %T", k, l)
} else { } else {
for i, item := range l { for i, item := range l {
if _, ok := item.(map[string]interface{}); !ok { if _, ok := item.(map[string]interface{}); !ok {
return fmt.Errorf("%s[%d] should appear as map[string]interface{}, not %T", k, i, item) return fmt.Errorf("%s[%d] should appear as map[string]interface{}, not %T", k, i, item)
}
} }
} }
} }

View File

@ -23,12 +23,22 @@ resource "test_resource_config_mode" "foo" {
foo = "resource_as_attr 1" foo = "resource_as_attr 1"
}, },
] ]
resource_as_attr_dynamic = [
{
foo = "resource_as_attr_dynamic 0"
},
{
},
]
} }
`), `),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "2"), 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.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.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{ resource.TestStep{
@ -39,21 +49,39 @@ resource "test_resource_config_mode" "foo" {
foo = "resource_as_attr 0 updated" foo = "resource_as_attr 0 updated"
}, },
] ]
resource_as_attr_dynamic = [
{
},
]
} }
`), `),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "1"), resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "1"),
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0 updated"), resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0 updated"),
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.#", "1"),
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.0.foo", "default"),
), ),
}, },
resource.TestStep{ resource.TestStep{
Config: strings.TrimSpace(` Config: strings.TrimSpace(`
resource "test_resource_config_mode" "foo" { resource "test_resource_config_mode" "foo" {
resource_as_attr = [] resource_as_attr = []
resource_as_attr_dynamic = []
} }
`), `),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "0"), resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "0"),
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.#", "0"),
),
},
resource.TestStep{
Config: strings.TrimSpace(`
resource "test_resource_config_mode" "foo" {
}
`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckNoResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#"),
resource.TestCheckNoResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.#"),
), ),
}, },
}, },

View File

@ -52,7 +52,7 @@ func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProvider
} }
resp.Provider = &proto.Schema{ resp.Provider = &proto.Schema{
Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlock()), Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlockForCore()),
} }
for typ, res := range s.provider.ResourcesMap { for typ, res := range s.provider.ResourcesMap {
@ -72,26 +72,41 @@ func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProvider
return resp, nil return resp, nil
} }
func (s *GRPCProviderServer) getProviderSchemaBlock() *configschema.Block { func (s *GRPCProviderServer) getProviderSchemaBlockForCore() *configschema.Block {
return schema.InternalMap(s.provider.Schema).CoreConfigSchema() return schema.InternalMap(s.provider.Schema).CoreConfigSchema()
} }
func (s *GRPCProviderServer) getResourceSchemaBlock(name string) *configschema.Block { func (s *GRPCProviderServer) getResourceSchemaBlockForCore(name string) *configschema.Block {
res := s.provider.ResourcesMap[name] res := s.provider.ResourcesMap[name]
return res.CoreConfigSchema() return res.CoreConfigSchema()
} }
func (s *GRPCProviderServer) getDatasourceSchemaBlock(name string) *configschema.Block { func (s *GRPCProviderServer) getDatasourceSchemaBlockForCore(name string) *configschema.Block {
dat := s.provider.DataSourcesMap[name] dat := s.provider.DataSourcesMap[name]
return dat.CoreConfigSchema() return dat.CoreConfigSchema()
} }
func (s *GRPCProviderServer) getProviderSchemaBlockForShimming() *configschema.Block {
return schema.InternalMap(s.provider.Schema).CoreConfigSchemaForShimming()
}
func (s *GRPCProviderServer) getResourceSchemaBlockForShimming(name string) *configschema.Block {
res := s.provider.ResourcesMap[name]
return res.CoreConfigSchemaForShimming()
}
func (s *GRPCProviderServer) getDatasourceSchemaBlockForShimming(name string) *configschema.Block {
dat := s.provider.DataSourcesMap[name]
return dat.CoreConfigSchemaForShimming()
}
func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto.PrepareProviderConfig_Request) (*proto.PrepareProviderConfig_Response, error) { func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto.PrepareProviderConfig_Request) (*proto.PrepareProviderConfig_Response, error) {
resp := &proto.PrepareProviderConfig_Response{} resp := &proto.PrepareProviderConfig_Response{}
block := s.getProviderSchemaBlock() blockForCore := s.getProviderSchemaBlockForCore()
blockForShimming := s.getProviderSchemaBlockForShimming()
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType()) configVal, err := msgpack.Unmarshal(req.Config.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -162,19 +177,19 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
return resp, nil return resp, nil
} }
configVal, err = block.CoerceValue(configVal) configVal, err = blockForShimming.CoerceValue(configVal)
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
config := terraform.NewResourceConfigShimmed(configVal, block) config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema) schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema)
warns, errs := s.provider.Validate(config) warns, errs := s.provider.Validate(config)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs)) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
preparedConfigMP, err := msgpack.Marshal(configVal, block.ImpliedType()) preparedConfigMP, err := msgpack.Marshal(configVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -188,15 +203,16 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *proto.ValidateResourceTypeConfig_Request) (*proto.ValidateResourceTypeConfig_Response, error) { func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *proto.ValidateResourceTypeConfig_Request) (*proto.ValidateResourceTypeConfig_Response, error) {
resp := &proto.ValidateResourceTypeConfig_Response{} resp := &proto.ValidateResourceTypeConfig_Response{}
block := s.getResourceSchemaBlock(req.TypeName) blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType()) configVal, err := msgpack.Unmarshal(req.Config.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
config := terraform.NewResourceConfigShimmed(configVal, block) config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.ResourcesMap[req.TypeName].Schema) schema.FixupAsSingleResourceConfigIn(config, s.provider.ResourcesMap[req.TypeName].Schema)
warns, errs := s.provider.ValidateResource(req.TypeName, config) warns, errs := s.provider.ValidateResource(req.TypeName, config)
@ -208,15 +224,16 @@ func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *
func (s *GRPCProviderServer) ValidateDataSourceConfig(_ context.Context, req *proto.ValidateDataSourceConfig_Request) (*proto.ValidateDataSourceConfig_Response, error) { func (s *GRPCProviderServer) ValidateDataSourceConfig(_ context.Context, req *proto.ValidateDataSourceConfig_Request) (*proto.ValidateDataSourceConfig_Response, error) {
resp := &proto.ValidateDataSourceConfig_Response{} resp := &proto.ValidateDataSourceConfig_Response{}
block := s.getDatasourceSchemaBlock(req.TypeName) blockForCore := s.getDatasourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getDatasourceSchemaBlockForShimming(req.TypeName)
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType()) configVal, err := msgpack.Unmarshal(req.Config.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
config := terraform.NewResourceConfigShimmed(configVal, block) config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema) schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema)
warns, errs := s.provider.ValidateDataSource(req.TypeName, config) warns, errs := s.provider.ValidateDataSource(req.TypeName, config)
@ -229,7 +246,8 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
resp := &proto.UpgradeResourceState_Response{} resp := &proto.UpgradeResourceState_Response{}
res := s.provider.ResourcesMap[req.TypeName] res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema() blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
version := int(req.Version) version := int(req.Version)
@ -272,14 +290,14 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
// now we need to turn the state into the default json representation, so // now we need to turn the state into the default json representation, so
// that it can be re-decoded using the actual schema. // that it can be re-decoded using the actual schema.
val, err := schema.JSONMapToStateValue(jsonMap, block) val, err := schema.JSONMapToStateValue(jsonMap, blockForShimming)
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
// encode the final state to the expected msgpack format // encode the final state to the expected msgpack format
newStateMP, err := msgpack.Marshal(val, block.ImpliedType()) newStateMP, err := msgpack.Marshal(val, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -302,7 +320,7 @@ func (s *GRPCProviderServer) upgradeFlatmapState(version int, m map[string]strin
// first determine if we need to call the legacy MigrateState func // first determine if we need to call the legacy MigrateState func
requiresMigrate := version < res.SchemaVersion requiresMigrate := version < res.SchemaVersion
schemaType := res.CoreConfigSchema().ImpliedType() schemaType := res.CoreConfigSchemaForShimming().ImpliedType()
// if there are any StateUpgraders, then we need to only compare // if there are any StateUpgraders, then we need to only compare
// against the first version there // against the first version there
@ -395,9 +413,10 @@ func (s *GRPCProviderServer) Stop(_ context.Context, _ *proto.Stop_Request) (*pr
func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_Request) (*proto.Configure_Response, error) { func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_Request) (*proto.Configure_Response, error) {
resp := &proto.Configure_Response{} resp := &proto.Configure_Response{}
block := s.getProviderSchemaBlock() blockForCore := s.getProviderSchemaBlockForCore()
blockForShimming := s.getProviderSchemaBlockForShimming()
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType()) configVal, err := msgpack.Unmarshal(req.Config.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -405,7 +424,7 @@ func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_R
s.provider.TerraformVersion = req.TerraformVersion s.provider.TerraformVersion = req.TerraformVersion
config := terraform.NewResourceConfigShimmed(configVal, block) config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema) schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema)
err = s.provider.Configure(config) err = s.provider.Configure(config)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
@ -417,9 +436,10 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
resp := &proto.ReadResource_Response{} resp := &proto.ReadResource_Response{}
res := s.provider.ResourcesMap[req.TypeName] res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema() blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
stateVal, err := msgpack.Unmarshal(req.CurrentState.Msgpack, block.ImpliedType()) stateVal, err := msgpack.Unmarshal(req.CurrentState.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -442,7 +462,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
// The old provider API used an empty id to signal that the remote // The old provider API used an empty id to signal that the remote
// object appears to have been deleted, but our new protocol expects // object appears to have been deleted, but our new protocol expects
// to see a null value (in the cty sense) in that case. // to see a null value (in the cty sense) in that case.
newStateMP, err := msgpack.Marshal(cty.NullVal(block.ImpliedType()), block.ImpliedType()) newStateMP, err := msgpack.Marshal(cty.NullVal(blockForCore.ImpliedType()), blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
} }
@ -456,7 +476,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
newInstanceState.Attributes["id"] = newInstanceState.ID newInstanceState.Attributes["id"] = newInstanceState.ID
schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.ResourcesMap[req.TypeName]) schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.ResourcesMap[req.TypeName])
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, block.ImpliedType()) newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -465,7 +485,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
newStateVal = normalizeNullValues(newStateVal, stateVal, true) newStateVal = normalizeNullValues(newStateVal, stateVal, true)
newStateVal = copyTimeoutValues(newStateVal, stateVal) newStateVal = copyTimeoutValues(newStateVal, stateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -490,9 +510,10 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
resp.LegacyTypeSystem = true resp.LegacyTypeSystem = true
res := s.provider.ResourcesMap[req.TypeName] res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema() blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, block.ImpliedType()) priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -500,7 +521,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
create := priorStateVal.IsNull() create := priorStateVal.IsNull()
proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, block.ImpliedType()) proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -533,7 +554,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
priorState.Meta = priorPrivate priorState.Meta = priorPrivate
// turn the proposed state into a legacy configuration // turn the proposed state into a legacy configuration
cfg := terraform.NewResourceConfigShimmed(proposedNewStateVal, block) cfg := terraform.NewResourceConfigShimmed(proposedNewStateVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(cfg, s.provider.ResourcesMap[req.TypeName].Schema) schema.FixupAsSingleResourceConfigIn(cfg, s.provider.ResourcesMap[req.TypeName].Schema)
diff, err := s.provider.SimpleDiff(info, priorState, cfg) diff, err := s.provider.SimpleDiff(info, priorState, cfg)
@ -590,13 +611,13 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
// also fixes up the requiresNew keys to match. // also fixes up the requiresNew keys to match.
schema.FixupAsSingleInstanceDiffOut(diff, s.provider.ResourcesMap[req.TypeName]) schema.FixupAsSingleInstanceDiffOut(diff, s.provider.ResourcesMap[req.TypeName])
plannedStateVal, err := hcl2shim.HCL2ValueFromFlatmap(plannedAttrs, block.ImpliedType()) plannedStateVal, err := hcl2shim.HCL2ValueFromFlatmap(plannedAttrs, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
plannedStateVal, err = block.CoerceValue(plannedStateVal) plannedStateVal, err = blockForShimming.CoerceValue(plannedStateVal)
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -628,10 +649,10 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
// if this was creating the resource, we need to set any remaining computed // if this was creating the resource, we need to set any remaining computed
// fields // fields
if create { if create {
plannedStateVal = SetUnknowns(plannedStateVal, block) plannedStateVal = SetUnknowns(plannedStateVal, blockForShimming)
} }
plannedMP, err := msgpack.Marshal(plannedStateVal, block.ImpliedType()) plannedMP, err := msgpack.Marshal(plannedStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -684,7 +705,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
requiresNew = append(requiresNew, "id") requiresNew = append(requiresNew, "id")
} }
requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, block.ImpliedType()) requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -705,15 +726,16 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
} }
res := s.provider.ResourcesMap[req.TypeName] res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema() blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, block.ImpliedType()) priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, block.ImpliedType()) plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -823,13 +845,13 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
} }
schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.ResourcesMap[req.TypeName]) schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.ResourcesMap[req.TypeName])
newStateVal := cty.NullVal(block.ImpliedType()) newStateVal := cty.NullVal(blockForShimming.ImpliedType())
// Always return a null value for destroy. // Always return a null value for destroy.
// While this is usually indicated by a nil state, check for missing ID or // While this is usually indicated by a nil state, check for missing ID or
// attributes in the case of a provider failure. // attributes in the case of a provider failure.
if destroy || newInstanceState == nil || newInstanceState.Attributes == nil || newInstanceState.ID == "" { if destroy || newInstanceState == nil || newInstanceState.Attributes == nil || newInstanceState.ID == "" {
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -845,7 +867,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
// We keep the null val if we destroyed the resource, otherwise build the // We keep the null val if we destroyed the resource, otherwise build the
// entire object, even if the new state was nil. // entire object, even if the new state was nil.
newStateVal, err = schema.StateValueFromInstanceState(newInstanceState, block.ImpliedType()) newStateVal, err = schema.StateValueFromInstanceState(newInstanceState, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -855,7 +877,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
newStateVal = copyTimeoutValues(newStateVal, plannedStateVal) newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -906,14 +928,15 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
resourceType = req.TypeName resourceType = req.TypeName
} }
block := s.getResourceSchemaBlock(resourceType) blockForCore := s.getResourceSchemaBlockForCore(resourceType)
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, block.ImpliedType()) blockForShimming := s.getResourceSchemaBlockForShimming(resourceType)
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
} }
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -942,10 +965,10 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDataSource_Request) (*proto.ReadDataSource_Response, error) { func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDataSource_Request) (*proto.ReadDataSource_Response, error) {
resp := &proto.ReadDataSource_Response{} resp := &proto.ReadDataSource_Response{}
res := s.provider.DataSourcesMap[req.TypeName] blockForCore := s.getDatasourceSchemaBlockForCore(req.TypeName)
block := res.CoreConfigSchema() blockForShimming := s.getDatasourceSchemaBlockForShimming(req.TypeName)
configVal, err := msgpack.Unmarshal(req.Config.Msgpack, block.ImpliedType()) configVal, err := msgpack.Unmarshal(req.Config.Msgpack, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -955,7 +978,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
Type: req.TypeName, Type: req.TypeName,
} }
config := terraform.NewResourceConfigShimmed(configVal, block) config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema) schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema)
// we need to still build the diff separately with the Read method to match // we need to still build the diff separately with the Read method to match
@ -974,7 +997,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
} }
schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.DataSourcesMap[req.TypeName]) schema.FixupAsSingleInstanceStateOut(newInstanceState, s.provider.DataSourcesMap[req.TypeName])
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, block.ImpliedType()) newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, blockForShimming.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil
@ -982,7 +1005,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
newStateVal = copyTimeoutValues(newStateVal, configVal) newStateVal = copyTimeoutValues(newStateVal, configVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType()) newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil return resp, nil

View File

@ -22,7 +22,18 @@ import (
// This method presumes a schema that passes InternalValidate, and so may // This method presumes a schema that passes InternalValidate, and so may
// panic or produce an invalid result if given an invalid schemaMap. // panic or produce an invalid result if given an invalid schemaMap.
func (m schemaMap) CoreConfigSchema() *configschema.Block { func (m schemaMap) CoreConfigSchema() *configschema.Block {
return m.coreConfigSchema(true) return m.coreConfigSchema(true, true)
}
// CoreConfigSchemaForShimming is a variant of CoreConfigSchema that returns
// the schema that should be used when applying our shimming behaviors.
//
// In particular, it ignores the SkipCoreTypeCheck flag on any legacy schemas,
// since the shims live on the SDK side and so they need to see the full
// type information that we'd normally hide from Terraform Core when skipping
// type checking over there.
func (m schemaMap) CoreConfigSchemaForShimming() *configschema.Block {
return m.coreConfigSchema(true, false)
} }
// CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns // CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns
@ -36,10 +47,10 @@ func (m schemaMap) CoreConfigSchema() *configschema.Block {
// This should be used with care only in unusual situations where we need to // This should be used with care only in unusual situations where we need to
// work with an already-shimmed value using a new-style schema. // work with an already-shimmed value using a new-style schema.
func (m schemaMap) CoreConfigSchemaWhenShimmed() *configschema.Block { func (m schemaMap) CoreConfigSchemaWhenShimmed() *configschema.Block {
return m.coreConfigSchema(false) return m.coreConfigSchema(false, false)
} }
func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block { func (m schemaMap) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.Block {
if len(m) == 0 { if len(m) == 0 {
// We return an actual (empty) object here, rather than a nil, // We return an actual (empty) object here, rather than a nil,
// because a nil result would mean that we don't have a schema at // because a nil result would mean that we don't have a schema at
@ -54,7 +65,7 @@ func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block {
for name, schema := range m { for name, schema := range m {
if schema.Elem == nil { if schema.Elem == nil {
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle) ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue continue
} }
if schema.Type == TypeMap { if schema.Type == TypeMap {
@ -68,27 +79,27 @@ func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block {
sch.Elem = &Schema{ sch.Elem = &Schema{
Type: TypeString, Type: TypeString,
} }
ret.Attributes[name] = sch.coreConfigSchemaAttribute(enableAsSingle) ret.Attributes[name] = sch.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue continue
} }
} }
switch schema.ConfigMode { switch schema.ConfigMode {
case SchemaConfigModeAttr: case SchemaConfigModeAttr:
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle) ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
case SchemaConfigModeBlock: case SchemaConfigModeBlock:
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(enableAsSingle) ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
default: // SchemaConfigModeAuto, or any other invalid value default: // SchemaConfigModeAuto, or any other invalid value
if schema.Computed && !schema.Optional { if schema.Computed && !schema.Optional {
// Computed-only schemas are always handled as attributes, // Computed-only schemas are always handled as attributes,
// because they never appear in configuration. // because they never appear in configuration.
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle) ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue continue
} }
switch schema.Elem.(type) { switch schema.Elem.(type) {
case *Schema, ValueType: case *Schema, ValueType:
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle) ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
case *Resource: case *Resource:
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(enableAsSingle) ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
default: default:
// Should never happen for a valid schema // Should never happen for a valid schema
panic(fmt.Errorf("invalid Schema.Elem %#v; need *Schema or *Resource", schema.Elem)) panic(fmt.Errorf("invalid Schema.Elem %#v; need *Schema or *Resource", schema.Elem))
@ -103,7 +114,7 @@ func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block {
// of a schema. This is appropriate only for primitives or collections whose // of a schema. This is appropriate only for primitives or collections whose
// Elem is an instance of Schema. Use coreConfigSchemaBlock for collections // Elem is an instance of Schema. Use coreConfigSchemaBlock for collections
// whose elem is a whole resource. // whose elem is a whole resource.
func (s *Schema) coreConfigSchemaAttribute(enableAsSingle bool) *configschema.Attribute { func (s *Schema) coreConfigSchemaAttribute(asSingle, skipCoreCheck bool) *configschema.Attribute {
// The Schema.DefaultFunc capability adds some extra weirdness here since // The Schema.DefaultFunc capability adds some extra weirdness here since
// it can be combined with "Required: true" to create a sitution where // it can be combined with "Required: true" to create a sitution where
// required-ness is conditional. Terraform Core doesn't share this concept, // required-ness is conditional. Terraform Core doesn't share this concept,
@ -134,7 +145,7 @@ func (s *Schema) coreConfigSchemaAttribute(enableAsSingle bool) *configschema.At
} }
return &configschema.Attribute{ return &configschema.Attribute{
Type: s.coreConfigSchemaType(enableAsSingle), Type: s.coreConfigSchemaType(asSingle, skipCoreCheck),
Optional: opt, Optional: opt,
Required: reqd, Required: reqd,
Computed: s.Computed, Computed: s.Computed,
@ -146,9 +157,9 @@ func (s *Schema) coreConfigSchemaAttribute(enableAsSingle bool) *configschema.At
// coreConfigSchemaBlock prepares a configschema.NestedBlock representation of // coreConfigSchemaBlock prepares a configschema.NestedBlock representation of
// a schema. This is appropriate only for collections whose Elem is an instance // a schema. This is appropriate only for collections whose Elem is an instance
// of Resource, and will panic otherwise. // of Resource, and will panic otherwise.
func (s *Schema) coreConfigSchemaBlock(enableAsSingle bool) *configschema.NestedBlock { func (s *Schema) coreConfigSchemaBlock(asSingle, skipCoreCheck bool) *configschema.NestedBlock {
ret := &configschema.NestedBlock{} ret := &configschema.NestedBlock{}
if nested := schemaMap(s.Elem.(*Resource).Schema).coreConfigSchema(enableAsSingle); nested != nil { if nested := schemaMap(s.Elem.(*Resource).Schema).coreConfigSchema(asSingle, skipCoreCheck); nested != nil {
ret.Block = *nested ret.Block = *nested
} }
switch s.Type { switch s.Type {
@ -166,7 +177,7 @@ func (s *Schema) coreConfigSchemaBlock(enableAsSingle bool) *configschema.Nested
ret.MinItems = s.MinItems ret.MinItems = s.MinItems
ret.MaxItems = s.MaxItems ret.MaxItems = s.MaxItems
if s.AsSingle && enableAsSingle { if s.AsSingle && asSingle {
// In AsSingle mode, we artifically force a TypeList or TypeSet // In AsSingle mode, we artifically force a TypeList or TypeSet
// attribute in the SDK to be treated as a single block by Terraform Core. // attribute in the SDK to be treated as a single block by Terraform Core.
// This must then be fixed up in the shim code (in helper/plugin) so // This must then be fixed up in the shim code (in helper/plugin) so
@ -199,7 +210,15 @@ func (s *Schema) coreConfigSchemaBlock(enableAsSingle bool) *configschema.Nested
// coreConfigSchemaType determines the core config schema type that corresponds // coreConfigSchemaType determines the core config schema type that corresponds
// to a particular schema's type. // to a particular schema's type.
func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type { func (s *Schema) coreConfigSchemaType(asSingle, skipCoreCheck bool) cty.Type {
if skipCoreCheck && s.SkipCoreTypeCheck {
// If we're preparing a schema for Terraform Core and the schema is
// asking us to skip the Core type-check then we'll tell core that this
// attribute is dynamically-typed, so it'll just pass through anything
// and let us validate it on the plugin side.
return cty.DynamicPseudoType
}
switch s.Type { switch s.Type {
case TypeString: case TypeString:
return cty.String return cty.String
@ -214,17 +233,17 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
var elemType cty.Type var elemType cty.Type
switch set := s.Elem.(type) { switch set := s.Elem.(type) {
case *Schema: case *Schema:
elemType = set.coreConfigSchemaType(enableAsSingle) elemType = set.coreConfigSchemaType(asSingle, skipCoreCheck)
case ValueType: case ValueType:
// This represents a mistake in the provider code, but it's a // This represents a mistake in the provider code, but it's a
// common one so we'll just shim it. // common one so we'll just shim it.
elemType = (&Schema{Type: set}).coreConfigSchemaType(enableAsSingle) elemType = (&Schema{Type: set}).coreConfigSchemaType(asSingle, skipCoreCheck)
case *Resource: case *Resource:
// By default we construct a NestedBlock in this case, but this // By default we construct a NestedBlock in this case, but this
// behavior is selected either for computed-only schemas or // behavior is selected either for computed-only schemas or
// when ConfigMode is explicitly SchemaConfigModeBlock. // when ConfigMode is explicitly SchemaConfigModeBlock.
// See schemaMap.CoreConfigSchema for the exact rules. // See schemaMap.CoreConfigSchema for the exact rules.
elemType = schemaMap(set.Schema).coreConfigSchema(enableAsSingle).ImpliedType() elemType = schemaMap(set.Schema).coreConfigSchema(asSingle, skipCoreCheck).ImpliedType()
default: default:
if set != nil { if set != nil {
// Should never happen for a valid schema // Should never happen for a valid schema
@ -234,7 +253,7 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
// to be compatible with them. // to be compatible with them.
elemType = cty.String elemType = cty.String
} }
if s.AsSingle && enableAsSingle { if s.AsSingle && asSingle {
// In AsSingle mode, we artifically force a TypeList or TypeSet // In AsSingle mode, we artifically force a TypeList or TypeSet
// attribute in the SDK to be treated as a single value by Terraform Core. // attribute in the SDK to be treated as a single value by Terraform Core.
// This must then be fixed up in the shim code (in helper/plugin) so // This must then be fixed up in the shim code (in helper/plugin) so
@ -262,7 +281,16 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
// the resource's schema. CoreConfigSchema adds the implicitly required "id" // the resource's schema. CoreConfigSchema adds the implicitly required "id"
// attribute for top level resources if it doesn't exist. // attribute for top level resources if it doesn't exist.
func (r *Resource) CoreConfigSchema() *configschema.Block { func (r *Resource) CoreConfigSchema() *configschema.Block {
return r.coreConfigSchema(true) return r.coreConfigSchema(true, true)
}
// CoreConfigSchemaForShimming is a variant of CoreConfigSchema that returns
// the schema that should be used to apply shims on the SDK side.
//
// In particular, it ignores the SkipCoreTypeCheck flag on any legacy schemas
// and uses the real type information instead.
func (r *Resource) CoreConfigSchemaForShimming() *configschema.Block {
return r.coreConfigSchema(true, false)
} }
// CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns // CoreConfigSchemaWhenShimmed is a variant of CoreConfigSchema that returns
@ -276,11 +304,11 @@ func (r *Resource) CoreConfigSchema() *configschema.Block {
// This should be used with care only in unusual situations where we need to // This should be used with care only in unusual situations where we need to
// work with an already-shimmed value using a new-style schema. // work with an already-shimmed value using a new-style schema.
func (r *Resource) CoreConfigSchemaWhenShimmed() *configschema.Block { func (r *Resource) CoreConfigSchemaWhenShimmed() *configschema.Block {
return r.coreConfigSchema(false) return r.coreConfigSchema(false, false)
} }
func (r *Resource) coreConfigSchema(enableAsSingle bool) *configschema.Block { func (r *Resource) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.Block {
block := schemaMap(r.Schema).coreConfigSchema(enableAsSingle) block := schemaMap(r.Schema).coreConfigSchema(asSingle, skipCoreCheck)
if block.Attributes == nil { if block.Attributes == nil {
block.Attributes = map[string]*configschema.Attribute{} block.Attributes = map[string]*configschema.Attribute{}

View File

@ -445,6 +445,28 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}), }),
}, },
"skip core type check": {
map[string]*Schema{
"list": {
Type: TypeList,
ConfigMode: SchemaConfigModeAttr,
SkipCoreTypeCheck: true,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{},
},
},
},
testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"list": {
Type: cty.DynamicPseudoType,
Optional: true, // Just so we can progress to provider-driven validation and return the error there
},
},
BlockTypes: map[string]*configschema.NestedBlock{},
}),
},
} }
for name, test := range tests { for name, test := range tests {