github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/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/internal/legacy/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  
   177  // ShimInstanceStateFromValue converts a cty.Value to a
   178  // terraform.InstanceState.
   179  func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.InstanceState, error) {
   180  	// Get the raw shimmed value. While this is correct, the set hashes don't
   181  	// match those from the Schema.
   182  	s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion)
   183  
   184  	// We now rebuild the state through the ResourceData, so that the set indexes
   185  	// match what helper/schema expects.
   186  	data, err := schemaMap(r.Schema).Data(s, nil)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	s = data.State()
   192  	if s == nil {
   193  		s = &terraform.InstanceState{}
   194  	}
   195  	return s, nil
   196  }
   197  
   198  // See Resource documentation.
   199  type CreateFunc func(*ResourceData, interface{}) error
   200  
   201  // See Resource documentation.
   202  type ReadFunc func(*ResourceData, interface{}) error
   203  
   204  // See Resource documentation.
   205  type UpdateFunc func(*ResourceData, interface{}) error
   206  
   207  // See Resource documentation.
   208  type DeleteFunc func(*ResourceData, interface{}) error
   209  
   210  // See Resource documentation.
   211  type ExistsFunc func(*ResourceData, interface{}) (bool, error)
   212  
   213  // See Resource documentation.
   214  type StateMigrateFunc func(
   215  	int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
   216  
   217  type StateUpgrader struct {
   218  	// Version is the version schema that this Upgrader will handle, converting
   219  	// it to Version+1.
   220  	Version int
   221  
   222  	// Type describes the schema that this function can upgrade. Type is
   223  	// required to decode the schema if the state was stored in a legacy
   224  	// flatmap format.
   225  	Type cty.Type
   226  
   227  	// Upgrade takes the JSON encoded state and the provider meta value, and
   228  	// upgrades the state one single schema version. The provided state is
   229  	// deocded into the default json types using a map[string]interface{}. It
   230  	// is up to the StateUpgradeFunc to ensure that the returned value can be
   231  	// encoded using the new schema.
   232  	Upgrade StateUpgradeFunc
   233  }
   234  
   235  // See StateUpgrader
   236  type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error)
   237  
   238  // See Resource documentation.
   239  type CustomizeDiffFunc func(*ResourceDiff, interface{}) error
   240  
   241  // Apply creates, updates, and/or deletes a resource.
   242  func (r *Resource) Apply(
   243  	s *terraform.InstanceState,
   244  	d *terraform.InstanceDiff,
   245  	meta interface{}) (*terraform.InstanceState, error) {
   246  	data, err := schemaMap(r.Schema).Data(s, d)
   247  	if err != nil {
   248  		return s, err
   249  	}
   250  	if s != nil && data != nil {
   251  		data.providerMeta = s.ProviderMeta
   252  	}
   253  
   254  	// Instance Diff shoould have the timeout info, need to copy it over to the
   255  	// ResourceData meta
   256  	rt := ResourceTimeout{}
   257  	if _, ok := d.Meta[TimeoutKey]; ok {
   258  		if err := rt.DiffDecode(d); err != nil {
   259  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   260  		}
   261  	} else if s != nil {
   262  		if _, ok := s.Meta[TimeoutKey]; ok {
   263  			if err := rt.StateDecode(s); err != nil {
   264  				log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   265  			}
   266  		}
   267  	} else {
   268  		log.Printf("[DEBUG] No meta timeoutkey found in Apply()")
   269  	}
   270  	data.timeouts = &rt
   271  
   272  	if s == nil {
   273  		// The Terraform API dictates that this should never happen, but
   274  		// it doesn't hurt to be safe in this case.
   275  		s = new(terraform.InstanceState)
   276  	}
   277  
   278  	if d.Destroy || d.RequiresNew() {
   279  		if s.ID != "" {
   280  			// Destroy the resource since it is created
   281  			if err := r.Delete(data, meta); err != nil {
   282  				return r.recordCurrentSchemaVersion(data.State()), err
   283  			}
   284  
   285  			// Make sure the ID is gone.
   286  			data.SetId("")
   287  		}
   288  
   289  		// If we're only destroying, and not creating, then return
   290  		// now since we're done!
   291  		if !d.RequiresNew() {
   292  			return nil, nil
   293  		}
   294  
   295  		// Reset the data to be stateless since we just destroyed
   296  		data, err = schemaMap(r.Schema).Data(nil, d)
   297  		// data was reset, need to re-apply the parsed timeouts
   298  		data.timeouts = &rt
   299  		if err != nil {
   300  			return nil, err
   301  		}
   302  	}
   303  
   304  	err = nil
   305  	if data.Id() == "" {
   306  		// We're creating, it is a new resource.
   307  		data.MarkNewResource()
   308  		err = r.Create(data, meta)
   309  	} else {
   310  		if r.Update == nil {
   311  			return s, fmt.Errorf("doesn't support update")
   312  		}
   313  
   314  		err = r.Update(data, meta)
   315  	}
   316  
   317  	return r.recordCurrentSchemaVersion(data.State()), err
   318  }
   319  
   320  // Diff returns a diff of this resource.
   321  func (r *Resource) Diff(
   322  	s *terraform.InstanceState,
   323  	c *terraform.ResourceConfig,
   324  	meta interface{}) (*terraform.InstanceDiff, error) {
   325  
   326  	t := &ResourceTimeout{}
   327  	err := t.ConfigDecode(r, c)
   328  
   329  	if err != nil {
   330  		return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
   331  	}
   332  
   333  	instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true)
   334  	if err != nil {
   335  		return instanceDiff, err
   336  	}
   337  
   338  	if instanceDiff != nil {
   339  		if err := t.DiffEncode(instanceDiff); err != nil {
   340  			log.Printf("[ERR] Error encoding timeout to instance diff: %s", err)
   341  		}
   342  	} else {
   343  		log.Printf("[DEBUG] Instance Diff is nil in Diff()")
   344  	}
   345  
   346  	return instanceDiff, err
   347  }
   348  
   349  func (r *Resource) simpleDiff(
   350  	s *terraform.InstanceState,
   351  	c *terraform.ResourceConfig,
   352  	meta interface{}) (*terraform.InstanceDiff, error) {
   353  
   354  	instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, false)
   355  	if err != nil {
   356  		return instanceDiff, err
   357  	}
   358  
   359  	if instanceDiff == nil {
   360  		instanceDiff = terraform.NewInstanceDiff()
   361  	}
   362  
   363  	// Make sure the old value is set in each of the instance diffs.
   364  	// This was done by the RequiresNew logic in the full legacy Diff.
   365  	for k, attr := range instanceDiff.Attributes {
   366  		if attr == nil {
   367  			continue
   368  		}
   369  		if s != nil {
   370  			attr.Old = s.Attributes[k]
   371  		}
   372  	}
   373  
   374  	return instanceDiff, nil
   375  }
   376  
   377  // Validate validates the resource configuration against the schema.
   378  func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
   379  	warns, errs := schemaMap(r.Schema).Validate(c)
   380  
   381  	if r.DeprecationMessage != "" {
   382  		warns = append(warns, r.DeprecationMessage)
   383  	}
   384  
   385  	return warns, errs
   386  }
   387  
   388  // ReadDataApply loads the data for a data source, given a diff that
   389  // describes the configuration arguments and desired computed attributes.
   390  func (r *Resource) ReadDataApply(
   391  	d *terraform.InstanceDiff,
   392  	meta interface{},
   393  ) (*terraform.InstanceState, error) {
   394  	// Data sources are always built completely from scratch
   395  	// on each read, so the source state is always nil.
   396  	data, err := schemaMap(r.Schema).Data(nil, d)
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  
   401  	err = r.Read(data, meta)
   402  	state := data.State()
   403  	if state != nil && state.ID == "" {
   404  		// Data sources can set an ID if they want, but they aren't
   405  		// required to; we'll provide a placeholder if they don't,
   406  		// to preserve the invariant that all resources have non-empty
   407  		// ids.
   408  		state.ID = "-"
   409  	}
   410  
   411  	return r.recordCurrentSchemaVersion(state), err
   412  }
   413  
   414  // RefreshWithoutUpgrade reads the instance state, but does not call
   415  // MigrateState or the StateUpgraders, since those are now invoked in a
   416  // separate API call.
   417  // RefreshWithoutUpgrade is part of the new plugin shims.
   418  func (r *Resource) RefreshWithoutUpgrade(
   419  	s *terraform.InstanceState,
   420  	meta interface{}) (*terraform.InstanceState, error) {
   421  	// If the ID is already somehow blank, it doesn't exist
   422  	if s.ID == "" {
   423  		return nil, nil
   424  	}
   425  
   426  	rt := ResourceTimeout{}
   427  	if _, ok := s.Meta[TimeoutKey]; ok {
   428  		if err := rt.StateDecode(s); err != nil {
   429  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   430  		}
   431  	}
   432  
   433  	if r.Exists != nil {
   434  		// Make a copy of data so that if it is modified it doesn't
   435  		// affect our Read later.
   436  		data, err := schemaMap(r.Schema).Data(s, nil)
   437  		data.timeouts = &rt
   438  
   439  		if err != nil {
   440  			return s, err
   441  		}
   442  
   443  		if s != nil {
   444  			data.providerMeta = s.ProviderMeta
   445  		}
   446  
   447  		exists, err := r.Exists(data, meta)
   448  		if err != nil {
   449  			return s, err
   450  		}
   451  		if !exists {
   452  			return nil, nil
   453  		}
   454  	}
   455  
   456  	data, err := schemaMap(r.Schema).Data(s, nil)
   457  	data.timeouts = &rt
   458  	if err != nil {
   459  		return s, err
   460  	}
   461  
   462  	if s != nil {
   463  		data.providerMeta = s.ProviderMeta
   464  	}
   465  
   466  	err = r.Read(data, meta)
   467  	state := data.State()
   468  	if state != nil && state.ID == "" {
   469  		state = nil
   470  	}
   471  
   472  	return r.recordCurrentSchemaVersion(state), err
   473  }
   474  
   475  // Refresh refreshes the state of the resource.
   476  func (r *Resource) Refresh(
   477  	s *terraform.InstanceState,
   478  	meta interface{}) (*terraform.InstanceState, error) {
   479  	// If the ID is already somehow blank, it doesn't exist
   480  	if s.ID == "" {
   481  		return nil, nil
   482  	}
   483  
   484  	rt := ResourceTimeout{}
   485  	if _, ok := s.Meta[TimeoutKey]; ok {
   486  		if err := rt.StateDecode(s); err != nil {
   487  			log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
   488  		}
   489  	}
   490  
   491  	if r.Exists != nil {
   492  		// Make a copy of data so that if it is modified it doesn't
   493  		// affect our Read later.
   494  		data, err := schemaMap(r.Schema).Data(s, nil)
   495  		data.timeouts = &rt
   496  
   497  		if err != nil {
   498  			return s, err
   499  		}
   500  
   501  		exists, err := r.Exists(data, meta)
   502  		if err != nil {
   503  			return s, err
   504  		}
   505  		if !exists {
   506  			return nil, nil
   507  		}
   508  	}
   509  
   510  	// there may be new StateUpgraders that need to be run
   511  	s, err := r.upgradeState(s, meta)
   512  	if err != nil {
   513  		return s, err
   514  	}
   515  
   516  	data, err := schemaMap(r.Schema).Data(s, nil)
   517  	data.timeouts = &rt
   518  	if err != nil {
   519  		return s, err
   520  	}
   521  
   522  	err = r.Read(data, meta)
   523  	state := data.State()
   524  	if state != nil && state.ID == "" {
   525  		state = nil
   526  	}
   527  
   528  	return r.recordCurrentSchemaVersion(state), err
   529  }
   530  
   531  func (r *Resource) upgradeState(s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
   532  	var err error
   533  
   534  	needsMigration, stateSchemaVersion := r.checkSchemaVersion(s)
   535  	migrate := needsMigration && r.MigrateState != nil
   536  
   537  	if migrate {
   538  		s, err = r.MigrateState(stateSchemaVersion, s, meta)
   539  		if err != nil {
   540  			return s, err
   541  		}
   542  	}
   543  
   544  	if len(r.StateUpgraders) == 0 {
   545  		return s, nil
   546  	}
   547  
   548  	// If we ran MigrateState, then the stateSchemaVersion value is no longer
   549  	// correct. We can expect the first upgrade function to be the correct
   550  	// schema type version.
   551  	if migrate {
   552  		stateSchemaVersion = r.StateUpgraders[0].Version
   553  	}
   554  
   555  	schemaType := r.CoreConfigSchema().ImpliedType()
   556  	// find the expected type to convert the state
   557  	for _, upgrader := range r.StateUpgraders {
   558  		if stateSchemaVersion == upgrader.Version {
   559  			schemaType = upgrader.Type
   560  		}
   561  	}
   562  
   563  	// StateUpgraders only operate on the new JSON format state, so the state
   564  	// need to be converted.
   565  	stateVal, err := StateValueFromInstanceState(s, schemaType)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  
   570  	jsonState, err := StateValueToJSONMap(stateVal, schemaType)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  
   575  	for _, upgrader := range r.StateUpgraders {
   576  		if stateSchemaVersion != upgrader.Version {
   577  			continue
   578  		}
   579  
   580  		jsonState, err = upgrader.Upgrade(jsonState, meta)
   581  		if err != nil {
   582  			return nil, err
   583  		}
   584  		stateSchemaVersion++
   585  	}
   586  
   587  	// now we need to re-flatmap the new state
   588  	stateVal, err = JSONMapToStateValue(jsonState, r.CoreConfigSchema())
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  
   593  	return r.ShimInstanceStateFromValue(stateVal)
   594  }
   595  
   596  // InternalValidate should be called to validate the structure
   597  // of the resource.
   598  //
   599  // This should be called in a unit test for any resource to verify
   600  // before release that a resource is properly configured for use with
   601  // this library.
   602  //
   603  // Provider.InternalValidate() will automatically call this for all of
   604  // the resources it manages, so you don't need to call this manually if it
   605  // is part of a Provider.
   606  func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error {
   607  	if r == nil {
   608  		return errors.New("resource is nil")
   609  	}
   610  
   611  	if !writable {
   612  		if r.Create != nil || r.Update != nil || r.Delete != nil {
   613  			return fmt.Errorf("must not implement Create, Update or Delete")
   614  		}
   615  
   616  		// CustomizeDiff cannot be defined for read-only resources
   617  		if r.CustomizeDiff != nil {
   618  			return fmt.Errorf("cannot implement CustomizeDiff")
   619  		}
   620  	}
   621  
   622  	tsm := topSchemaMap
   623  
   624  	if r.isTopLevel() && writable {
   625  		// All non-Computed attributes must be ForceNew if Update is not defined
   626  		if r.Update == nil {
   627  			nonForceNewAttrs := make([]string, 0)
   628  			for k, v := range r.Schema {
   629  				if !v.ForceNew && !v.Computed {
   630  					nonForceNewAttrs = append(nonForceNewAttrs, k)
   631  				}
   632  			}
   633  			if len(nonForceNewAttrs) > 0 {
   634  				return fmt.Errorf(
   635  					"No Update defined, must set ForceNew on: %#v", nonForceNewAttrs)
   636  			}
   637  		} else {
   638  			nonUpdateableAttrs := make([]string, 0)
   639  			for k, v := range r.Schema {
   640  				if v.ForceNew || v.Computed && !v.Optional {
   641  					nonUpdateableAttrs = append(nonUpdateableAttrs, k)
   642  				}
   643  			}
   644  			updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs)
   645  			if updateableAttrs == 0 {
   646  				return fmt.Errorf(
   647  					"All fields are ForceNew or Computed w/out Optional, Update is superfluous")
   648  			}
   649  		}
   650  
   651  		tsm = schemaMap(r.Schema)
   652  
   653  		// Destroy, and Read are required
   654  		if r.Read == nil {
   655  			return fmt.Errorf("Read must be implemented")
   656  		}
   657  		if r.Delete == nil {
   658  			return fmt.Errorf("Delete must be implemented")
   659  		}
   660  
   661  		// If we have an importer, we need to verify the importer.
   662  		if r.Importer != nil {
   663  			if err := r.Importer.InternalValidate(); err != nil {
   664  				return err
   665  			}
   666  		}
   667  
   668  		for k, f := range tsm {
   669  			if isReservedResourceFieldName(k, f) {
   670  				return fmt.Errorf("%s is a reserved field name", k)
   671  			}
   672  		}
   673  	}
   674  
   675  	lastVersion := -1
   676  	for _, u := range r.StateUpgraders {
   677  		if lastVersion >= 0 && u.Version-lastVersion > 1 {
   678  			return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version)
   679  		}
   680  
   681  		if u.Version >= r.SchemaVersion {
   682  			return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion)
   683  		}
   684  
   685  		if !u.Type.IsObjectType() {
   686  			return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version)
   687  		}
   688  
   689  		if u.Upgrade == nil {
   690  			return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version)
   691  		}
   692  
   693  		lastVersion = u.Version
   694  	}
   695  
   696  	if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 {
   697  		return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion)
   698  	}
   699  
   700  	// Data source
   701  	if r.isTopLevel() && !writable {
   702  		tsm = schemaMap(r.Schema)
   703  		for k, _ := range tsm {
   704  			if isReservedDataSourceFieldName(k) {
   705  				return fmt.Errorf("%s is a reserved field name", k)
   706  			}
   707  		}
   708  	}
   709  
   710  	return schemaMap(r.Schema).InternalValidate(tsm)
   711  }
   712  
   713  func isReservedDataSourceFieldName(name string) bool {
   714  	for _, reservedName := range ReservedDataSourceFields {
   715  		if name == reservedName {
   716  			return true
   717  		}
   718  	}
   719  	return false
   720  }
   721  
   722  func isReservedResourceFieldName(name string, s *Schema) bool {
   723  	// Allow phasing out "id"
   724  	// See https://github.com/terraform-providers/terraform-provider-aws/pull/1626#issuecomment-328881415
   725  	if name == "id" && (s.Deprecated != "" || s.Removed != "") {
   726  		return false
   727  	}
   728  
   729  	for _, reservedName := range ReservedResourceFields {
   730  		if name == reservedName {
   731  			return true
   732  		}
   733  	}
   734  	return false
   735  }
   736  
   737  // Data returns a ResourceData struct for this Resource. Each return value
   738  // is a separate copy and can be safely modified differently.
   739  //
   740  // The data returned from this function has no actual affect on the Resource
   741  // itself (including the state given to this function).
   742  //
   743  // This function is useful for unit tests and ResourceImporter functions.
   744  func (r *Resource) Data(s *terraform.InstanceState) *ResourceData {
   745  	result, err := schemaMap(r.Schema).Data(s, nil)
   746  	if err != nil {
   747  		// At the time of writing, this isn't possible (Data never returns
   748  		// non-nil errors). We panic to find this in the future if we have to.
   749  		// I don't see a reason for Data to ever return an error.
   750  		panic(err)
   751  	}
   752  
   753  	// load the Resource timeouts
   754  	result.timeouts = r.Timeouts
   755  	if result.timeouts == nil {
   756  		result.timeouts = &ResourceTimeout{}
   757  	}
   758  
   759  	// Set the schema version to latest by default
   760  	result.meta = map[string]interface{}{
   761  		"schema_version": strconv.Itoa(r.SchemaVersion),
   762  	}
   763  
   764  	return result
   765  }
   766  
   767  // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing
   768  //
   769  // TODO: May be able to be removed with the above ResourceData function.
   770  func (r *Resource) TestResourceData() *ResourceData {
   771  	return &ResourceData{
   772  		schema: r.Schema,
   773  	}
   774  }
   775  
   776  // SchemasForFlatmapPath tries its best to find a sequence of schemas that
   777  // the given dot-delimited attribute path traverses through in the schema
   778  // of the receiving Resource.
   779  func (r *Resource) SchemasForFlatmapPath(path string) []*Schema {
   780  	return SchemasForFlatmapPath(path, r.Schema)
   781  }
   782  
   783  // Returns true if the resource is "top level" i.e. not a sub-resource.
   784  func (r *Resource) isTopLevel() bool {
   785  	// TODO: This is a heuristic; replace with a definitive attribute?
   786  	return (r.Create != nil || r.Read != nil)
   787  }
   788  
   789  // Determines if a given InstanceState needs to be migrated by checking the
   790  // stored version number with the current SchemaVersion
   791  func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
   792  	// Get the raw interface{} value for the schema version. If it doesn't
   793  	// exist or is nil then set it to zero.
   794  	raw := is.Meta["schema_version"]
   795  	if raw == nil {
   796  		raw = "0"
   797  	}
   798  
   799  	// Try to convert it to a string. If it isn't a string then we pretend
   800  	// that it isn't set at all. It should never not be a string unless it
   801  	// was manually tampered with.
   802  	rawString, ok := raw.(string)
   803  	if !ok {
   804  		rawString = "0"
   805  	}
   806  
   807  	stateSchemaVersion, _ := strconv.Atoi(rawString)
   808  
   809  	// Don't run MigrateState if the version is handled by a StateUpgrader,
   810  	// since StateMigrateFuncs are not required to handle unknown versions
   811  	maxVersion := r.SchemaVersion
   812  	if len(r.StateUpgraders) > 0 {
   813  		maxVersion = r.StateUpgraders[0].Version
   814  	}
   815  
   816  	return stateSchemaVersion < maxVersion, stateSchemaVersion
   817  }
   818  
   819  func (r *Resource) recordCurrentSchemaVersion(
   820  	state *terraform.InstanceState) *terraform.InstanceState {
   821  	if state != nil && r.SchemaVersion > 0 {
   822  		if state.Meta == nil {
   823  			state.Meta = make(map[string]interface{})
   824  		}
   825  		state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
   826  	}
   827  	return state
   828  }
   829  
   830  // Noop is a convenience implementation of resource function which takes
   831  // no action and returns no error.
   832  func Noop(*ResourceData, interface{}) error {
   833  	return nil
   834  }
   835  
   836  // RemoveFromState is a convenience implementation of a resource function
   837  // which sets the resource ID to empty string (to remove it from state)
   838  // and returns no error.
   839  func RemoveFromState(d *ResourceData, _ interface{}) error {
   840  	d.SetId("")
   841  	return nil
   842  }