github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/plugin/grpc_provider.go (about)

     1  package plugin
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"strconv"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  	ctyconvert "github.com/zclconf/go-cty/cty/convert"
    11  	"github.com/zclconf/go-cty/cty/msgpack"
    12  	context "golang.org/x/net/context"
    13  
    14  	"github.com/hashicorp/terraform/configs/configschema"
    15  	"github.com/hashicorp/terraform/configs/hcl2shim"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  	proto "github.com/hashicorp/terraform/internal/tfplugin5"
    18  	"github.com/hashicorp/terraform/plans/objchange"
    19  	"github.com/hashicorp/terraform/plugin/convert"
    20  	"github.com/hashicorp/terraform/terraform"
    21  )
    22  
    23  const newExtraKey = "_new_extra_shim"
    24  
    25  // NewGRPCProviderServerShim wraps a terraform.ResourceProvider in a
    26  // proto.ProviderServer implementation. If the provided provider is not a
    27  // *schema.Provider, this will return nil,
    28  func NewGRPCProviderServerShim(p terraform.ResourceProvider) *GRPCProviderServer {
    29  	sp, ok := p.(*schema.Provider)
    30  	if !ok {
    31  		return nil
    32  	}
    33  
    34  	return &GRPCProviderServer{
    35  		provider: sp,
    36  	}
    37  }
    38  
    39  // GRPCProviderServer handles the server, or plugin side of the rpc connection.
    40  type GRPCProviderServer struct {
    41  	provider *schema.Provider
    42  }
    43  
    44  func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProviderSchema_Request) (*proto.GetProviderSchema_Response, error) {
    45  	// Here we are certain that the provider is being called through grpc, so
    46  	// make sure the feature flag for helper/schema is set
    47  	schema.SetProto5()
    48  
    49  	resp := &proto.GetProviderSchema_Response{
    50  		ResourceSchemas:   make(map[string]*proto.Schema),
    51  		DataSourceSchemas: make(map[string]*proto.Schema),
    52  	}
    53  
    54  	resp.Provider = &proto.Schema{
    55  		Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlock()),
    56  	}
    57  
    58  	for typ, res := range s.provider.ResourcesMap {
    59  		resp.ResourceSchemas[typ] = &proto.Schema{
    60  			Version: int64(res.SchemaVersion),
    61  			Block:   convert.ConfigSchemaToProto(res.CoreConfigSchema()),
    62  		}
    63  	}
    64  
    65  	for typ, dat := range s.provider.DataSourcesMap {
    66  		resp.DataSourceSchemas[typ] = &proto.Schema{
    67  			Version: int64(dat.SchemaVersion),
    68  			Block:   convert.ConfigSchemaToProto(dat.CoreConfigSchema()),
    69  		}
    70  	}
    71  
    72  	return resp, nil
    73  }
    74  
    75  func (s *GRPCProviderServer) getProviderSchemaBlock() *configschema.Block {
    76  	return schema.InternalMap(s.provider.Schema).CoreConfigSchema()
    77  }
    78  
    79  func (s *GRPCProviderServer) getResourceSchemaBlock(name string) *configschema.Block {
    80  	res := s.provider.ResourcesMap[name]
    81  	return res.CoreConfigSchema()
    82  }
    83  
    84  func (s *GRPCProviderServer) getDatasourceSchemaBlock(name string) *configschema.Block {
    85  	dat := s.provider.DataSourcesMap[name]
    86  	return dat.CoreConfigSchema()
    87  }
    88  
    89  func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto.PrepareProviderConfig_Request) (*proto.PrepareProviderConfig_Response, error) {
    90  	resp := &proto.PrepareProviderConfig_Response{}
    91  
    92  	schemaBlock := s.getProviderSchemaBlock()
    93  
    94  	configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
    95  	if err != nil {
    96  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
    97  		return resp, nil
    98  	}
    99  
   100  	// lookup any required, top-level attributes that are Null, and see if we
   101  	// have a Default value available.
   102  	configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
   103  		// we're only looking for top-level attributes
   104  		if len(path) != 1 {
   105  			return val, nil
   106  		}
   107  
   108  		// nothing to do if we already have a value
   109  		if !val.IsNull() {
   110  			return val, nil
   111  		}
   112  
   113  		// get the Schema definition for this attribute
   114  		getAttr, ok := path[0].(cty.GetAttrStep)
   115  		// these should all exist, but just ignore anything strange
   116  		if !ok {
   117  			return val, nil
   118  		}
   119  
   120  		attrSchema := s.provider.Schema[getAttr.Name]
   121  		// continue to ignore anything that doesn't match
   122  		if attrSchema == nil {
   123  			return val, nil
   124  		}
   125  
   126  		// this is deprecated, so don't set it
   127  		if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
   128  			return val, nil
   129  		}
   130  
   131  		// find a default value if it exists
   132  		def, err := attrSchema.DefaultValue()
   133  		if err != nil {
   134  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
   135  			return val, err
   136  		}
   137  
   138  		// no default
   139  		if def == nil {
   140  			return val, nil
   141  		}
   142  
   143  		// create a cty.Value and make sure it's the correct type
   144  		tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
   145  
   146  		// helper/schema used to allow setting "" to a bool
   147  		if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
   148  			// return a warning about the conversion
   149  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, "provider set empty string as default value for bool "+getAttr.Name)
   150  			tmpVal = cty.False
   151  		}
   152  
   153  		val, err = ctyconvert.Convert(tmpVal, val.Type())
   154  		if err != nil {
   155  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
   156  		}
   157  
   158  		return val, err
   159  	})
   160  	if err != nil {
   161  		// any error here was already added to the diagnostics
   162  		return resp, nil
   163  	}
   164  
   165  	configVal, err = schemaBlock.CoerceValue(configVal)
   166  	if err != nil {
   167  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   168  		return resp, nil
   169  	}
   170  
   171  	// Ensure there are no nulls that will cause helper/schema to panic.
   172  	if err := validateConfigNulls(configVal, nil); err != nil {
   173  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   174  		return resp, nil
   175  	}
   176  
   177  	config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
   178  
   179  	warns, errs := s.provider.Validate(config)
   180  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
   181  
   182  	preparedConfigMP, err := msgpack.Marshal(configVal, schemaBlock.ImpliedType())
   183  	if err != nil {
   184  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   185  		return resp, nil
   186  	}
   187  
   188  	resp.PreparedConfig = &proto.DynamicValue{Msgpack: preparedConfigMP}
   189  
   190  	return resp, nil
   191  }
   192  
   193  func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *proto.ValidateResourceTypeConfig_Request) (*proto.ValidateResourceTypeConfig_Response, error) {
   194  	resp := &proto.ValidateResourceTypeConfig_Response{}
   195  
   196  	schemaBlock := s.getResourceSchemaBlock(req.TypeName)
   197  
   198  	configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
   199  	if err != nil {
   200  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   201  		return resp, nil
   202  	}
   203  
   204  	config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
   205  
   206  	warns, errs := s.provider.ValidateResource(req.TypeName, config)
   207  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
   208  
   209  	return resp, nil
   210  }
   211  
   212  func (s *GRPCProviderServer) ValidateDataSourceConfig(_ context.Context, req *proto.ValidateDataSourceConfig_Request) (*proto.ValidateDataSourceConfig_Response, error) {
   213  	resp := &proto.ValidateDataSourceConfig_Response{}
   214  
   215  	schemaBlock := s.getDatasourceSchemaBlock(req.TypeName)
   216  
   217  	configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
   218  	if err != nil {
   219  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   220  		return resp, nil
   221  	}
   222  
   223  	// Ensure there are no nulls that will cause helper/schema to panic.
   224  	if err := validateConfigNulls(configVal, nil); err != nil {
   225  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   226  		return resp, nil
   227  	}
   228  
   229  	config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
   230  
   231  	warns, errs := s.provider.ValidateDataSource(req.TypeName, config)
   232  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
   233  
   234  	return resp, nil
   235  }
   236  
   237  func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.UpgradeResourceState_Request) (*proto.UpgradeResourceState_Response, error) {
   238  	resp := &proto.UpgradeResourceState_Response{}
   239  
   240  	res := s.provider.ResourcesMap[req.TypeName]
   241  	schemaBlock := s.getResourceSchemaBlock(req.TypeName)
   242  
   243  	version := int(req.Version)
   244  
   245  	jsonMap := map[string]interface{}{}
   246  	var err error
   247  
   248  	switch {
   249  	// We first need to upgrade a flatmap state if it exists.
   250  	// There should never be both a JSON and Flatmap state in the request.
   251  	case len(req.RawState.Flatmap) > 0:
   252  		jsonMap, version, err = s.upgradeFlatmapState(version, req.RawState.Flatmap, res)
   253  		if err != nil {
   254  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   255  			return resp, nil
   256  		}
   257  	// if there's a JSON state, we need to decode it.
   258  	case len(req.RawState.Json) > 0:
   259  		err = json.Unmarshal(req.RawState.Json, &jsonMap)
   260  		if err != nil {
   261  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   262  			return resp, nil
   263  		}
   264  	default:
   265  		log.Println("[DEBUG] no state provided to upgrade")
   266  		return resp, nil
   267  	}
   268  
   269  	// complete the upgrade of the JSON states
   270  	jsonMap, err = s.upgradeJSONState(version, jsonMap, res)
   271  	if err != nil {
   272  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   273  		return resp, nil
   274  	}
   275  
   276  	// The provider isn't required to clean out removed fields
   277  	s.removeAttributes(jsonMap, schemaBlock.ImpliedType())
   278  
   279  	// now we need to turn the state into the default json representation, so
   280  	// that it can be re-decoded using the actual schema.
   281  	val, err := schema.JSONMapToStateValue(jsonMap, schemaBlock)
   282  	if err != nil {
   283  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   284  		return resp, nil
   285  	}
   286  
   287  	// Now we need to make sure blocks are represented correctly, which means
   288  	// that missing blocks are empty collections, rather than null.
   289  	// First we need to CoerceValue to ensure that all object types match.
   290  	val, err = schemaBlock.CoerceValue(val)
   291  	if err != nil {
   292  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   293  		return resp, nil
   294  	}
   295  	// Normalize the value and fill in any missing blocks.
   296  	val = objchange.NormalizeObjectFromLegacySDK(val, schemaBlock)
   297  
   298  	// encode the final state to the expected msgpack format
   299  	newStateMP, err := msgpack.Marshal(val, schemaBlock.ImpliedType())
   300  	if err != nil {
   301  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   302  		return resp, nil
   303  	}
   304  
   305  	resp.UpgradedState = &proto.DynamicValue{Msgpack: newStateMP}
   306  	return resp, nil
   307  }
   308  
   309  // upgradeFlatmapState takes a legacy flatmap state, upgrades it using Migrate
   310  // state if necessary, and converts it to the new JSON state format decoded as a
   311  // map[string]interface{}.
   312  // upgradeFlatmapState returns the json map along with the corresponding schema
   313  // version.
   314  func (s *GRPCProviderServer) upgradeFlatmapState(version int, m map[string]string, res *schema.Resource) (map[string]interface{}, int, error) {
   315  	// this will be the version we've upgraded so, defaulting to the given
   316  	// version in case no migration was called.
   317  	upgradedVersion := version
   318  
   319  	// first determine if we need to call the legacy MigrateState func
   320  	requiresMigrate := version < res.SchemaVersion
   321  
   322  	schemaType := res.CoreConfigSchema().ImpliedType()
   323  
   324  	// if there are any StateUpgraders, then we need to only compare
   325  	// against the first version there
   326  	if len(res.StateUpgraders) > 0 {
   327  		requiresMigrate = version < res.StateUpgraders[0].Version
   328  	}
   329  
   330  	if requiresMigrate && res.MigrateState == nil {
   331  		// Providers were previously allowed to bump the version
   332  		// without declaring MigrateState.
   333  		// If there are further upgraders, then we've only updated that far.
   334  		if len(res.StateUpgraders) > 0 {
   335  			schemaType = res.StateUpgraders[0].Type
   336  			upgradedVersion = res.StateUpgraders[0].Version
   337  		}
   338  	} else if requiresMigrate {
   339  		is := &terraform.InstanceState{
   340  			ID:         m["id"],
   341  			Attributes: m,
   342  			Meta: map[string]interface{}{
   343  				"schema_version": strconv.Itoa(version),
   344  			},
   345  		}
   346  
   347  		is, err := res.MigrateState(version, is, s.provider.Meta())
   348  		if err != nil {
   349  			return nil, 0, err
   350  		}
   351  
   352  		// re-assign the map in case there was a copy made, making sure to keep
   353  		// the ID
   354  		m := is.Attributes
   355  		m["id"] = is.ID
   356  
   357  		// if there are further upgraders, then we've only updated that far
   358  		if len(res.StateUpgraders) > 0 {
   359  			schemaType = res.StateUpgraders[0].Type
   360  			upgradedVersion = res.StateUpgraders[0].Version
   361  		}
   362  	} else {
   363  		// the schema version may be newer than the MigrateState functions
   364  		// handled and older than the current, but still stored in the flatmap
   365  		// form. If that's the case, we need to find the correct schema type to
   366  		// convert the state.
   367  		for _, upgrader := range res.StateUpgraders {
   368  			if upgrader.Version == version {
   369  				schemaType = upgrader.Type
   370  				break
   371  			}
   372  		}
   373  	}
   374  
   375  	// now we know the state is up to the latest version that handled the
   376  	// flatmap format state. Now we can upgrade the format and continue from
   377  	// there.
   378  	newConfigVal, err := hcl2shim.HCL2ValueFromFlatmap(m, schemaType)
   379  	if err != nil {
   380  		return nil, 0, err
   381  	}
   382  
   383  	jsonMap, err := schema.StateValueToJSONMap(newConfigVal, schemaType)
   384  	return jsonMap, upgradedVersion, err
   385  }
   386  
   387  func (s *GRPCProviderServer) upgradeJSONState(version int, m map[string]interface{}, res *schema.Resource) (map[string]interface{}, error) {
   388  	var err error
   389  
   390  	for _, upgrader := range res.StateUpgraders {
   391  		if version != upgrader.Version {
   392  			continue
   393  		}
   394  
   395  		m, err = upgrader.Upgrade(m, s.provider.Meta())
   396  		if err != nil {
   397  			return nil, err
   398  		}
   399  		version++
   400  	}
   401  
   402  	return m, nil
   403  }
   404  
   405  // Remove any attributes no longer present in the schema, so that the json can
   406  // be correctly decoded.
   407  func (s *GRPCProviderServer) removeAttributes(v interface{}, ty cty.Type) {
   408  	// we're only concerned with finding maps that corespond to object
   409  	// attributes
   410  	switch v := v.(type) {
   411  	case []interface{}:
   412  		// If these aren't blocks the next call will be a noop
   413  		if ty.IsListType() || ty.IsSetType() {
   414  			eTy := ty.ElementType()
   415  			for _, eV := range v {
   416  				s.removeAttributes(eV, eTy)
   417  			}
   418  		}
   419  		return
   420  	case map[string]interface{}:
   421  		// map blocks aren't yet supported, but handle this just in case
   422  		if ty.IsMapType() {
   423  			eTy := ty.ElementType()
   424  			for _, eV := range v {
   425  				s.removeAttributes(eV, eTy)
   426  			}
   427  			return
   428  		}
   429  
   430  		if ty == cty.DynamicPseudoType {
   431  			log.Printf("[DEBUG] ignoring dynamic block: %#v\n", v)
   432  			return
   433  		}
   434  
   435  		if !ty.IsObjectType() {
   436  			// This shouldn't happen, and will fail to decode further on, so
   437  			// there's no need to handle it here.
   438  			log.Printf("[WARN] unexpected type %#v for map in json state", ty)
   439  			return
   440  		}
   441  
   442  		attrTypes := ty.AttributeTypes()
   443  		for attr, attrV := range v {
   444  			attrTy, ok := attrTypes[attr]
   445  			if !ok {
   446  				log.Printf("[DEBUG] attribute %q no longer present in schema", attr)
   447  				delete(v, attr)
   448  				continue
   449  			}
   450  
   451  			s.removeAttributes(attrV, attrTy)
   452  		}
   453  	}
   454  }
   455  
   456  func (s *GRPCProviderServer) Stop(_ context.Context, _ *proto.Stop_Request) (*proto.Stop_Response, error) {
   457  	resp := &proto.Stop_Response{}
   458  
   459  	err := s.provider.Stop()
   460  	if err != nil {
   461  		resp.Error = err.Error()
   462  	}
   463  
   464  	return resp, nil
   465  }
   466  
   467  func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_Request) (*proto.Configure_Response, error) {
   468  	resp := &proto.Configure_Response{}
   469  
   470  	schemaBlock := s.getProviderSchemaBlock()
   471  
   472  	configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
   473  	if err != nil {
   474  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   475  		return resp, nil
   476  	}
   477  
   478  	s.provider.TerraformVersion = req.TerraformVersion
   479  
   480  	// Ensure there are no nulls that will cause helper/schema to panic.
   481  	if err := validateConfigNulls(configVal, nil); err != nil {
   482  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   483  		return resp, nil
   484  	}
   485  
   486  	config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
   487  	err = s.provider.Configure(config)
   488  	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   489  
   490  	return resp, nil
   491  }
   492  
   493  func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadResource_Request) (*proto.ReadResource_Response, error) {
   494  	resp := &proto.ReadResource_Response{
   495  		// helper/schema did previously handle private data during refresh, but
   496  		// core is now going to expect this to be maintained in order to
   497  		// persist it in the state.
   498  		Private: req.Private,
   499  	}
   500  
   501  	res := s.provider.ResourcesMap[req.TypeName]
   502  	schemaBlock := s.getResourceSchemaBlock(req.TypeName)
   503  
   504  	stateVal, err := msgpack.Unmarshal(req.CurrentState.Msgpack, schemaBlock.ImpliedType())
   505  	if err != nil {
   506  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   507  		return resp, nil
   508  	}
   509  
   510  	instanceState, err := res.ShimInstanceStateFromValue(stateVal)
   511  	if err != nil {
   512  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   513  		return resp, nil
   514  	}
   515  
   516  	private := make(map[string]interface{})
   517  	if len(req.Private) > 0 {
   518  		if err := json.Unmarshal(req.Private, &private); err != nil {
   519  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   520  			return resp, nil
   521  		}
   522  	}
   523  	instanceState.Meta = private
   524  
   525  	newInstanceState, err := res.RefreshWithoutUpgrade(instanceState, s.provider.Meta())
   526  	if err != nil {
   527  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   528  		return resp, nil
   529  	}
   530  
   531  	if newInstanceState == nil || newInstanceState.ID == "" {
   532  		// The old provider API used an empty id to signal that the remote
   533  		// object appears to have been deleted, but our new protocol expects
   534  		// to see a null value (in the cty sense) in that case.
   535  		newStateMP, err := msgpack.Marshal(cty.NullVal(schemaBlock.ImpliedType()), schemaBlock.ImpliedType())
   536  		if err != nil {
   537  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   538  		}
   539  		resp.NewState = &proto.DynamicValue{
   540  			Msgpack: newStateMP,
   541  		}
   542  		return resp, nil
   543  	}
   544  
   545  	// helper/schema should always copy the ID over, but do it again just to be safe
   546  	newInstanceState.Attributes["id"] = newInstanceState.ID
   547  
   548  	newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, schemaBlock.ImpliedType())
   549  	if err != nil {
   550  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   551  		return resp, nil
   552  	}
   553  
   554  	newStateVal = normalizeNullValues(newStateVal, stateVal, false)
   555  	newStateVal = copyTimeoutValues(newStateVal, stateVal)
   556  
   557  	newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
   558  	if err != nil {
   559  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   560  		return resp, nil
   561  	}
   562  
   563  	resp.NewState = &proto.DynamicValue{
   564  		Msgpack: newStateMP,
   565  	}
   566  
   567  	return resp, nil
   568  }
   569  
   570  func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.PlanResourceChange_Request) (*proto.PlanResourceChange_Response, error) {
   571  	resp := &proto.PlanResourceChange_Response{}
   572  
   573  	// This is a signal to Terraform Core that we're doing the best we can to
   574  	// shim the legacy type system of the SDK onto the Terraform type system
   575  	// but we need it to cut us some slack. This setting should not be taken
   576  	// forward to any new SDK implementations, since setting it prevents us
   577  	// from catching certain classes of provider bug that can lead to
   578  	// confusing downstream errors.
   579  	resp.LegacyTypeSystem = true
   580  
   581  	res := s.provider.ResourcesMap[req.TypeName]
   582  	schemaBlock := s.getResourceSchemaBlock(req.TypeName)
   583  
   584  	priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, schemaBlock.ImpliedType())
   585  	if err != nil {
   586  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   587  		return resp, nil
   588  	}
   589  
   590  	create := priorStateVal.IsNull()
   591  
   592  	proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, schemaBlock.ImpliedType())
   593  	if err != nil {
   594  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   595  		return resp, nil
   596  	}
   597  
   598  	// We don't usually plan destroys, but this can return early in any case.
   599  	if proposedNewStateVal.IsNull() {
   600  		resp.PlannedState = req.ProposedNewState
   601  		resp.PlannedPrivate = req.PriorPrivate
   602  		return resp, nil
   603  	}
   604  
   605  	info := &terraform.InstanceInfo{
   606  		Type: req.TypeName,
   607  	}
   608  
   609  	priorState, err := res.ShimInstanceStateFromValue(priorStateVal)
   610  	if err != nil {
   611  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   612  		return resp, nil
   613  	}
   614  	priorPrivate := make(map[string]interface{})
   615  	if len(req.PriorPrivate) > 0 {
   616  		if err := json.Unmarshal(req.PriorPrivate, &priorPrivate); err != nil {
   617  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   618  			return resp, nil
   619  		}
   620  	}
   621  
   622  	priorState.Meta = priorPrivate
   623  
   624  	// Ensure there are no nulls that will cause helper/schema to panic.
   625  	if err := validateConfigNulls(proposedNewStateVal, nil); err != nil {
   626  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   627  		return resp, nil
   628  	}
   629  
   630  	// turn the proposed state into a legacy configuration
   631  	cfg := terraform.NewResourceConfigShimmed(proposedNewStateVal, schemaBlock)
   632  
   633  	diff, err := s.provider.SimpleDiff(info, priorState, cfg)
   634  	if err != nil {
   635  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   636  		return resp, nil
   637  	}
   638  
   639  	// if this is a new instance, we need to make sure ID is going to be computed
   640  	if create {
   641  		if diff == nil {
   642  			diff = terraform.NewInstanceDiff()
   643  		}
   644  
   645  		diff.Attributes["id"] = &terraform.ResourceAttrDiff{
   646  			NewComputed: true,
   647  		}
   648  	}
   649  
   650  	if diff == nil || len(diff.Attributes) == 0 {
   651  		// schema.Provider.Diff returns nil if it ends up making a diff with no
   652  		// changes, but our new interface wants us to return an actual change
   653  		// description that _shows_ there are no changes. This is always the
   654  		// prior state, because we force a diff above if this is a new instance.
   655  		resp.PlannedState = req.PriorState
   656  		resp.PlannedPrivate = req.PriorPrivate
   657  		return resp, nil
   658  	}
   659  
   660  	if priorState == nil {
   661  		priorState = &terraform.InstanceState{}
   662  	}
   663  
   664  	// now we need to apply the diff to the prior state, so get the planned state
   665  	plannedAttrs, err := diff.Apply(priorState.Attributes, schemaBlock)
   666  
   667  	plannedStateVal, err := hcl2shim.HCL2ValueFromFlatmap(plannedAttrs, schemaBlock.ImpliedType())
   668  	if err != nil {
   669  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   670  		return resp, nil
   671  	}
   672  
   673  	plannedStateVal, err = schemaBlock.CoerceValue(plannedStateVal)
   674  	if err != nil {
   675  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   676  		return resp, nil
   677  	}
   678  
   679  	plannedStateVal = normalizeNullValues(plannedStateVal, proposedNewStateVal, false)
   680  
   681  	if err != nil {
   682  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   683  		return resp, nil
   684  	}
   685  
   686  	plannedStateVal = copyTimeoutValues(plannedStateVal, proposedNewStateVal)
   687  
   688  	// The old SDK code has some imprecisions that cause it to sometimes
   689  	// generate differences that the SDK itself does not consider significant
   690  	// but Terraform Core would. To avoid producing weird do-nothing diffs
   691  	// in that case, we'll check if the provider as produced something we
   692  	// think is "equivalent" to the prior state and just return the prior state
   693  	// itself if so, thus ensuring that Terraform Core will treat this as
   694  	// a no-op. See the docs for ValuesSDKEquivalent for some caveats on its
   695  	// accuracy.
   696  	forceNoChanges := false
   697  	if hcl2shim.ValuesSDKEquivalent(priorStateVal, plannedStateVal) {
   698  		plannedStateVal = priorStateVal
   699  		forceNoChanges = true
   700  	}
   701  
   702  	// if this was creating the resource, we need to set any remaining computed
   703  	// fields
   704  	if create {
   705  		plannedStateVal = SetUnknowns(plannedStateVal, schemaBlock)
   706  	}
   707  
   708  	plannedMP, err := msgpack.Marshal(plannedStateVal, schemaBlock.ImpliedType())
   709  	if err != nil {
   710  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   711  		return resp, nil
   712  	}
   713  	resp.PlannedState = &proto.DynamicValue{
   714  		Msgpack: plannedMP,
   715  	}
   716  
   717  	// encode any timeouts into the diff Meta
   718  	t := &schema.ResourceTimeout{}
   719  	if err := t.ConfigDecode(res, cfg); err != nil {
   720  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   721  		return resp, nil
   722  	}
   723  
   724  	if err := t.DiffEncode(diff); err != nil {
   725  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   726  		return resp, nil
   727  	}
   728  
   729  	// Now we need to store any NewExtra values, which are where any actual
   730  	// StateFunc modified config fields are hidden.
   731  	privateMap := diff.Meta
   732  	if privateMap == nil {
   733  		privateMap = map[string]interface{}{}
   734  	}
   735  
   736  	newExtra := map[string]interface{}{}
   737  
   738  	for k, v := range diff.Attributes {
   739  		if v.NewExtra != nil {
   740  			newExtra[k] = v.NewExtra
   741  		}
   742  	}
   743  	privateMap[newExtraKey] = newExtra
   744  
   745  	// the Meta field gets encoded into PlannedPrivate
   746  	plannedPrivate, err := json.Marshal(privateMap)
   747  	if err != nil {
   748  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   749  		return resp, nil
   750  	}
   751  	resp.PlannedPrivate = plannedPrivate
   752  
   753  	// collect the attributes that require instance replacement, and convert
   754  	// them to cty.Paths.
   755  	var requiresNew []string
   756  	if !forceNoChanges {
   757  		for attr, d := range diff.Attributes {
   758  			if d.RequiresNew {
   759  				requiresNew = append(requiresNew, attr)
   760  			}
   761  		}
   762  	}
   763  
   764  	// If anything requires a new resource already, or the "id" field indicates
   765  	// that we will be creating a new resource, then we need to add that to
   766  	// RequiresReplace so that core can tell if the instance is being replaced
   767  	// even if changes are being suppressed via "ignore_changes".
   768  	id := plannedStateVal.GetAttr("id")
   769  	if len(requiresNew) > 0 || id.IsNull() || !id.IsKnown() {
   770  		requiresNew = append(requiresNew, "id")
   771  	}
   772  
   773  	requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schemaBlock.ImpliedType())
   774  	if err != nil {
   775  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   776  		return resp, nil
   777  	}
   778  
   779  	// convert these to the protocol structures
   780  	for _, p := range requiresReplace {
   781  		resp.RequiresReplace = append(resp.RequiresReplace, pathToAttributePath(p))
   782  	}
   783  
   784  	return resp, nil
   785  }
   786  
   787  func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) {
   788  	resp := &proto.ApplyResourceChange_Response{
   789  		// Start with the existing state as a fallback
   790  		NewState: req.PriorState,
   791  	}
   792  
   793  	res := s.provider.ResourcesMap[req.TypeName]
   794  	schemaBlock := s.getResourceSchemaBlock(req.TypeName)
   795  
   796  	priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, schemaBlock.ImpliedType())
   797  	if err != nil {
   798  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   799  		return resp, nil
   800  	}
   801  
   802  	plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, schemaBlock.ImpliedType())
   803  	if err != nil {
   804  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   805  		return resp, nil
   806  	}
   807  
   808  	info := &terraform.InstanceInfo{
   809  		Type: req.TypeName,
   810  	}
   811  
   812  	priorState, err := res.ShimInstanceStateFromValue(priorStateVal)
   813  	if err != nil {
   814  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   815  		return resp, nil
   816  	}
   817  
   818  	private := make(map[string]interface{})
   819  	if len(req.PlannedPrivate) > 0 {
   820  		if err := json.Unmarshal(req.PlannedPrivate, &private); err != nil {
   821  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   822  			return resp, nil
   823  		}
   824  	}
   825  
   826  	var diff *terraform.InstanceDiff
   827  	destroy := false
   828  
   829  	// a null state means we are destroying the instance
   830  	if plannedStateVal.IsNull() {
   831  		destroy = true
   832  		diff = &terraform.InstanceDiff{
   833  			Attributes: make(map[string]*terraform.ResourceAttrDiff),
   834  			Meta:       make(map[string]interface{}),
   835  			Destroy:    true,
   836  		}
   837  	} else {
   838  		diff, err = schema.DiffFromValues(priorStateVal, plannedStateVal, stripResourceModifiers(res))
   839  		if err != nil {
   840  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   841  			return resp, nil
   842  		}
   843  	}
   844  
   845  	if diff == nil {
   846  		diff = &terraform.InstanceDiff{
   847  			Attributes: make(map[string]*terraform.ResourceAttrDiff),
   848  			Meta:       make(map[string]interface{}),
   849  		}
   850  	}
   851  
   852  	// add NewExtra Fields that may have been stored in the private data
   853  	if newExtra := private[newExtraKey]; newExtra != nil {
   854  		for k, v := range newExtra.(map[string]interface{}) {
   855  			d := diff.Attributes[k]
   856  
   857  			if d == nil {
   858  				d = &terraform.ResourceAttrDiff{}
   859  			}
   860  
   861  			d.NewExtra = v
   862  			diff.Attributes[k] = d
   863  		}
   864  	}
   865  
   866  	if private != nil {
   867  		diff.Meta = private
   868  	}
   869  
   870  	for k, d := range diff.Attributes {
   871  		// We need to turn off any RequiresNew. There could be attributes
   872  		// without changes in here inserted by helper/schema, but if they have
   873  		// RequiresNew then the state will be dropped from the ResourceData.
   874  		d.RequiresNew = false
   875  
   876  		// Check that any "removed" attributes that don't actually exist in the
   877  		// prior state, or helper/schema will confuse itself
   878  		if d.NewRemoved {
   879  			if _, ok := priorState.Attributes[k]; !ok {
   880  				delete(diff.Attributes, k)
   881  			}
   882  		}
   883  	}
   884  
   885  	newInstanceState, err := s.provider.Apply(info, priorState, diff)
   886  	// we record the error here, but continue processing any returned state.
   887  	if err != nil {
   888  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   889  	}
   890  	newStateVal := cty.NullVal(schemaBlock.ImpliedType())
   891  
   892  	// Always return a null value for destroy.
   893  	// While this is usually indicated by a nil state, check for missing ID or
   894  	// attributes in the case of a provider failure.
   895  	if destroy || newInstanceState == nil || newInstanceState.Attributes == nil || newInstanceState.ID == "" {
   896  		newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
   897  		if err != nil {
   898  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   899  			return resp, nil
   900  		}
   901  		resp.NewState = &proto.DynamicValue{
   902  			Msgpack: newStateMP,
   903  		}
   904  		return resp, nil
   905  	}
   906  
   907  	// We keep the null val if we destroyed the resource, otherwise build the
   908  	// entire object, even if the new state was nil.
   909  	newStateVal, err = schema.StateValueFromInstanceState(newInstanceState, schemaBlock.ImpliedType())
   910  	if err != nil {
   911  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   912  		return resp, nil
   913  	}
   914  
   915  	newStateVal = normalizeNullValues(newStateVal, plannedStateVal, true)
   916  
   917  	newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
   918  
   919  	newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
   920  	if err != nil {
   921  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   922  		return resp, nil
   923  	}
   924  	resp.NewState = &proto.DynamicValue{
   925  		Msgpack: newStateMP,
   926  	}
   927  
   928  	meta, err := json.Marshal(newInstanceState.Meta)
   929  	if err != nil {
   930  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   931  		return resp, nil
   932  	}
   933  	resp.Private = meta
   934  
   935  	// This is a signal to Terraform Core that we're doing the best we can to
   936  	// shim the legacy type system of the SDK onto the Terraform type system
   937  	// but we need it to cut us some slack. This setting should not be taken
   938  	// forward to any new SDK implementations, since setting it prevents us
   939  	// from catching certain classes of provider bug that can lead to
   940  	// confusing downstream errors.
   941  	resp.LegacyTypeSystem = true
   942  
   943  	return resp, nil
   944  }
   945  
   946  func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.ImportResourceState_Request) (*proto.ImportResourceState_Response, error) {
   947  	resp := &proto.ImportResourceState_Response{}
   948  
   949  	info := &terraform.InstanceInfo{
   950  		Type: req.TypeName,
   951  	}
   952  
   953  	newInstanceStates, err := s.provider.ImportState(info, req.Id)
   954  	if err != nil {
   955  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   956  		return resp, nil
   957  	}
   958  
   959  	for _, is := range newInstanceStates {
   960  		// copy the ID again just to be sure it wasn't missed
   961  		is.Attributes["id"] = is.ID
   962  
   963  		resourceType := is.Ephemeral.Type
   964  		if resourceType == "" {
   965  			resourceType = req.TypeName
   966  		}
   967  
   968  		schemaBlock := s.getResourceSchemaBlock(resourceType)
   969  		newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schemaBlock.ImpliedType())
   970  		if err != nil {
   971  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   972  			return resp, nil
   973  		}
   974  
   975  		// Normalize the value and fill in any missing blocks.
   976  		newStateVal = objchange.NormalizeObjectFromLegacySDK(newStateVal, schemaBlock)
   977  
   978  		newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
   979  		if err != nil {
   980  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   981  			return resp, nil
   982  		}
   983  
   984  		meta, err := json.Marshal(is.Meta)
   985  		if err != nil {
   986  			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
   987  			return resp, nil
   988  		}
   989  
   990  		importedResource := &proto.ImportResourceState_ImportedResource{
   991  			TypeName: resourceType,
   992  			State: &proto.DynamicValue{
   993  				Msgpack: newStateMP,
   994  			},
   995  			Private: meta,
   996  		}
   997  
   998  		resp.ImportedResources = append(resp.ImportedResources, importedResource)
   999  	}
  1000  
  1001  	return resp, nil
  1002  }
  1003  
  1004  func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDataSource_Request) (*proto.ReadDataSource_Response, error) {
  1005  	resp := &proto.ReadDataSource_Response{}
  1006  
  1007  	schemaBlock := s.getDatasourceSchemaBlock(req.TypeName)
  1008  
  1009  	configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
  1010  	if err != nil {
  1011  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1012  		return resp, nil
  1013  	}
  1014  
  1015  	info := &terraform.InstanceInfo{
  1016  		Type: req.TypeName,
  1017  	}
  1018  
  1019  	// Ensure there are no nulls that will cause helper/schema to panic.
  1020  	if err := validateConfigNulls(configVal, nil); err != nil {
  1021  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1022  		return resp, nil
  1023  	}
  1024  
  1025  	config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
  1026  
  1027  	// we need to still build the diff separately with the Read method to match
  1028  	// the old behavior
  1029  	diff, err := s.provider.ReadDataDiff(info, config)
  1030  	if err != nil {
  1031  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1032  		return resp, nil
  1033  	}
  1034  
  1035  	// now we can get the new complete data source
  1036  	newInstanceState, err := s.provider.ReadDataApply(info, diff)
  1037  	if err != nil {
  1038  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1039  		return resp, nil
  1040  	}
  1041  
  1042  	newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, schemaBlock.ImpliedType())
  1043  	if err != nil {
  1044  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1045  		return resp, nil
  1046  	}
  1047  
  1048  	newStateVal = copyTimeoutValues(newStateVal, configVal)
  1049  
  1050  	newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
  1051  	if err != nil {
  1052  		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
  1053  		return resp, nil
  1054  	}
  1055  	resp.State = &proto.DynamicValue{
  1056  		Msgpack: newStateMP,
  1057  	}
  1058  	return resp, nil
  1059  }
  1060  
  1061  func pathToAttributePath(path cty.Path) *proto.AttributePath {
  1062  	var steps []*proto.AttributePath_Step
  1063  
  1064  	for _, step := range path {
  1065  		switch s := step.(type) {
  1066  		case cty.GetAttrStep:
  1067  			steps = append(steps, &proto.AttributePath_Step{
  1068  				Selector: &proto.AttributePath_Step_AttributeName{
  1069  					AttributeName: s.Name,
  1070  				},
  1071  			})
  1072  		case cty.IndexStep:
  1073  			ty := s.Key.Type()
  1074  			switch ty {
  1075  			case cty.Number:
  1076  				i, _ := s.Key.AsBigFloat().Int64()
  1077  				steps = append(steps, &proto.AttributePath_Step{
  1078  					Selector: &proto.AttributePath_Step_ElementKeyInt{
  1079  						ElementKeyInt: i,
  1080  					},
  1081  				})
  1082  			case cty.String:
  1083  				steps = append(steps, &proto.AttributePath_Step{
  1084  					Selector: &proto.AttributePath_Step_ElementKeyString{
  1085  						ElementKeyString: s.Key.AsString(),
  1086  					},
  1087  				})
  1088  			}
  1089  		}
  1090  	}
  1091  
  1092  	return &proto.AttributePath{Steps: steps}
  1093  }
  1094  
  1095  // helper/schema throws away timeout values from the config and stores them in
  1096  // the Private/Meta fields. we need to copy those values into the planned state
  1097  // so that core doesn't see a perpetual diff with the timeout block.
  1098  func copyTimeoutValues(to cty.Value, from cty.Value) cty.Value {
  1099  	// if `to` is null we are planning to remove it altogether.
  1100  	if to.IsNull() {
  1101  		return to
  1102  	}
  1103  	toAttrs := to.AsValueMap()
  1104  	// We need to remove the key since the hcl2shims will add a non-null block
  1105  	// because we can't determine if a single block was null from the flatmapped
  1106  	// values. This needs to conform to the correct schema for marshaling, so
  1107  	// change the value to null rather than deleting it from the object map.
  1108  	timeouts, ok := toAttrs[schema.TimeoutsConfigKey]
  1109  	if ok {
  1110  		toAttrs[schema.TimeoutsConfigKey] = cty.NullVal(timeouts.Type())
  1111  	}
  1112  
  1113  	// if from is null then there are no timeouts to copy
  1114  	if from.IsNull() {
  1115  		return cty.ObjectVal(toAttrs)
  1116  	}
  1117  
  1118  	fromAttrs := from.AsValueMap()
  1119  	timeouts, ok = fromAttrs[schema.TimeoutsConfigKey]
  1120  
  1121  	// timeouts shouldn't be unknown, but don't copy possibly invalid values either
  1122  	if !ok || timeouts.IsNull() || !timeouts.IsWhollyKnown() {
  1123  		// no timeouts block to copy
  1124  		return cty.ObjectVal(toAttrs)
  1125  	}
  1126  
  1127  	toAttrs[schema.TimeoutsConfigKey] = timeouts
  1128  
  1129  	return cty.ObjectVal(toAttrs)
  1130  }
  1131  
  1132  // stripResourceModifiers takes a *schema.Resource and returns a deep copy with all
  1133  // StateFuncs and CustomizeDiffs removed. This will be used during apply to
  1134  // create a diff from a planned state where the diff modifications have already
  1135  // been applied.
  1136  func stripResourceModifiers(r *schema.Resource) *schema.Resource {
  1137  	if r == nil {
  1138  		return nil
  1139  	}
  1140  	// start with a shallow copy
  1141  	newResource := new(schema.Resource)
  1142  	*newResource = *r
  1143  
  1144  	newResource.CustomizeDiff = nil
  1145  	newResource.Schema = map[string]*schema.Schema{}
  1146  
  1147  	for k, s := range r.Schema {
  1148  		newResource.Schema[k] = stripSchema(s)
  1149  	}
  1150  
  1151  	return newResource
  1152  }
  1153  
  1154  func stripSchema(s *schema.Schema) *schema.Schema {
  1155  	if s == nil {
  1156  		return nil
  1157  	}
  1158  	// start with a shallow copy
  1159  	newSchema := new(schema.Schema)
  1160  	*newSchema = *s
  1161  
  1162  	newSchema.StateFunc = nil
  1163  
  1164  	switch e := newSchema.Elem.(type) {
  1165  	case *schema.Schema:
  1166  		newSchema.Elem = stripSchema(e)
  1167  	case *schema.Resource:
  1168  		newSchema.Elem = stripResourceModifiers(e)
  1169  	}
  1170  
  1171  	return newSchema
  1172  }
  1173  
  1174  // Zero values and empty containers may be interchanged by the apply process.
  1175  // When there is a discrepency between src and dst value being null or empty,
  1176  // prefer the src value. This takes a little more liberty with set types, since
  1177  // we can't correlate modified set values. In the case of sets, if the src set
  1178  // was wholly known we assume the value was correctly applied and copy that
  1179  // entirely to the new value.
  1180  // While apply prefers the src value, during plan we prefer dst whenever there
  1181  // is an unknown or a set is involved, since the plan can alter the value
  1182  // however it sees fit. This however means that a CustomizeDiffFunction may not
  1183  // be able to change a null to an empty value or vice versa, but that should be
  1184  // very uncommon nor was it reliable before 0.12 either.
  1185  func normalizeNullValues(dst, src cty.Value, apply bool) cty.Value {
  1186  	ty := dst.Type()
  1187  	if !src.IsNull() && !src.IsKnown() {
  1188  		// Return src during plan to retain unknown interpolated placeholders,
  1189  		// which could be lost if we're only updating a resource. If this is a
  1190  		// read scenario, then there shouldn't be any unknowns at all.
  1191  		if dst.IsNull() && !apply {
  1192  			return src
  1193  		}
  1194  		return dst
  1195  	}
  1196  
  1197  	// Handle null/empty changes for collections during apply.
  1198  	// A change between null and empty values prefers src to make sure the state
  1199  	// is consistent between plan and apply.
  1200  	if ty.IsCollectionType() && apply {
  1201  		dstEmpty := !dst.IsNull() && dst.IsKnown() && dst.LengthInt() == 0
  1202  		srcEmpty := !src.IsNull() && src.IsKnown() && src.LengthInt() == 0
  1203  
  1204  		if (src.IsNull() && dstEmpty) || (srcEmpty && dst.IsNull()) {
  1205  			return src
  1206  		}
  1207  	}
  1208  
  1209  	// check the invariants that we need below, to ensure we are working with
  1210  	// non-null and known values.
  1211  	if src.IsNull() || !src.IsKnown() || !dst.IsKnown() {
  1212  		return dst
  1213  	}
  1214  
  1215  	switch {
  1216  	case ty.IsMapType(), ty.IsObjectType():
  1217  		var dstMap map[string]cty.Value
  1218  		if !dst.IsNull() {
  1219  			dstMap = dst.AsValueMap()
  1220  		}
  1221  		if dstMap == nil {
  1222  			dstMap = map[string]cty.Value{}
  1223  		}
  1224  
  1225  		srcMap := src.AsValueMap()
  1226  		for key, v := range srcMap {
  1227  			dstVal, ok := dstMap[key]
  1228  			if !ok && apply && ty.IsMapType() {
  1229  				// don't transfer old map values to dst during apply
  1230  				continue
  1231  			}
  1232  
  1233  			if dstVal == cty.NilVal {
  1234  				if !apply && ty.IsMapType() {
  1235  					// let plan shape this map however it wants
  1236  					continue
  1237  				}
  1238  				dstVal = cty.NullVal(v.Type())
  1239  			}
  1240  
  1241  			dstMap[key] = normalizeNullValues(dstVal, v, apply)
  1242  		}
  1243  
  1244  		// you can't call MapVal/ObjectVal with empty maps, but nothing was
  1245  		// copied in anyway. If the dst is nil, and the src is known, assume the
  1246  		// src is correct.
  1247  		if len(dstMap) == 0 {
  1248  			if dst.IsNull() && src.IsWhollyKnown() && apply {
  1249  				return src
  1250  			}
  1251  			return dst
  1252  		}
  1253  
  1254  		if ty.IsMapType() {
  1255  			// helper/schema will populate an optional+computed map with
  1256  			// unknowns which we have to fixup here.
  1257  			// It would be preferable to simply prevent any known value from
  1258  			// becoming unknown, but concessions have to be made to retain the
  1259  			// broken legacy behavior when possible.
  1260  			for k, srcVal := range srcMap {
  1261  				if !srcVal.IsNull() && srcVal.IsKnown() {
  1262  					dstVal, ok := dstMap[k]
  1263  					if !ok {
  1264  						continue
  1265  					}
  1266  
  1267  					if !dstVal.IsNull() && !dstVal.IsKnown() {
  1268  						dstMap[k] = srcVal
  1269  					}
  1270  				}
  1271  			}
  1272  
  1273  			return cty.MapVal(dstMap)
  1274  		}
  1275  
  1276  		return cty.ObjectVal(dstMap)
  1277  
  1278  	case ty.IsSetType():
  1279  		// If the original was wholly known, then we expect that is what the
  1280  		// provider applied. The apply process loses too much information to
  1281  		// reliably re-create the set.
  1282  		if src.IsWhollyKnown() && apply {
  1283  			return src
  1284  		}
  1285  
  1286  	case ty.IsListType(), ty.IsTupleType():
  1287  		// If the dst is null, and the src is known, then we lost an empty value
  1288  		// so take the original.
  1289  		if dst.IsNull() {
  1290  			if src.IsWhollyKnown() && src.LengthInt() == 0 && apply {
  1291  				return src
  1292  			}
  1293  
  1294  			// if dst is null and src only contains unknown values, then we lost
  1295  			// those during a read or plan.
  1296  			if !apply && !src.IsNull() {
  1297  				allUnknown := true
  1298  				for _, v := range src.AsValueSlice() {
  1299  					if v.IsKnown() {
  1300  						allUnknown = false
  1301  						break
  1302  					}
  1303  				}
  1304  				if allUnknown {
  1305  					return src
  1306  				}
  1307  			}
  1308  
  1309  			return dst
  1310  		}
  1311  
  1312  		// if the lengths are identical, then iterate over each element in succession.
  1313  		srcLen := src.LengthInt()
  1314  		dstLen := dst.LengthInt()
  1315  		if srcLen == dstLen && srcLen > 0 {
  1316  			srcs := src.AsValueSlice()
  1317  			dsts := dst.AsValueSlice()
  1318  
  1319  			for i := 0; i < srcLen; i++ {
  1320  				dsts[i] = normalizeNullValues(dsts[i], srcs[i], apply)
  1321  			}
  1322  
  1323  			if ty.IsTupleType() {
  1324  				return cty.TupleVal(dsts)
  1325  			}
  1326  			return cty.ListVal(dsts)
  1327  		}
  1328  
  1329  	case ty == cty.String:
  1330  		// The legacy SDK should not be able to remove a value during plan or
  1331  		// apply, however we are only going to overwrite this if the source was
  1332  		// an empty string, since that is what is often equated with unset and
  1333  		// lost in the diff process.
  1334  		if dst.IsNull() && src.AsString() == "" {
  1335  			return src
  1336  		}
  1337  	}
  1338  
  1339  	return dst
  1340  }
  1341  
  1342  // validateConfigNulls checks a config value for unsupported nulls before
  1343  // attempting to shim the value. While null values can mostly be ignored in the
  1344  // configuration, since they're not supported in HCL1, the case where a null
  1345  // appears in a list-like attribute (list, set, tuple) will present a nil value
  1346  // to helper/schema which can panic. Return an error to the user in this case,
  1347  // indicating the attribute with the null value.
  1348  func validateConfigNulls(v cty.Value, path cty.Path) []*proto.Diagnostic {
  1349  	var diags []*proto.Diagnostic
  1350  	if v.IsNull() || !v.IsKnown() {
  1351  		return diags
  1352  	}
  1353  
  1354  	switch {
  1355  	case v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType():
  1356  		it := v.ElementIterator()
  1357  		for it.Next() {
  1358  			kv, ev := it.Element()
  1359  			if ev.IsNull() {
  1360  				// if this is a set, the kv is also going to be null which
  1361  				// isn't a valid path element, so we can't append it to the
  1362  				// diagnostic.
  1363  				p := path
  1364  				if !kv.IsNull() {
  1365  					p = append(p, cty.IndexStep{Key: kv})
  1366  				}
  1367  
  1368  				diags = append(diags, &proto.Diagnostic{
  1369  					Severity:  proto.Diagnostic_ERROR,
  1370  					Summary:   "Null value found in list",
  1371  					Detail:    "Null values are not allowed for this attribute value.",
  1372  					Attribute: convert.PathToAttributePath(p),
  1373  				})
  1374  				continue
  1375  			}
  1376  
  1377  			d := validateConfigNulls(ev, append(path, cty.IndexStep{Key: kv}))
  1378  			diags = convert.AppendProtoDiag(diags, d)
  1379  		}
  1380  
  1381  	case v.Type().IsMapType() || v.Type().IsObjectType():
  1382  		it := v.ElementIterator()
  1383  		for it.Next() {
  1384  			kv, ev := it.Element()
  1385  			var step cty.PathStep
  1386  			switch {
  1387  			case v.Type().IsMapType():
  1388  				step = cty.IndexStep{Key: kv}
  1389  			case v.Type().IsObjectType():
  1390  				step = cty.GetAttrStep{Name: kv.AsString()}
  1391  			}
  1392  			d := validateConfigNulls(ev, append(path, step))
  1393  			diags = convert.AppendProtoDiag(diags, d)
  1394  		}
  1395  	}
  1396  
  1397  	return diags
  1398  }