github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource.go (about)

     1  package schema
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"strconv"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  var ReservedDataSourceFields = []string{
    14  	"connection",
    15  	"count",
    16  	"depends_on",
    17  	"lifecycle",
    18  	"provider",
    19  	"provisioner",
    20  }
    21  
    22  var ReservedResourceFields = []string{
    23  	"connection",
    24  	"count",
    25  	"depends_on",
    26  	"id",
    27  	"lifecycle",
    28  	"provider",
    29  	"provisioner",
    30  }
    31  
    32  // Resource represents a thing in Terraform that has a set of configurable
    33  // attributes and a lifecycle (create, read, update, delete).
    34  //
    35  // The Resource schema is an abstraction that allows provider writers to
    36  // worry only about CRUD operations while off-loading validation, diff
    37  // generation, etc. to this higher level library.
    38  //
    39  // In spite of the name, this struct is not used only for terraform resources,
    40  // but also for data sources. In the case of data sources, the Create,
    41  // Update and Delete functions must not be provided.
    42  type Resource struct {
    43  	// Schema is the schema for the configuration of this resource.
    44  	//
    45  	// The keys of this map are the configuration keys, and the values
    46  	// describe the schema of the configuration value.
    47  	//
    48  	// The schema is used to represent both configurable data as well
    49  	// as data that might be computed in the process of creating this
    50  	// resource.
    51  	Schema map[string]*Schema
    52  
    53  	// SchemaVersion is the version number for this resource's Schema
    54  	// definition. The current SchemaVersion stored in the state for each
    55  	// resource. Provider authors can increment this version number
    56  	// when Schema semantics change. If the State's SchemaVersion is less than
    57  	// the current SchemaVersion, the InstanceState is yielded to the
    58  	// MigrateState callback, where the provider can make whatever changes it
    59  	// needs to update the state to be compatible to the latest version of the
    60  	// Schema.
    61  	//
    62  	// When unset, SchemaVersion defaults to 0, so provider authors can start
    63  	// their Versioning at any integer >= 1
    64  	SchemaVersion int
    65  
    66  	// MigrateState is deprecated and any new changes to a resource's schema
    67  	// should be handled by StateUpgraders. Existing MigrateState implementations
    68  	// should remain for compatibility with existing state. MigrateState will
    69  	// still be called if the stored SchemaVersion is less than the
    70  	// first version of the StateUpgraders.
    71  	//
    72  	// MigrateState is responsible for updating an InstanceState with an old
    73  	// version to the format expected by the current version of the Schema.
    74  	//
    75  	// It is called during Refresh if the State's stored SchemaVersion is less
    76  	// than the current SchemaVersion of the Resource.
    77  	//
    78  	// The function is yielded the state's stored SchemaVersion and a pointer to
    79  	// the InstanceState that needs updating, as well as the configured
    80  	// provider's configured meta interface{}, in case the migration process
    81  	// needs to make any remote API calls.
    82  	MigrateState StateMigrateFunc
    83  
    84  	// StateUpgraders contains the functions responsible for upgrading an
    85  	// existing state with an old schema version to a newer schema. It is
    86  	// called specifically by Terraform when the stored schema version is less
    87  	// than the current SchemaVersion of the Resource.
    88  	//
    89  	// StateUpgraders map specific schema versions to a StateUpgrader
    90  	// function. The registered versions are expected to be ordered,
    91  	// consecutive values. The initial value may be greater than 0 to account
    92  	// for legacy schemas that weren't recorded and can be handled by
    93  	// MigrateState.
    94  	StateUpgraders []StateUpgrader
    95  
    96  	// The functions below are the CRUD operations for this resource.
    97  	//
    98  	// The only optional operation is Update. If Update is not implemented,
    99  	// then updates will not be supported for this resource.
   100  	//
   101  	// The ResourceData parameter in the functions below are used to
   102  	// query configuration and changes for the resource as well as to set
   103  	// the ID, computed data, etc.
   104  	//
   105  	// The interface{} parameter is the result of the ConfigureFunc in
   106  	// the provider for this resource. If the provider does not define
   107  	// a ConfigureFunc, this will be nil. This parameter should be used
   108  	// to store API clients, configuration structures, etc.
   109  	//
   110  	// If any errors occur during each of the operation, an error should be
   111  	// returned. If a resource was partially updated, be careful to enable
   112  	// partial state mode for ResourceData and use it accordingly.
   113  	//
   114  	// Exists is a function that is called to check if a resource still
   115  	// exists. If this returns false, then this will affect the diff
   116  	// accordingly. If this function isn't set, it will not be called. You
   117  	// can also signal existence in the Read method by calling d.SetId("")
   118  	// if the Resource is no longer present and should be removed from state.
   119  	// The *ResourceData passed to Exists should _not_ be modified.
   120  	Create CreateFunc
   121  	Read   ReadFunc
   122  	Update UpdateFunc
   123  	Delete DeleteFunc
   124  	Exists ExistsFunc
   125  
   126  	// CustomizeDiff is a custom function for working with the diff that
   127  	// Terraform has created for this resource - it can be used to customize the
   128  	// diff that has been created, diff values not controlled by configuration,
   129  	// or even veto the diff altogether and abort the plan. It is passed a
   130  	// *ResourceDiff, a structure similar to ResourceData but lacking most write
   131  	// functions like Set, while introducing new functions that work with the
   132  	// diff such as SetNew, SetNewComputed, and ForceNew.
   133  	//
   134  	// The phases Terraform runs this in, and the state available via functions
   135  	// like Get and GetChange, are as follows:
   136  	//
   137  	//  * New resource: One run with no state
   138  	//  * Existing resource: One run with state
   139  	//   * Existing resource, forced new: One run with state (before ForceNew),
   140  	//     then one run without state (as if new resource)
   141  	//  * Tainted resource: No runs (custom diff logic is skipped)
   142  	//  * Destroy: No runs (standard diff logic is skipped on destroy diffs)
   143  	//
   144  	// This function needs to be resilient to support all scenarios.
   145  	//
   146  	// If this function needs to access external API resources, remember to flag
   147  	// the RequiresRefresh attribute mentioned below to ensure that
   148  	// -refresh=false is blocked when running plan or apply, as this means that
   149  	// this resource requires refresh-like behaviour to work effectively.
   150  	//
   151  	// For the most part, only computed fields can be customized by this
   152  	// function.
   153  	//
   154  	// This function is only allowed on regular resources (not data sources).
   155  	CustomizeDiff CustomizeDiffFunc
   156  
   157  	// Importer is the ResourceImporter implementation for this resource.
   158  	// If this is nil, then this resource does not support importing. If
   159  	// this is non-nil, then it supports importing and ResourceImporter
   160  	// must be validated. The validity of ResourceImporter is verified
   161  	// by InternalValidate on Resource.
   162  	Importer *ResourceImporter
   163  
   164  	// If non-empty, this string is emitted as a warning during Validate.
   165  	DeprecationMessage string
   166  
   167  	// Timeouts allow users to specify specific time durations in which an
   168  	// operation should time out, to allow them to extend an action to suit their
   169  	// usage. For example, a user may specify a large Creation timeout for their
   170  	// AWS RDS Instance due to it's size, or restoring from a snapshot.
   171  	// Resource implementors must enable Timeout support by adding the allowed
   172  	// actions (Create, Read, Update, Delete, Default) to the Resource struct, and
   173  	// accessing them in the matching methods.
   174  	Timeouts *ResourceTimeout
   175  
   176  	// Description is used as the description for docs, the language server and
   177  	// other user facing usage. It can be plain-text or markdown depending on the
   178  	// global DescriptionKind setting.
   179  	Description string
   180  
   181  	// UseJSONNumber should be set when state upgraders will expect
   182  	// json.Numbers instead of float64s for numbers. This is added as a
   183  	// toggle for backwards compatibility for type assertions, but should
   184  	// be used in all new resources to avoid bugs with sufficiently large
   185  	// user input.
   186  	//
   187  	// See github.com/hashicorp/terraform-plugin-sdk/issues/655 for more
   188  	// details.
   189  	UseJSONNumber bool
   190  }
   191  
   192  // ShimInstanceStateFromValue converts a cty.Value to a
   193  // terraform.InstanceState.
   194  func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.InstanceState, error) {
   195  	// Get the raw shimmed value. While this is correct, the set hashes don't
   196  	// match those from the Schema.
   197  	s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion)
   198  
   199  	// We now rebuild the state through the ResourceData, so that the set indexes
   200  	// match what helper/schema expects.
   201  	data, err := schemaMap(r.Schema).Data(s, nil)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	s = data.State()
   207  	if s == nil {
   208  		s = &terraform.InstanceState{}
   209  	}
   210  	return s, nil
   211  }
   212  
   213  // See Resource documentation.
   214  type CreateFunc func(*ResourceData, interface{}) error
   215  
   216  // See Resource documentation.
   217  type ReadFunc func(*ResourceData, interface{}) error
   218  
   219  // See Resource documentation.
   220  type UpdateFunc func(*ResourceData, interface{}) error
   221  
   222  // See Resource documentation.
   223  type DeleteFunc func(*ResourceData, interface{}) error
   224  
   225  // See Resource documentation.
   226  type ExistsFunc func(*ResourceData, interface{}) (bool, error)
   227  
   228  // See Resource documentation.
   229  type StateMigrateFunc func(
   230  	int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
   231  
   232  type StateUpgrader struct {
   233  	// Version is the version schema that this Upgrader will handle, converting
   234  	// it to Version+1.
   235  	Version int
   236  
   237  	// Type describes the schema that this function can upgrade. Type is
   238  	// required to decode the schema if the state was stored in a legacy
   239  	// flatmap format.
   240  	Type cty.Type
   241  
   242  	// Upgrade takes the JSON encoded state and the provider meta value, and
   243  	// upgrades the state one single schema version. The provided state is
   244  	// deocded into the default json types using a map[string]interface{}. It
   245  	// is up to the StateUpgradeFunc to ensure that the returned value can be
   246  	// encoded using the new schema.
   247  	Upgrade StateUpgradeFunc
   248  }
   249  
   250  // See StateUpgrader
   251  type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error)
   252  
   253  // See Resource documentation.
   254  type CustomizeDiffFunc func(*ResourceDiff, interface{}) error
   255  
   256  // Apply creates, updates, and/or deletes a resource.
   257  func (r *Resource) Apply(
   258  	s *terraform.InstanceState,
   259  	d *terraform.InstanceDiff,
   260  	meta interface{}) (*terraform.InstanceState, error) {
   261  	data, err := schemaMap(r.Schema).Data(s, d)
   262  	if err != nil {
   263  		return s, err
   264  	}
   265  
   266  	// Instance Diff shoould have the timeout info, need to copy it over to the
   267  	// ResourceData meta
   268  	rt := ResourceTimeout{}
   269  	if _, ok := d.Meta[TimeoutKey]; ok {
   270  		if err := rt.DiffDecode(d); err != nil {
   271  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   272  		}
   273  	} else if s != nil {
   274  		if _, ok := s.Meta[TimeoutKey]; ok {
   275  			if err := rt.StateDecode(s); err != nil {
   276  				log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   277  			}
   278  		}
   279  	} else {
   280  		log.Printf("[DEBUG] No meta timeoutkey found in Apply()")
   281  	}
   282  	data.timeouts = &rt
   283  
   284  	if s == nil {
   285  		// The Terraform API dictates that this should never happen, but
   286  		// it doesn't hurt to be safe in this case.
   287  		s = new(terraform.InstanceState)
   288  	}
   289  
   290  	if d.Destroy || d.RequiresNew() {
   291  		if s.ID != "" {
   292  			// Destroy the resource since it is created
   293  			if err := r.Delete(data, meta); err != nil {
   294  				return r.recordCurrentSchemaVersion(data.State()), err
   295  			}
   296  
   297  			// Make sure the ID is gone.
   298  			data.SetId("")
   299  		}
   300  
   301  		// If we're only destroying, and not creating, then return
   302  		// now since we're done!
   303  		if !d.RequiresNew() {
   304  			return nil, nil
   305  		}
   306  
   307  		// Reset the data to be stateless since we just destroyed
   308  		data, err = schemaMap(r.Schema).Data(nil, d)
   309  		// data was reset, need to re-apply the parsed timeouts
   310  		data.timeouts = &rt
   311  		if err != nil {
   312  			return nil, err
   313  		}
   314  	}
   315  
   316  	err = nil
   317  	if data.Id() == "" {
   318  		// We're creating, it is a new resource.
   319  		data.MarkNewResource()
   320  		err = r.Create(data, meta)
   321  	} else {
   322  		if r.Update == nil {
   323  			return s, fmt.Errorf("doesn't support update")
   324  		}
   325  
   326  		err = r.Update(data, meta)
   327  	}
   328  
   329  	return r.recordCurrentSchemaVersion(data.State()), err
   330  }
   331  
   332  // Diff returns a diff of this resource.
   333  func (r *Resource) Diff(
   334  	s *terraform.InstanceState,
   335  	c *terraform.ResourceConfig,
   336  	meta interface{}) (*terraform.InstanceDiff, error) {
   337  
   338  	t := &ResourceTimeout{}
   339  	err := t.ConfigDecode(r, c)
   340  
   341  	if err != nil {
   342  		return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
   343  	}
   344  
   345  	instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true)
   346  	if err != nil {
   347  		return instanceDiff, err
   348  	}
   349  
   350  	if instanceDiff != nil {
   351  		if err := t.DiffEncode(instanceDiff); err != nil {
   352  			log.Printf("[ERR] Error encoding timeout to instance diff: %s", err)
   353  		}
   354  	} else {
   355  		log.Printf("[DEBUG] Instance Diff is nil in Diff()")
   356  	}
   357  
   358  	return instanceDiff, err
   359  }
   360  
   361  func (r *Resource) simpleDiff(
   362  	s *terraform.InstanceState,
   363  	c *terraform.ResourceConfig,
   364  	meta interface{}) (*terraform.InstanceDiff, error) {
   365  
   366  	instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, false)
   367  	if err != nil {
   368  		return instanceDiff, err
   369  	}
   370  
   371  	if instanceDiff == nil {
   372  		instanceDiff = terraform.NewInstanceDiff()
   373  	}
   374  
   375  	// Make sure the old value is set in each of the instance diffs.
   376  	// This was done by the RequiresNew logic in the full legacy Diff.
   377  	for k, attr := range instanceDiff.Attributes {
   378  		if attr == nil {
   379  			continue
   380  		}
   381  		if s != nil {
   382  			attr.Old = s.Attributes[k]
   383  		}
   384  	}
   385  
   386  	return instanceDiff, nil
   387  }
   388  
   389  // Validate validates the resource configuration against the schema.
   390  func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
   391  	warns, errs := schemaMap(r.Schema).Validate(c)
   392  
   393  	if r.DeprecationMessage != "" {
   394  		warns = append(warns, r.DeprecationMessage)
   395  	}
   396  
   397  	return warns, errs
   398  }
   399  
   400  // ReadDataApply loads the data for a data source, given a diff that
   401  // describes the configuration arguments and desired computed attributes.
   402  func (r *Resource) ReadDataApply(
   403  	d *terraform.InstanceDiff,
   404  	meta interface{},
   405  ) (*terraform.InstanceState, error) {
   406  	// Data sources are always built completely from scratch
   407  	// on each read, so the source state is always nil.
   408  	data, err := schemaMap(r.Schema).Data(nil, d)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	err = r.Read(data, meta)
   414  	state := data.State()
   415  	if state != nil && state.ID == "" {
   416  		// Data sources can set an ID if they want, but they aren't
   417  		// required to; we'll provide a placeholder if they don't,
   418  		// to preserve the invariant that all resources have non-empty
   419  		// ids.
   420  		state.ID = "-"
   421  	}
   422  
   423  	return r.recordCurrentSchemaVersion(state), err
   424  }
   425  
   426  // RefreshWithoutUpgrade reads the instance state, but does not call
   427  // MigrateState or the StateUpgraders, since those are now invoked in a
   428  // separate API call.
   429  // RefreshWithoutUpgrade is part of the new plugin shims.
   430  func (r *Resource) RefreshWithoutUpgrade(
   431  	s *terraform.InstanceState,
   432  	meta interface{}) (*terraform.InstanceState, error) {
   433  	// If the ID is already somehow blank, it doesn't exist
   434  	if s.ID == "" {
   435  		return nil, nil
   436  	}
   437  
   438  	rt := ResourceTimeout{}
   439  	if _, ok := s.Meta[TimeoutKey]; ok {
   440  		if err := rt.StateDecode(s); err != nil {
   441  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   442  		}
   443  	}
   444  
   445  	if r.Exists != nil {
   446  		// Make a copy of data so that if it is modified it doesn't
   447  		// affect our Read later.
   448  		data, err := schemaMap(r.Schema).Data(s, nil)
   449  		data.timeouts = &rt
   450  
   451  		if err != nil {
   452  			return s, err
   453  		}
   454  
   455  		exists, err := r.Exists(data, meta)
   456  		if err != nil {
   457  			return s, err
   458  		}
   459  		if !exists {
   460  			return nil, nil
   461  		}
   462  	}
   463  
   464  	data, err := schemaMap(r.Schema).Data(s, nil)
   465  	data.timeouts = &rt
   466  	if err != nil {
   467  		return s, err
   468  	}
   469  
   470  	err = r.Read(data, meta)
   471  	state := data.State()
   472  	if state != nil && state.ID == "" {
   473  		state = nil
   474  	}
   475  
   476  	return r.recordCurrentSchemaVersion(state), err
   477  }
   478  
   479  // Refresh refreshes the state of the resource.
   480  func (r *Resource) Refresh(
   481  	s *terraform.InstanceState,
   482  	meta interface{}) (*terraform.InstanceState, error) {
   483  	// If the ID is already somehow blank, it doesn't exist
   484  	if s.ID == "" {
   485  		return nil, nil
   486  	}
   487  
   488  	rt := ResourceTimeout{}
   489  	if _, ok := s.Meta[TimeoutKey]; ok {
   490  		if err := rt.StateDecode(s); err != nil {
   491  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   492  		}
   493  	}
   494  
   495  	if r.Exists != nil {
   496  		// Make a copy of data so that if it is modified it doesn't
   497  		// affect our Read later.
   498  		data, err := schemaMap(r.Schema).Data(s, nil)
   499  		data.timeouts = &rt
   500  
   501  		if err != nil {
   502  			return s, err
   503  		}
   504  
   505  		exists, err := r.Exists(data, meta)
   506  		if err != nil {
   507  			return s, err
   508  		}
   509  		if !exists {
   510  			return nil, nil
   511  		}
   512  	}
   513  
   514  	// there may be new StateUpgraders that need to be run
   515  	s, err := r.upgradeState(s, meta)
   516  	if err != nil {
   517  		return s, err
   518  	}
   519  
   520  	data, err := schemaMap(r.Schema).Data(s, nil)
   521  	data.timeouts = &rt
   522  	if err != nil {
   523  		return s, err
   524  	}
   525  
   526  	err = r.Read(data, meta)
   527  	state := data.State()
   528  	if state != nil && state.ID == "" {
   529  		state = nil
   530  	}
   531  
   532  	return r.recordCurrentSchemaVersion(state), err
   533  }
   534  
   535  func (r *Resource) upgradeState(s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
   536  	var err error
   537  
   538  	needsMigration, stateSchemaVersion := r.checkSchemaVersion(s)
   539  	migrate := needsMigration && r.MigrateState != nil
   540  
   541  	if migrate {
   542  		s, err = r.MigrateState(stateSchemaVersion, s, meta)
   543  		if err != nil {
   544  			return s, err
   545  		}
   546  	}
   547  
   548  	if len(r.StateUpgraders) == 0 {
   549  		return s, nil
   550  	}
   551  
   552  	// If we ran MigrateState, then the stateSchemaVersion value is no longer
   553  	// correct. We can expect the first upgrade function to be the correct
   554  	// schema type version.
   555  	if migrate {
   556  		stateSchemaVersion = r.StateUpgraders[0].Version
   557  	}
   558  
   559  	schemaType := r.CoreConfigSchema().ImpliedType()
   560  	// find the expected type to convert the state
   561  	for _, upgrader := range r.StateUpgraders {
   562  		if stateSchemaVersion == upgrader.Version {
   563  			schemaType = upgrader.Type
   564  		}
   565  	}
   566  
   567  	// StateUpgraders only operate on the new JSON format state, so the state
   568  	// need to be converted.
   569  	stateVal, err := StateValueFromInstanceState(s, schemaType)
   570  	if err != nil {
   571  		return nil, err
   572  	}
   573  
   574  	jsonState, err := StateValueToJSONMap(stateVal, schemaType)
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  
   579  	for _, upgrader := range r.StateUpgraders {
   580  		if stateSchemaVersion != upgrader.Version {
   581  			continue
   582  		}
   583  
   584  		jsonState, err = upgrader.Upgrade(jsonState, meta)
   585  		if err != nil {
   586  			return nil, err
   587  		}
   588  		stateSchemaVersion++
   589  	}
   590  
   591  	// now we need to re-flatmap the new state
   592  	stateVal, err = JSONMapToStateValue(jsonState, r.CoreConfigSchema())
   593  	if err != nil {
   594  		return nil, err
   595  	}
   596  
   597  	return r.ShimInstanceStateFromValue(stateVal)
   598  }
   599  
   600  // InternalValidate should be called to validate the structure
   601  // of the resource.
   602  //
   603  // This should be called in a unit test for any resource to verify
   604  // before release that a resource is properly configured for use with
   605  // this library.
   606  //
   607  // Provider.InternalValidate() will automatically call this for all of
   608  // the resources it manages, so you don't need to call this manually if it
   609  // is part of a Provider.
   610  func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error {
   611  	if r == nil {
   612  		return errors.New("resource is nil")
   613  	}
   614  
   615  	if !writable {
   616  		if r.Create != nil || r.Update != nil || r.Delete != nil {
   617  			return fmt.Errorf("must not implement Create, Update or Delete")
   618  		}
   619  
   620  		// CustomizeDiff cannot be defined for read-only resources
   621  		if r.CustomizeDiff != nil {
   622  			return fmt.Errorf("cannot implement CustomizeDiff")
   623  		}
   624  	}
   625  
   626  	tsm := topSchemaMap
   627  
   628  	if r.isTopLevel() && writable {
   629  		// All non-Computed attributes must be ForceNew if Update is not defined
   630  		if r.Update == nil {
   631  			nonForceNewAttrs := make([]string, 0)
   632  			for k, v := range r.Schema {
   633  				if !v.ForceNew && !v.Computed {
   634  					nonForceNewAttrs = append(nonForceNewAttrs, k)
   635  				}
   636  			}
   637  			if len(nonForceNewAttrs) > 0 {
   638  				return fmt.Errorf(
   639  					"No Update defined, must set ForceNew on: %#v", nonForceNewAttrs)
   640  			}
   641  		} else {
   642  			nonUpdateableAttrs := make([]string, 0)
   643  			for k, v := range r.Schema {
   644  				if v.ForceNew || v.Computed && !v.Optional {
   645  					nonUpdateableAttrs = append(nonUpdateableAttrs, k)
   646  				}
   647  			}
   648  			updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs)
   649  			if updateableAttrs == 0 {
   650  				return fmt.Errorf(
   651  					"All fields are ForceNew or Computed w/out Optional, Update is superfluous")
   652  			}
   653  		}
   654  
   655  		tsm = schemaMap(r.Schema)
   656  
   657  		// Destroy, and Read are required
   658  		if r.Read == nil {
   659  			return fmt.Errorf("Read must be implemented")
   660  		}
   661  		if r.Delete == nil {
   662  			return fmt.Errorf("Delete must be implemented")
   663  		}
   664  
   665  		// If we have an importer, we need to verify the importer.
   666  		if r.Importer != nil {
   667  			if err := r.Importer.InternalValidate(); err != nil {
   668  				return err
   669  			}
   670  		}
   671  
   672  		for k, f := range tsm {
   673  			if isReservedResourceFieldName(k, f) {
   674  				return fmt.Errorf("%s is a reserved field name", k)
   675  			}
   676  		}
   677  	}
   678  
   679  	lastVersion := -1
   680  	for _, u := range r.StateUpgraders {
   681  		if lastVersion >= 0 && u.Version-lastVersion > 1 {
   682  			return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version)
   683  		}
   684  
   685  		if u.Version >= r.SchemaVersion {
   686  			return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion)
   687  		}
   688  
   689  		if !u.Type.IsObjectType() {
   690  			return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version)
   691  		}
   692  
   693  		if u.Upgrade == nil {
   694  			return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version)
   695  		}
   696  
   697  		lastVersion = u.Version
   698  	}
   699  
   700  	if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 {
   701  		return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion)
   702  	}
   703  
   704  	// Data source
   705  	if r.isTopLevel() && !writable {
   706  		tsm = schemaMap(r.Schema)
   707  		for k := range tsm {
   708  			if isReservedDataSourceFieldName(k) {
   709  				return fmt.Errorf("%s is a reserved field name", k)
   710  			}
   711  		}
   712  	}
   713  
   714  	return schemaMap(r.Schema).InternalValidate(tsm)
   715  }
   716  
   717  func isReservedDataSourceFieldName(name string) bool {
   718  	for _, reservedName := range ReservedDataSourceFields {
   719  		if name == reservedName {
   720  			return true
   721  		}
   722  	}
   723  	return false
   724  }
   725  
   726  func isReservedResourceFieldName(name string, s *Schema) bool {
   727  	// Allow phasing out "id"
   728  	// See https://github.com/terraform-providers/terraform-provider-aws/pull/1626#issuecomment-328881415
   729  	if name == "id" && (s.Deprecated != "" || s.Removed != "") {
   730  		return false
   731  	}
   732  
   733  	for _, reservedName := range ReservedResourceFields {
   734  		if name == reservedName {
   735  			return true
   736  		}
   737  	}
   738  	return false
   739  }
   740  
   741  // Data returns a ResourceData struct for this Resource. Each return value
   742  // is a separate copy and can be safely modified differently.
   743  //
   744  // The data returned from this function has no actual affect on the Resource
   745  // itself (including the state given to this function).
   746  //
   747  // This function is useful for unit tests and ResourceImporter functions.
   748  func (r *Resource) Data(s *terraform.InstanceState) *ResourceData {
   749  	result, err := schemaMap(r.Schema).Data(s, nil)
   750  	if err != nil {
   751  		// At the time of writing, this isn't possible (Data never returns
   752  		// non-nil errors). We panic to find this in the future if we have to.
   753  		// I don't see a reason for Data to ever return an error.
   754  		panic(err)
   755  	}
   756  
   757  	// load the Resource timeouts
   758  	result.timeouts = r.Timeouts
   759  	if result.timeouts == nil {
   760  		result.timeouts = &ResourceTimeout{}
   761  	}
   762  
   763  	// Set the schema version to latest by default
   764  	result.meta = map[string]interface{}{
   765  		"schema_version": strconv.Itoa(r.SchemaVersion),
   766  	}
   767  
   768  	return result
   769  }
   770  
   771  // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing
   772  //
   773  // TODO: May be able to be removed with the above ResourceData function.
   774  func (r *Resource) TestResourceData() *ResourceData {
   775  	return &ResourceData{
   776  		schema: r.Schema,
   777  	}
   778  }
   779  
   780  // SchemasForFlatmapPath tries its best to find a sequence of schemas that
   781  // the given dot-delimited attribute path traverses through in the schema
   782  // of the receiving Resource.
   783  //
   784  // Deprecated: This function will be removed in version 2 without replacement.
   785  func (r *Resource) SchemasForFlatmapPath(path string) []*Schema {
   786  	return SchemasForFlatmapPath(path, r.Schema)
   787  }
   788  
   789  // Returns true if the resource is "top level" i.e. not a sub-resource.
   790  func (r *Resource) isTopLevel() bool {
   791  	// TODO: This is a heuristic; replace with a definitive attribute?
   792  	return (r.Create != nil || r.Read != nil)
   793  }
   794  
   795  // Determines if a given InstanceState needs to be migrated by checking the
   796  // stored version number with the current SchemaVersion
   797  func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
   798  	// Get the raw interface{} value for the schema version. If it doesn't
   799  	// exist or is nil then set it to zero.
   800  	raw := is.Meta["schema_version"]
   801  	if raw == nil {
   802  		raw = "0"
   803  	}
   804  
   805  	// Try to convert it to a string. If it isn't a string then we pretend
   806  	// that it isn't set at all. It should never not be a string unless it
   807  	// was manually tampered with.
   808  	rawString, ok := raw.(string)
   809  	if !ok {
   810  		rawString = "0"
   811  	}
   812  
   813  	stateSchemaVersion, _ := strconv.Atoi(rawString)
   814  
   815  	// Don't run MigrateState if the version is handled by a StateUpgrader,
   816  	// since StateMigrateFuncs are not required to handle unknown versions
   817  	maxVersion := r.SchemaVersion
   818  	if len(r.StateUpgraders) > 0 {
   819  		maxVersion = r.StateUpgraders[0].Version
   820  	}
   821  
   822  	return stateSchemaVersion < maxVersion, stateSchemaVersion
   823  }
   824  
   825  func (r *Resource) recordCurrentSchemaVersion(
   826  	state *terraform.InstanceState) *terraform.InstanceState {
   827  	if state != nil && r.SchemaVersion > 0 {
   828  		if state.Meta == nil {
   829  			state.Meta = make(map[string]interface{})
   830  		}
   831  		state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
   832  	}
   833  	return state
   834  }
   835  
   836  // Noop is a convenience implementation of resource function which takes
   837  // no action and returns no error.
   838  func Noop(*ResourceData, interface{}) error {
   839  	return nil
   840  }
   841  
   842  // RemoveFromState is a convenience implementation of a resource function
   843  // which sets the resource ID to empty string (to remove it from state)
   844  // and returns no error.
   845  func RemoveFromState(d *ResourceData, _ interface{}) error {
   846  	d.SetId("")
   847  	return nil
   848  }