2022-08-06 14:21:18 +00:00
|
|
|
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:
|
|
|
|
//
|
2022-12-24 16:57:19 +00:00
|
|
|
// - 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.
|
2022-08-06 14:21:18 +00:00
|
|
|
//
|
|
|
|
// 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:
|
|
|
|
//
|
2022-12-24 16:57:19 +00:00
|
|
|
// - TypeBool: bool
|
|
|
|
// - TypeFloat: float
|
|
|
|
// - TypeInt: int
|
|
|
|
// - TypeList: []interface{}
|
|
|
|
// - TypeMap: map[string]interface{}
|
|
|
|
// - TypeSet: *Set
|
|
|
|
// - TypeString: string
|
2022-08-06 14:21:18 +00:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
}
|