github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/resource.go (about)

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