terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/resource.go
dependabot[bot] 282cd097f9
Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.20.0 to 2.24.1
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.20.0 to 2.24.1.
- [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.20.0...v2.24.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-24 18:04:13 +01:00

1321 lines
50 KiB
Go

package schema
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
var ReservedDataSourceFields = []string{
"connection",
"count",
"depends_on",
"lifecycle",
"provider",
"provisioner",
}
var ReservedResourceFields = []string{
"connection",
"count",
"depends_on",
"lifecycle",
"provider",
"provisioner",
}
// Resource is an abstraction for multiple Terraform concepts:
//
// - Managed Resource: An infrastructure component with a schema, lifecycle
// operations such as create, read, update, and delete
// (CRUD), and optional implementation details such as
// import support, upgrade state support, and difference
// customization.
// - Data Resource: Also known as a data source. An infrastructure component
// with a schema and only the read lifecycle operation.
// - Block: When implemented within a Schema type Elem field, a configuration
// block that contains nested schema information such as attributes
// and blocks.
//
// To fully implement managed resources, the Provider type ResourcesMap field
// should include a reference to an implementation of this type. To fully
// implement data resources, the Provider type DataSourcesMap field should
// include a reference to an implementation of this type.
//
// Each field further documents any constraints based on the Terraform concept
// being implemented.
type Resource struct {
// Schema is the structure and type information for this component. This
// field is required for all Resource concepts.
//
// The keys of this map are the names used in a practitioner configuration,
// such as the attribute or block name. The values describe the structure
// and type information of that attribute or block.
Schema map[string]*Schema
// SchemaVersion is the version number for this resource's Schema
// definition. This field is only valid when the Resource is a managed
// resource.
//
// The current SchemaVersion stored in the state for each resource.
// Provider authors can increment this version number when Schema semantics
// change in an incompatible manner. If the state's SchemaVersion is less
// than the current SchemaVersion, the MigrateState and StateUpgraders
// functionality is executed to upgrade the state information.
//
// When unset, SchemaVersion defaults to 0, so provider authors can start
// their Versioning at any integer >= 1
SchemaVersion int
// MigrateState is responsible for updating an InstanceState with an old
// version to the format expected by the current version of the Schema.
// This field is only valid when the Resource is a managed resource.
//
// It is called during Refresh if the State's stored SchemaVersion is less
// than the current SchemaVersion of the Resource.
//
// The function is yielded the state's stored SchemaVersion and a pointer to
// the InstanceState that needs updating, as well as the configured
// provider's configured meta interface{}, in case the migration process
// needs to make any remote API calls.
//
// Deprecated: MigrateState is deprecated and any new changes to a resource's schema
// should be handled by StateUpgraders. Existing MigrateState implementations
// should remain for compatibility with existing state. MigrateState will
// still be called if the stored SchemaVersion is less than the
// first version of the StateUpgraders.
MigrateState StateMigrateFunc
// StateUpgraders contains the functions responsible for upgrading an
// existing state with an old schema version to a newer schema. It is
// called specifically by Terraform when the stored schema version is less
// than the current SchemaVersion of the Resource. This field is only valid
// when the Resource is a managed resource.
//
// StateUpgraders map specific schema versions to a StateUpgrader
// function. The registered versions are expected to be ordered,
// consecutive values. The initial value may be greater than 0 to account
// for legacy schemas that weren't recorded and can be handled by
// MigrateState.
StateUpgraders []StateUpgrader
// Create is called when the provider must create a new instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Create, CreateContext, or
// CreateWithoutTimeout should be implemented.
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the practitioner
// configuration and any CustomizeDiff field logic.
//
// The SetId method must be called with a non-empty value for the managed
// resource instance to be properly saved into the Terraform state and
// avoid a "inconsistent result after apply" error.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The error return parameter, if not nil, will be converted into an error
// diagnostic when passed back to Terraform.
//
// Deprecated: Use CreateContext or CreateWithoutTimeout instead. This
// implementation does not support request cancellation initiated by
// Terraform, such as a system or practitioner sending SIGINT (Ctrl-c).
// This implementation also does not support warning diagnostics.
Create CreateFunc
// Read is called when the provider must refresh the state of a managed
// resource instance or data resource instance. This field is only valid
// when the Resource is a managed resource or data resource. Only one of
// Read, ReadContext, or ReadWithoutTimeout should be implemented.
//
// The *ResourceData parameter contains the state data for this managed
// resource instance or data resource instance.
//
// Managed resources can signal to Terraform that the managed resource
// instance no longer exists and potentially should be recreated by calling
// the SetId method with an empty string ("") parameter and without
// returning an error.
//
// Data resources that are designed to return state for a singular
// infrastructure component should conventionally return an error if that
// infrastructure does not exist and omit any calls to the
// SetId method.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The error return parameter, if not nil, will be converted into an error
// diagnostic when passed back to Terraform.
//
// Deprecated: Use ReadContext or ReadWithoutTimeout instead. This
// implementation does not support request cancellation initiated by
// Terraform, such as a system or practitioner sending SIGINT (Ctrl-c).
// This implementation also does not support warning diagnostics.
Read ReadFunc
// Update is called when the provider must update an instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Update, UpdateContext, or
// UpdateWithoutTimeout should be implemented.
//
// This implementation is optional. If omitted, all Schema must enable
// the ForceNew field and any practitioner changes that would have
// caused and update will instead destroy and recreate the infrastructure
// compontent.
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the prior state,
// practitioner configuration, and any CustomizeDiff field logic. The
// available data for the GetChange* and HasChange* methods is the prior
// state and proposed state.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The error return parameter, if not nil, will be converted into an error
// diagnostic when passed back to Terraform.
//
// Deprecated: Use UpdateContext or UpdateWithoutTimeout instead. This
// implementation does not support request cancellation initiated by
// Terraform, such as a system or practitioner sending SIGINT (Ctrl-c).
// This implementation also does not support warning diagnostics.
Update UpdateFunc
// Delete is called when the provider must destroy the instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Delete, DeleteContext, or
// DeleteWithoutTimeout should be implemented.
//
// The *ResourceData parameter contains the state data for this managed
// resource instance.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The error return parameter, if not nil, will be converted into an error
// diagnostic when passed back to Terraform.
//
// Deprecated: Use DeleteContext or DeleteWithoutTimeout instead. This
// implementation does not support request cancellation initiated by
// Terraform, such as a system or practitioner sending SIGINT (Ctrl-c).
// This implementation also does not support warning diagnostics.
Delete DeleteFunc
// Exists is a function that is called to check if a resource still
// exists. This field is only valid when the Resource is a managed
// resource.
//
// If this returns false, then this will affect the diff
// accordingly. If this function isn't set, it will not be called. You
// can also signal existence in the Read method by calling d.SetId("")
// if the Resource is no longer present and should be removed from state.
// The *ResourceData passed to Exists should _not_ be modified.
//
// Deprecated: Remove in preference of ReadContext or ReadWithoutTimeout.
Exists ExistsFunc
// CreateContext is called when the provider must create a new instance of
// a managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Create, CreateContext, or
// CreateWithoutTimeout should be implemented.
//
// The Context parameter stores SDK information, such as loggers and
// timeout deadlines. It also is wired to receive any cancellation from
// Terraform such as a system or practitioner sending SIGINT (Ctrl-c).
//
// By default, CreateContext has a 20 minute timeout. Use the Timeouts
// field to control the default duration or implement CreateWithoutTimeout
// instead of CreateContext to remove the default timeout.
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the practitioner
// configuration and any CustomizeDiff field logic.
//
// The SetId method must be called with a non-empty value for the managed
// resource instance to be properly saved into the Terraform state and
// avoid a "inconsistent result after apply" error.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
CreateContext CreateContextFunc
// ReadContext is called when the provider must refresh the state of a managed
// resource instance or data resource instance. This field is only valid
// when the Resource is a managed resource or data resource. Only one of
// Read, ReadContext, or ReadWithoutTimeout should be implemented.
//
// The Context parameter stores SDK information, such as loggers and
// timeout deadlines. It also is wired to receive any cancellation from
// Terraform such as a system or practitioner sending SIGINT (Ctrl-c).
//
// By default, ReadContext has a 20 minute timeout. Use the Timeouts
// field to control the default duration or implement ReadWithoutTimeout
// instead of ReadContext to remove the default timeout.
//
// The *ResourceData parameter contains the state data for this managed
// resource instance or data resource instance.
//
// Managed resources can signal to Terraform that the managed resource
// instance no longer exists and potentially should be recreated by calling
// the SetId method with an empty string ("") parameter and without
// returning an error.
//
// Data resources that are designed to return state for a singular
// infrastructure component should conventionally return an error if that
// infrastructure does not exist and omit any calls to the
// SetId method.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
ReadContext ReadContextFunc
// UpdateContext is called when the provider must update an instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Update, UpdateContext, or
// UpdateWithoutTimeout should be implemented.
//
// This implementation is optional. If omitted, all Schema must enable
// the ForceNew field and any practitioner changes that would have
// caused and update will instead destroy and recreate the infrastructure
// compontent.
//
// The Context parameter stores SDK information, such as loggers and
// timeout deadlines. It also is wired to receive any cancellation from
// Terraform such as a system or practitioner sending SIGINT (Ctrl-c).
//
// By default, UpdateContext has a 20 minute timeout. Use the Timeouts
// field to control the default duration or implement UpdateWithoutTimeout
// instead of UpdateContext to remove the default timeout.
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the prior state,
// practitioner configuration, and any CustomizeDiff field logic. The
// available data for the GetChange* and HasChange* methods is the prior
// state and proposed state.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
UpdateContext UpdateContextFunc
// DeleteContext is called when the provider must destroy the instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Delete, DeleteContext, or
// DeleteWithoutTimeout should be implemented.
//
// The Context parameter stores SDK information, such as loggers and
// timeout deadlines. It also is wired to receive any cancellation from
// Terraform such as a system or practitioner sending SIGINT (Ctrl-c).
//
// By default, DeleteContext has a 20 minute timeout. Use the Timeouts
// field to control the default duration or implement DeleteWithoutTimeout
// instead of DeleteContext to remove the default timeout.
//
// The *ResourceData parameter contains the state data for this managed
// resource instance.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
DeleteContext DeleteContextFunc
// CreateWithoutTimeout is called when the provider must create a new
// instance of a managed resource. This field is only valid when the
// Resource is a managed resource. Only one of Create, CreateContext, or
// CreateWithoutTimeout should be implemented.
//
// Most resources should prefer CreateContext with properly implemented
// operation timeout values, however there are cases where operation
// synchronization across concurrent resources is necessary in the resource
// logic, such as a mutex, to prevent remote system errors. Since these
// operations would have an indeterminate timeout that scales with the
// number of resources, this allows resources to control timeout behavior.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the practitioner
// configuration and any CustomizeDiff field logic.
//
// The SetId method must be called with a non-empty value for the managed
// resource instance to be properly saved into the Terraform state and
// avoid a "inconsistent result after apply" error.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
CreateWithoutTimeout CreateContextFunc
// ReadWithoutTimeout is called when the provider must refresh the state of
// a managed resource instance or data resource instance. This field is
// only valid when the Resource is a managed resource or data resource.
// Only one of Read, ReadContext, or ReadWithoutTimeout should be
// implemented.
//
// Most resources should prefer ReadContext with properly implemented
// operation timeout values, however there are cases where operation
// synchronization across concurrent resources is necessary in the resource
// logic, such as a mutex, to prevent remote system errors. Since these
// operations would have an indeterminate timeout that scales with the
// number of resources, this allows resources to control timeout behavior.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The *ResourceData parameter contains the state data for this managed
// resource instance or data resource instance.
//
// Managed resources can signal to Terraform that the managed resource
// instance no longer exists and potentially should be recreated by calling
// the SetId method with an empty string ("") parameter and without
// returning an error.
//
// Data resources that are designed to return state for a singular
// infrastructure component should conventionally return an error if that
// infrastructure does not exist and omit any calls to the
// SetId method.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
ReadWithoutTimeout ReadContextFunc
// UpdateWithoutTimeout is called when the provider must update an instance
// of a managed resource. This field is only valid when the Resource is a
// managed resource. Only one of Update, UpdateContext, or
// UpdateWithoutTimeout should be implemented.
//
// Most resources should prefer UpdateContext with properly implemented
// operation timeout values, however there are cases where operation
// synchronization across concurrent resources is necessary in the resource
// logic, such as a mutex, to prevent remote system errors. Since these
// operations would have an indeterminate timeout that scales with the
// number of resources, this allows resources to control timeout behavior.
//
// This implementation is optional. If omitted, all Schema must enable
// the ForceNew field and any practitioner changes that would have
// caused and update will instead destroy and recreate the infrastructure
// compontent.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The *ResourceData parameter contains the plan and state data for this
// managed resource instance. The available data in the Get* methods is the
// the proposed state, which is the merged data of the prior state,
// practitioner configuration, and any CustomizeDiff field logic. The
// available data for the GetChange* and HasChange* methods is the prior
// state and proposed state.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
UpdateWithoutTimeout UpdateContextFunc
// DeleteWithoutTimeout is called when the provider must destroy the
// instance of a managed resource. This field is only valid when the
// Resource is a managed resource. Only one of Delete, DeleteContext, or
// DeleteWithoutTimeout should be implemented.
//
// Most resources should prefer DeleteContext with properly implemented
// operation timeout values, however there are cases where operation
// synchronization across concurrent resources is necessary in the resource
// logic, such as a mutex, to prevent remote system errors. Since these
// operations would have an indeterminate timeout that scales with the
// number of resources, this allows resources to control timeout behavior.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The *ResourceData parameter contains the state data for this managed
// resource instance.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The diagnostics return parameter, if not nil, can contain any
// combination and multiple of warning and/or error diagnostics.
DeleteWithoutTimeout DeleteContextFunc
// CustomizeDiff is called after a difference (plan) has been generated
// for the Resource and allows for customizations, such as setting values
// not controlled by configuration, conditionally triggering resource
// recreation, or implementing additional validation logic to abort a plan.
// This field is only valid when the Resource is a managed resource.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The *ResourceDiff parameter is similar to ResourceData but replaces the
// Set method with other difference handling methods, such as SetNew,
// SetNewComputed, and ForceNew. In general, only Schema with Computed
// enabled can have those methods executed against them.
//
// The phases Terraform runs this in, and the state available via functions
// like Get and GetChange, are as follows:
//
// * New resource: One run with no state
// * Existing resource: One run with state
// * Existing resource, forced new: One run with state (before ForceNew),
// then one run without state (as if new resource)
// * Tainted resource: No runs (custom diff logic is skipped)
// * Destroy: No runs (standard diff logic is skipped on destroy diffs)
//
// This function needs to be resilient to support all scenarios.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The error return parameter, if not nil, will be converted into an error
// diagnostic when passed back to Terraform.
CustomizeDiff CustomizeDiffFunc
// Importer is called when the provider must import an instance of a
// managed resource. This field is only valid when the Resource is a
// managed resource.
//
// If this is nil, then this resource does not support importing. If
// this is non-nil, then it supports importing and ResourceImporter
// must be validated. The validity of ResourceImporter is verified
// by InternalValidate on Resource.
Importer *ResourceImporter
// If non-empty, this string is emitted as the details of a warning
// diagnostic during validation (validate, plan, and apply operations).
// This field is only valid when the Resource is a managed resource or
// data resource.
DeprecationMessage string
// Timeouts configures the default time duration allowed before a create,
// read, update, or delete operation is considered timed out, which returns
// an error to practitioners. This field is only valid when the Resource is
// a managed resource or data resource.
//
// When implemented, practitioners can add a timeouts configuration block
// within their managed resource or data resource configuration to further
// customize the create, read, update, or delete operation timeouts. For
// example, a configuration may specify a longer create timeout for a
// database resource due to its data size.
//
// The ResourceData that is passed to create, read, update, and delete
// functionality can access the merged time duration of the Resource
// default timeouts configured in this field and the practitioner timeouts
// configuration via the Timeout method. Practitioner configuration
// always overrides any default values set here, whether shorter or longer.
Timeouts *ResourceTimeout
// Description is used as the description for docs, the language server and
// other user facing usage. It can be plain-text or markdown depending on the
// global DescriptionKind setting. This field is valid for any Resource.
Description string
// UseJSONNumber should be set when state upgraders will expect
// json.Numbers instead of float64s for numbers. This is added as a
// toggle for backwards compatibility for type assertions, but should
// be used in all new resources to avoid bugs with sufficiently large
// user input. This field is only valid when the Resource is a managed
// resource.
//
// See github.com/hashicorp/terraform-plugin-sdk/issues/655 for more
// details.
UseJSONNumber bool
}
// ShimInstanceStateFromValue converts a cty.Value to a
// terraform.InstanceState.
func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.InstanceState, error) {
// Get the raw shimmed value. While this is correct, the set hashes don't
// match those from the Schema.
s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion)
// We now rebuild the state through the ResourceData, so that the set indexes
// match what helper/schema expects.
data, err := schemaMap(r.Schema).Data(s, nil)
if err != nil {
return nil, err
}
s = data.State()
if s == nil {
s = &terraform.InstanceState{}
}
return s, nil
}
// The following function types are of the legacy CRUD operations.
//
// Deprecated: Please use the context aware equivalents instead.
type CreateFunc func(*ResourceData, interface{}) error
// Deprecated: Please use the context aware equivalents instead.
type ReadFunc func(*ResourceData, interface{}) error
// Deprecated: Please use the context aware equivalents instead.
type UpdateFunc func(*ResourceData, interface{}) error
// Deprecated: Please use the context aware equivalents instead.
type DeleteFunc func(*ResourceData, interface{}) error
// Deprecated: Please use the context aware equivalents instead.
type ExistsFunc func(*ResourceData, interface{}) (bool, error)
// See Resource documentation.
type CreateContextFunc func(context.Context, *ResourceData, interface{}) diag.Diagnostics
// See Resource documentation.
type ReadContextFunc func(context.Context, *ResourceData, interface{}) diag.Diagnostics
// See Resource documentation.
type UpdateContextFunc func(context.Context, *ResourceData, interface{}) diag.Diagnostics
// See Resource documentation.
type DeleteContextFunc func(context.Context, *ResourceData, interface{}) diag.Diagnostics
// See Resource documentation.
type StateMigrateFunc func(
int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
// Implementation of a single schema version state upgrade.
type StateUpgrader struct {
// Version is the version schema that this Upgrader will handle, converting
// it to Version+1.
Version int
// Type describes the schema that this function can upgrade. Type is
// required to decode the schema if the state was stored in a legacy
// flatmap format.
Type cty.Type
// Upgrade takes the JSON encoded state and the provider meta value, and
// upgrades the state one single schema version. The provided state is
// deocded into the default json types using a map[string]interface{}. It
// is up to the StateUpgradeFunc to ensure that the returned value can be
// encoded using the new schema.
Upgrade StateUpgradeFunc
}
// Function signature for a schema version state upgrade handler.
//
// The Context parameter stores SDK information, such as loggers. It also
// is wired to receive any cancellation from Terraform such as a system or
// practitioner sending SIGINT (Ctrl-c).
//
// The map[string]interface{} parameter contains the previous schema version
// state data for a managed resource instance. The keys are top level attribute
// or block names mapped to values that can be type asserted similar to
// fetching values using the ResourceData Get* methods:
//
// - TypeBool: bool
// - TypeFloat: float
// - TypeInt: int
// - TypeList: []interface{}
// - TypeMap: map[string]interface{}
// - TypeSet: *Set
// - TypeString: string
//
// In certain scenarios, the map may be nil, so checking for that condition
// upfront is recommended to prevent potential panics.
//
// The interface{} parameter is the result of the Provider type
// ConfigureFunc field execution. If the Provider does not define
// a ConfigureFunc, this will be nil. This parameter is conventionally
// used to store API clients and other provider instance specific data.
//
// The map[string]interface{} return parameter should contain the upgraded
// schema version state data for a managed resource instance. Values must
// align to the typing mentioned above.
type StateUpgradeFunc func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error)
// See Resource documentation.
type CustomizeDiffFunc func(context.Context, *ResourceDiff, interface{}) error
func (r *Resource) create(ctx context.Context, d *ResourceData, meta interface{}) diag.Diagnostics {
if r.Create != nil {
if err := r.Create(d, meta); err != nil {
return diag.FromErr(err)
}
return nil
}
if r.CreateWithoutTimeout != nil {
return r.CreateWithoutTimeout(ctx, d, meta)
}
ctx, cancel := context.WithTimeout(ctx, d.Timeout(TimeoutCreate))
defer cancel()
return r.CreateContext(ctx, d, meta)
}
func (r *Resource) read(ctx context.Context, d *ResourceData, meta interface{}) diag.Diagnostics {
if r.Read != nil {
if err := r.Read(d, meta); err != nil {
return diag.FromErr(err)
}
return nil
}
if r.ReadWithoutTimeout != nil {
return r.ReadWithoutTimeout(ctx, d, meta)
}
ctx, cancel := context.WithTimeout(ctx, d.Timeout(TimeoutRead))
defer cancel()
return r.ReadContext(ctx, d, meta)
}
func (r *Resource) update(ctx context.Context, d *ResourceData, meta interface{}) diag.Diagnostics {
if r.Update != nil {
if err := r.Update(d, meta); err != nil {
return diag.FromErr(err)
}
return nil
}
if r.UpdateWithoutTimeout != nil {
return r.UpdateWithoutTimeout(ctx, d, meta)
}
ctx, cancel := context.WithTimeout(ctx, d.Timeout(TimeoutUpdate))
defer cancel()
return r.UpdateContext(ctx, d, meta)
}
func (r *Resource) delete(ctx context.Context, d *ResourceData, meta interface{}) diag.Diagnostics {
if r.Delete != nil {
if err := r.Delete(d, meta); err != nil {
return diag.FromErr(err)
}
return nil
}
if r.DeleteWithoutTimeout != nil {
return r.DeleteWithoutTimeout(ctx, d, meta)
}
ctx, cancel := context.WithTimeout(ctx, d.Timeout(TimeoutDelete))
defer cancel()
return r.DeleteContext(ctx, d, meta)
}
// Apply creates, updates, and/or deletes a resource.
func (r *Resource) Apply(
ctx context.Context,
s *terraform.InstanceState,
d *terraform.InstanceDiff,
meta interface{}) (*terraform.InstanceState, diag.Diagnostics) {
data, err := schemaMap(r.Schema).Data(s, d)
if err != nil {
return s, diag.FromErr(err)
}
if s != nil && data != nil {
data.providerMeta = s.ProviderMeta
}
// Instance Diff shoould have the timeout info, need to copy it over to the
// ResourceData meta
rt := ResourceTimeout{}
if _, ok := d.Meta[TimeoutKey]; ok {
if err := rt.DiffDecode(d); err != nil {
logging.HelperSchemaError(ctx, "Error decoding ResourceTimeout", map[string]interface{}{logging.KeyError: err})
}
} else if s != nil {
if _, ok := s.Meta[TimeoutKey]; ok {
if err := rt.StateDecode(s); err != nil {
logging.HelperSchemaError(ctx, "Error decoding ResourceTimeout", map[string]interface{}{logging.KeyError: err})
}
}
} else {
logging.HelperSchemaDebug(ctx, "No meta timeoutkey found in Apply()")
}
data.timeouts = &rt
if s == nil {
// The Terraform API dictates that this should never happen, but
// it doesn't hurt to be safe in this case.
s = new(terraform.InstanceState)
}
var diags diag.Diagnostics
if d.Destroy || d.RequiresNew() {
if s.ID != "" {
// Destroy the resource since it is created
logging.HelperSchemaTrace(ctx, "Calling downstream")
diags = append(diags, r.delete(ctx, data, meta)...)
logging.HelperSchemaTrace(ctx, "Called downstream")
if diags.HasError() {
return r.recordCurrentSchemaVersion(data.State()), diags
}
// Make sure the ID is gone.
data.SetId("")
}
// If we're only destroying, and not creating, then return
// now since we're done!
if !d.RequiresNew() {
return nil, diags
}
// Reset the data to be stateless since we just destroyed
data, err = schemaMap(r.Schema).Data(nil, d)
if err != nil {
return nil, append(diags, diag.FromErr(err)...)
}
// data was reset, need to re-apply the parsed timeouts
data.timeouts = &rt
}
if data.Id() == "" {
// We're creating, it is a new resource.
data.MarkNewResource()
logging.HelperSchemaTrace(ctx, "Calling downstream")
diags = append(diags, r.create(ctx, data, meta)...)
logging.HelperSchemaTrace(ctx, "Called downstream")
} else {
if !r.updateFuncSet() {
return s, append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "doesn't support update",
})
}
logging.HelperSchemaTrace(ctx, "Calling downstream")
diags = append(diags, r.update(ctx, data, meta)...)
logging.HelperSchemaTrace(ctx, "Called downstream")
}
return r.recordCurrentSchemaVersion(data.State()), diags
}
// Diff returns a diff of this resource.
func (r *Resource) Diff(
ctx context.Context,
s *terraform.InstanceState,
c *terraform.ResourceConfig,
meta interface{}) (*terraform.InstanceDiff, error) {
t := &ResourceTimeout{}
err := t.ConfigDecode(r, c)
if err != nil {
return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
}
instanceDiff, err := schemaMap(r.Schema).Diff(ctx, s, c, r.CustomizeDiff, meta, true)
if err != nil {
return instanceDiff, err
}
if instanceDiff != nil {
if err := t.DiffEncode(instanceDiff); err != nil {
logging.HelperSchemaError(ctx, "Error encoding timeout to instance diff", map[string]interface{}{logging.KeyError: err})
}
} else {
logging.HelperSchemaDebug(ctx, "Instance Diff is nil in Diff()")
}
return instanceDiff, err
}
func (r *Resource) SimpleDiff(
ctx context.Context,
s *terraform.InstanceState,
c *terraform.ResourceConfig,
meta interface{}) (*terraform.InstanceDiff, error) {
instanceDiff, err := schemaMap(r.Schema).Diff(ctx, s, c, r.CustomizeDiff, meta, false)
if err != nil {
return instanceDiff, err
}
if instanceDiff == nil {
instanceDiff = terraform.NewInstanceDiff()
}
// Make sure the old value is set in each of the instance diffs.
// This was done by the RequiresNew logic in the full legacy Diff.
for k, attr := range instanceDiff.Attributes {
if attr == nil {
continue
}
if s != nil {
attr.Old = s.Attributes[k]
}
}
return instanceDiff, nil
}
// Validate validates the resource configuration against the schema.
func (r *Resource) Validate(c *terraform.ResourceConfig) diag.Diagnostics {
diags := schemaMap(r.Schema).Validate(c)
if r.DeprecationMessage != "" {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Deprecated Resource",
Detail: r.DeprecationMessage,
})
}
return diags
}
// ReadDataApply loads the data for a data source, given a diff that
// describes the configuration arguments and desired computed attributes.
func (r *Resource) ReadDataApply(
ctx context.Context,
d *terraform.InstanceDiff,
meta interface{},
) (*terraform.InstanceState, diag.Diagnostics) {
// Data sources are always built completely from scratch
// on each read, so the source state is always nil.
data, err := schemaMap(r.Schema).Data(nil, d)
if err != nil {
return nil, diag.FromErr(err)
}
logging.HelperSchemaTrace(ctx, "Calling downstream")
diags := r.read(ctx, data, meta)
logging.HelperSchemaTrace(ctx, "Called downstream")
state := data.State()
if state != nil && state.ID == "" {
// Data sources can set an ID if they want, but they aren't
// required to; we'll provide a placeholder if they don't,
// to preserve the invariant that all resources have non-empty
// ids.
state.ID = "-"
}
return r.recordCurrentSchemaVersion(state), diags
}
// RefreshWithoutUpgrade reads the instance state, but does not call
// MigrateState or the StateUpgraders, since those are now invoked in a
// separate API call.
// RefreshWithoutUpgrade is part of the new plugin shims.
func (r *Resource) RefreshWithoutUpgrade(
ctx context.Context,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, diag.Diagnostics) {
// If the ID is already somehow blank, it doesn't exist
if s.ID == "" {
return nil, nil
}
rt := ResourceTimeout{}
if _, ok := s.Meta[TimeoutKey]; ok {
if err := rt.StateDecode(s); err != nil {
logging.HelperSchemaError(ctx, "Error decoding ResourceTimeout", map[string]interface{}{logging.KeyError: err})
}
}
if r.Exists != nil {
// Make a copy of data so that if it is modified it doesn't
// affect our Read later.
data, err := schemaMap(r.Schema).Data(s, nil)
if err != nil {
return s, diag.FromErr(err)
}
data.timeouts = &rt
if s != nil {
data.providerMeta = s.ProviderMeta
}
logging.HelperSchemaTrace(ctx, "Calling downstream")
exists, err := r.Exists(data, meta)
logging.HelperSchemaTrace(ctx, "Called downstream")
if err != nil {
return s, diag.FromErr(err)
}
if !exists {
return nil, nil
}
}
data, err := schemaMap(r.Schema).Data(s, nil)
if err != nil {
return s, diag.FromErr(err)
}
data.timeouts = &rt
if s != nil {
data.providerMeta = s.ProviderMeta
}
logging.HelperSchemaTrace(ctx, "Calling downstream")
diags := r.read(ctx, data, meta)
logging.HelperSchemaTrace(ctx, "Called downstream")
state := data.State()
if state != nil && state.ID == "" {
state = nil
}
schemaMap(r.Schema).handleDiffSuppressOnRefresh(ctx, s, state)
return r.recordCurrentSchemaVersion(state), diags
}
func (r *Resource) createFuncSet() bool {
return (r.Create != nil || r.CreateContext != nil || r.CreateWithoutTimeout != nil)
}
func (r *Resource) readFuncSet() bool {
return (r.Read != nil || r.ReadContext != nil || r.ReadWithoutTimeout != nil)
}
func (r *Resource) updateFuncSet() bool {
return (r.Update != nil || r.UpdateContext != nil || r.UpdateWithoutTimeout != nil)
}
func (r *Resource) deleteFuncSet() bool {
return (r.Delete != nil || r.DeleteContext != nil || r.DeleteWithoutTimeout != nil)
}
// InternalValidate should be called to validate the structure
// of the resource.
//
// This should be called in a unit test for any resource to verify
// before release that a resource is properly configured for use with
// this library.
//
// Provider.InternalValidate() will automatically call this for all of
// the resources it manages, so you don't need to call this manually if it
// is part of a Provider.
func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error {
if r == nil {
return errors.New("resource is nil")
}
if !writable {
if r.createFuncSet() || r.updateFuncSet() || r.deleteFuncSet() {
return fmt.Errorf("must not implement Create, Update or Delete")
}
// CustomizeDiff cannot be defined for read-only resources
if r.CustomizeDiff != nil {
return fmt.Errorf("cannot implement CustomizeDiff")
}
}
tsm := topSchemaMap
if r.isTopLevel() && writable {
// All non-Computed attributes must be ForceNew if Update is not defined
if !r.updateFuncSet() {
nonForceNewAttrs := make([]string, 0)
for k, v := range r.Schema {
if !v.ForceNew && !v.Computed {
nonForceNewAttrs = append(nonForceNewAttrs, k)
}
}
if len(nonForceNewAttrs) > 0 {
return fmt.Errorf(
"No Update defined, must set ForceNew on: %#v", nonForceNewAttrs)
}
} else {
nonUpdateableAttrs := make([]string, 0)
for k, v := range r.Schema {
if v.ForceNew || v.Computed && !v.Optional {
nonUpdateableAttrs = append(nonUpdateableAttrs, k)
}
}
updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs)
if updateableAttrs == 0 {
return fmt.Errorf(
"All fields are ForceNew or Computed w/out Optional, Update is superfluous")
}
}
tsm = schemaMap(r.Schema)
// Destroy, and Read are required
if !r.readFuncSet() {
return fmt.Errorf("Read must be implemented")
}
if !r.deleteFuncSet() {
return fmt.Errorf("Delete must be implemented")
}
// If we have an importer, we need to verify the importer.
if r.Importer != nil {
if err := r.Importer.InternalValidate(); err != nil {
return err
}
}
if f, ok := tsm["id"]; ok {
// if there is an explicit ID, validate it...
err := validateResourceID(f)
if err != nil {
return err
}
}
for k := range tsm {
if isReservedResourceFieldName(k) {
return fmt.Errorf("%s is a reserved field name", k)
}
}
}
lastVersion := -1
for _, u := range r.StateUpgraders {
if lastVersion >= 0 && u.Version-lastVersion > 1 {
return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version)
}
if u.Version >= r.SchemaVersion {
return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion)
}
if !u.Type.IsObjectType() {
return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version)
}
if u.Upgrade == nil {
return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version)
}
lastVersion = u.Version
}
if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 {
return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion)
}
// Data source
if r.isTopLevel() && !writable {
tsm = schemaMap(r.Schema)
for k := range tsm {
if isReservedDataSourceFieldName(k) {
return fmt.Errorf("%s is a reserved field name", k)
}
}
}
// check context funcs are not set alongside their nonctx counterparts
if r.CreateContext != nil && r.Create != nil {
return fmt.Errorf("CreateContext and Create should not both be set")
}
if r.ReadContext != nil && r.Read != nil {
return fmt.Errorf("ReadContext and Read should not both be set")
}
if r.UpdateContext != nil && r.Update != nil {
return fmt.Errorf("UpdateContext and Update should not both be set")
}
if r.DeleteContext != nil && r.Delete != nil {
return fmt.Errorf("DeleteContext and Delete should not both be set")
}
// check context funcs are not set alongside their without timeout counterparts
if r.CreateContext != nil && r.CreateWithoutTimeout != nil {
return fmt.Errorf("CreateContext and CreateWithoutTimeout should not both be set")
}
if r.ReadContext != nil && r.ReadWithoutTimeout != nil {
return fmt.Errorf("ReadContext and ReadWithoutTimeout should not both be set")
}
if r.UpdateContext != nil && r.UpdateWithoutTimeout != nil {
return fmt.Errorf("UpdateContext and UpdateWithoutTimeout should not both be set")
}
if r.DeleteContext != nil && r.DeleteWithoutTimeout != nil {
return fmt.Errorf("DeleteContext and DeleteWithoutTimeout should not both be set")
}
// check non-context funcs are not set alongside the context without timeout counterparts
if r.Create != nil && r.CreateWithoutTimeout != nil {
return fmt.Errorf("Create and CreateWithoutTimeout should not both be set")
}
if r.Read != nil && r.ReadWithoutTimeout != nil {
return fmt.Errorf("Read and ReadWithoutTimeout should not both be set")
}
if r.Update != nil && r.UpdateWithoutTimeout != nil {
return fmt.Errorf("Update and UpdateWithoutTimeout should not both be set")
}
if r.Delete != nil && r.DeleteWithoutTimeout != nil {
return fmt.Errorf("Delete and DeleteWithoutTimeout should not both be set")
}
return schemaMap(r.Schema).InternalValidate(tsm)
}
func isReservedDataSourceFieldName(name string) bool {
for _, reservedName := range ReservedDataSourceFields {
if name == reservedName {
return true
}
}
return false
}
func validateResourceID(s *Schema) error {
if s.Type != TypeString {
return fmt.Errorf(`the "id" attribute must be of TypeString`)
}
if s.Required {
return fmt.Errorf(`the "id" attribute cannot be marked Required`)
}
// ID should at least be computed. If unspecified it will be set to Computed and Optional,
// but Optional is unnecessary if undesired.
if !s.Computed {
return fmt.Errorf(`the "id" attribute must be marked Computed`)
}
return nil
}
func isReservedResourceFieldName(name string) bool {
for _, reservedName := range ReservedResourceFields {
if name == reservedName {
return true
}
}
return false
}
// Data returns a ResourceData struct for this Resource. Each return value
// is a separate copy and can be safely modified differently.
//
// The data returned from this function has no actual affect on the Resource
// itself (including the state given to this function).
//
// This function is useful for unit tests and ResourceImporter functions.
func (r *Resource) Data(s *terraform.InstanceState) *ResourceData {
result, err := schemaMap(r.Schema).Data(s, nil)
if err != nil {
// At the time of writing, this isn't possible (Data never returns
// non-nil errors). We panic to find this in the future if we have to.
// I don't see a reason for Data to ever return an error.
panic(err)
}
// load the Resource timeouts
result.timeouts = r.Timeouts
if result.timeouts == nil {
result.timeouts = &ResourceTimeout{}
}
// Set the schema version to latest by default
result.meta = map[string]interface{}{
"schema_version": strconv.Itoa(r.SchemaVersion),
}
return result
}
// TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing
//
// TODO: May be able to be removed with the above ResourceData function.
func (r *Resource) TestResourceData() *ResourceData {
return &ResourceData{
schema: r.Schema,
}
}
// Returns true if the resource is "top level" i.e. not a sub-resource.
func (r *Resource) isTopLevel() bool {
// TODO: This is a heuristic; replace with a definitive attribute?
return (r.createFuncSet() || r.readFuncSet())
}
func (r *Resource) recordCurrentSchemaVersion(
state *terraform.InstanceState) *terraform.InstanceState {
if state != nil && r.SchemaVersion > 0 {
if state.Meta == nil {
state.Meta = make(map[string]interface{})
}
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
}
return state
}
// Noop is a convenience implementation of resource function which takes
// no action and returns no error.
func Noop(*ResourceData, interface{}) error {
return nil
}
// NoopContext is a convenience implementation of context aware resource function which takes
// no action and returns no error.
func NoopContext(context.Context, *ResourceData, interface{}) diag.Diagnostics {
return nil
}
// RemoveFromState is a convenience implementation of a resource function
// which sets the resource ID to empty string (to remove it from state)
// and returns no error.
func RemoveFromState(d *ResourceData, _ interface{}) error {
d.SetId("")
return nil
}