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