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 {
const k = "resource_as_attr"
if l, ok := d.Get(k).([]interface{}); !ok {
return fmt.Errorf("%s should appear as []interface{}, not %T", k, l)
} else {
for i, item := range l {
if _, ok := item.(map[string]interface{}); !ok {
return fmt.Errorf("%s[%d] should appear as map[string]interface{}, not %T", k, i, item)
for _, k := range []string{"resource_as_attr", "resource_as_attr_dynamic"} {
if l, ok := d.Get(k).([]interface{}); !ok {
return fmt.Errorf("%s should appear as []interface{}, not %T", k, l)
} else {
for i, item := range l {
if _, ok := item.(map[string]interface{}); !ok {
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"
},
]
resource_as_attr_dynamic = [
{
foo = "resource_as_attr_dynamic 0"
},
{
},
]
}
`),
Check: resource.ComposeTestCheckFunc(
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.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{
@ -39,21 +49,39 @@ resource "test_resource_config_mode" "foo" {
foo = "resource_as_attr 0 updated"
},
]
resource_as_attr_dynamic = [
{
},
]
}
`),
Check: resource.ComposeTestCheckFunc(
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_dynamic.#", "1"),
resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr_dynamic.0.foo", "default"),
),
},
resource.TestStep{
Config: strings.TrimSpace(`
resource "test_resource_config_mode" "foo" {
resource_as_attr = []
resource_as_attr_dynamic = []
}
`),
Check: resource.ComposeTestCheckFunc(
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{
Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlock()),
Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlockForCore()),
}
for typ, res := range s.provider.ResourcesMap {
@ -72,26 +72,41 @@ func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProvider
return resp, nil
}
func (s *GRPCProviderServer) getProviderSchemaBlock() *configschema.Block {
func (s *GRPCProviderServer) getProviderSchemaBlockForCore() *configschema.Block {
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]
return res.CoreConfigSchema()
}
func (s *GRPCProviderServer) getDatasourceSchemaBlock(name string) *configschema.Block {
func (s *GRPCProviderServer) getDatasourceSchemaBlockForCore(name string) *configschema.Block {
dat := s.provider.DataSourcesMap[name]
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) {
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -162,19 +177,19 @@ func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto
return resp, nil
}
configVal, err = block.CoerceValue(configVal)
configVal, err = blockForShimming.CoerceValue(configVal)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema)
warns, errs := s.provider.Validate(config)
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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) {
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.ResourcesMap[req.TypeName].Schema)
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) {
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
config := terraform.NewResourceConfigShimmed(configVal, block)
config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema)
warns, errs := s.provider.ValidateDataSource(req.TypeName, config)
@ -229,7 +246,8 @@ func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.
resp := &proto.UpgradeResourceState_Response{}
res := s.provider.ResourcesMap[req.TypeName]
block := res.CoreConfigSchema()
blockForCore := s.getResourceSchemaBlockForCore(req.TypeName)
blockForShimming := s.getResourceSchemaBlockForShimming(req.TypeName)
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
// 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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
// 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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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
requiresMigrate := version < res.SchemaVersion
schemaType := res.CoreConfigSchema().ImpliedType()
schemaType := res.CoreConfigSchemaForShimming().ImpliedType()
// if there are any StateUpgraders, then we need to only compare
// 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) {
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -405,7 +424,7 @@ func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_R
s.provider.TerraformVersion = req.TerraformVersion
config := terraform.NewResourceConfigShimmed(configVal, block)
config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.Schema)
err = s.provider.Configure(config)
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
@ -417,9 +436,10 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
resp := &proto.ReadResource_Response{}
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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
// object appears to have been deleted, but our new protocol expects
// 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 {
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
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -465,7 +485,7 @@ func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadReso
newStateVal = normalizeNullValues(newStateVal, stateVal, true)
newStateVal = copyTimeoutValues(newStateVal, stateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -490,9 +510,10 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
resp.LegacyTypeSystem = true
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -500,7 +521,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
create := priorStateVal.IsNull()
proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, block.ImpliedType())
proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -533,7 +554,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
priorState.Meta = priorPrivate
// 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)
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.
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
plannedStateVal, err = block.CoerceValue(plannedStateVal)
plannedStateVal, err = blockForShimming.CoerceValue(plannedStateVal)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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
// fields
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -684,7 +705,7 @@ func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.Pl
requiresNew = append(requiresNew, "id")
}
requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, block.ImpliedType())
requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, blockForShimming.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -705,15 +726,16 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
}
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, block.ImpliedType())
plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -823,13 +845,13 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
}
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.
// While this is usually indicated by a nil state, check for missing ID or
// attributes in the case of a provider failure.
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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
// 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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -855,7 +877,7 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -906,14 +928,15 @@ func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.I
resourceType = req.TypeName
}
block := s.getResourceSchemaBlock(resourceType)
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, block.ImpliedType())
blockForCore := s.getResourceSchemaBlockForCore(resourceType)
blockForShimming := s.getResourceSchemaBlockForShimming(resourceType)
newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, blockForShimming.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
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) {
resp := &proto.ReadDataSource_Response{}
res := s.provider.DataSourcesMap[req.TypeName]
block := res.CoreConfigSchema()
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 {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -955,7 +978,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
Type: req.TypeName,
}
config := terraform.NewResourceConfigShimmed(configVal, block)
config := terraform.NewResourceConfigShimmed(configVal, blockForShimming)
schema.FixupAsSingleResourceConfigIn(config, s.provider.DataSourcesMap[req.TypeName].Schema)
// 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])
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, block.ImpliedType())
newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, blockForShimming.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
@ -982,7 +1005,7 @@ func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDa
newStateVal = copyTimeoutValues(newStateVal, configVal)
newStateMP, err := msgpack.Marshal(newStateVal, block.ImpliedType())
newStateMP, err := msgpack.Marshal(newStateVal, blockForCore.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil

View File

@ -22,7 +22,18 @@ import (
// This method presumes a schema that passes InternalValidate, and so may
// panic or produce an invalid result if given an invalid schemaMap.
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
@ -36,10 +47,10 @@ func (m schemaMap) CoreConfigSchema() *configschema.Block {
// 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.
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 {
// 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
@ -54,7 +65,7 @@ func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block {
for name, schema := range m {
if schema.Elem == nil {
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle)
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue
}
if schema.Type == TypeMap {
@ -68,27 +79,27 @@ func (m schemaMap) coreConfigSchema(enableAsSingle bool) *configschema.Block {
sch.Elem = &Schema{
Type: TypeString,
}
ret.Attributes[name] = sch.coreConfigSchemaAttribute(enableAsSingle)
ret.Attributes[name] = sch.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue
}
}
switch schema.ConfigMode {
case SchemaConfigModeAttr:
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle)
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
case SchemaConfigModeBlock:
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(enableAsSingle)
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
default: // SchemaConfigModeAuto, or any other invalid value
if schema.Computed && !schema.Optional {
// Computed-only schemas are always handled as attributes,
// because they never appear in configuration.
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle)
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
continue
}
switch schema.Elem.(type) {
case *Schema, ValueType:
ret.Attributes[name] = schema.coreConfigSchemaAttribute(enableAsSingle)
ret.Attributes[name] = schema.coreConfigSchemaAttribute(asSingle, skipCoreCheck)
case *Resource:
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(enableAsSingle)
ret.BlockTypes[name] = schema.coreConfigSchemaBlock(asSingle, skipCoreCheck)
default:
// Should never happen for a valid schema
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
// Elem is an instance of Schema. Use coreConfigSchemaBlock for collections
// 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
// it can be combined with "Required: true" to create a sitution where
// 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{
Type: s.coreConfigSchemaType(enableAsSingle),
Type: s.coreConfigSchemaType(asSingle, skipCoreCheck),
Optional: opt,
Required: reqd,
Computed: s.Computed,
@ -146,9 +157,9 @@ func (s *Schema) coreConfigSchemaAttribute(enableAsSingle bool) *configschema.At
// coreConfigSchemaBlock prepares a configschema.NestedBlock representation of
// a schema. This is appropriate only for collections whose Elem is an instance
// 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{}
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
}
switch s.Type {
@ -166,7 +177,7 @@ func (s *Schema) coreConfigSchemaBlock(enableAsSingle bool) *configschema.Nested
ret.MinItems = s.MinItems
ret.MaxItems = s.MaxItems
if s.AsSingle && enableAsSingle {
if s.AsSingle && asSingle {
// In AsSingle mode, we artifically force a TypeList or TypeSet
// 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
@ -199,7 +210,15 @@ func (s *Schema) coreConfigSchemaBlock(enableAsSingle bool) *configschema.Nested
// coreConfigSchemaType determines the core config schema type that corresponds
// 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 {
case TypeString:
return cty.String
@ -214,17 +233,17 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
var elemType cty.Type
switch set := s.Elem.(type) {
case *Schema:
elemType = set.coreConfigSchemaType(enableAsSingle)
elemType = set.coreConfigSchemaType(asSingle, skipCoreCheck)
case ValueType:
// This represents a mistake in the provider code, but it's a
// common one so we'll just shim it.
elemType = (&Schema{Type: set}).coreConfigSchemaType(enableAsSingle)
elemType = (&Schema{Type: set}).coreConfigSchemaType(asSingle, skipCoreCheck)
case *Resource:
// By default we construct a NestedBlock in this case, but this
// behavior is selected either for computed-only schemas or
// when ConfigMode is explicitly SchemaConfigModeBlock.
// See schemaMap.CoreConfigSchema for the exact rules.
elemType = schemaMap(set.Schema).coreConfigSchema(enableAsSingle).ImpliedType()
elemType = schemaMap(set.Schema).coreConfigSchema(asSingle, skipCoreCheck).ImpliedType()
default:
if set != nil {
// Should never happen for a valid schema
@ -234,7 +253,7 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
// to be compatible with them.
elemType = cty.String
}
if s.AsSingle && enableAsSingle {
if s.AsSingle && asSingle {
// In AsSingle mode, we artifically force a TypeList or TypeSet
// 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
@ -262,7 +281,16 @@ func (s *Schema) coreConfigSchemaType(enableAsSingle bool) cty.Type {
// the resource's schema. CoreConfigSchema adds the implicitly required "id"
// attribute for top level resources if it doesn't exist.
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
@ -276,11 +304,11 @@ func (r *Resource) CoreConfigSchema() *configschema.Block {
// 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.
func (r *Resource) CoreConfigSchemaWhenShimmed() *configschema.Block {
return r.coreConfigSchema(false)
return r.coreConfigSchema(false, false)
}
func (r *Resource) coreConfigSchema(enableAsSingle bool) *configschema.Block {
block := schemaMap(r.Schema).coreConfigSchema(enableAsSingle)
func (r *Resource) coreConfigSchema(asSingle, skipCoreCheck bool) *configschema.Block {
block := schemaMap(r.Schema).coreConfigSchema(asSingle, skipCoreCheck)
if block.Attributes == nil {
block.Attributes = map[string]*configschema.Attribute{}

View File

@ -445,6 +445,28 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
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 {