Terraform
Plan Modification
After validation and before applying configuration changes, Terraform generates a plan that describes the expected values and behaviors of those changes. Providers can then tailor the plan to match the expected end state. For example, they may replace unknown values with expected known values or mark a resource that must be replaced. Users can perform this plan modification for an attribute or an entire resource.
Plan Modification Process
Terraform and the framework support three types of plan modification on resources:
- Adjusting attribute values, such as providing a known remote default value when a configuration is not present.
- Marking resources that should be replaced, such as when an in-place update is not supported for a change.
- Returning warning or error diagnostics on planned resource destruction with Terraform 1.3 and later.
When the provider receives a request to generate the plan for a resource change via the framework, the following occurs:
- If the plan differs from the current resource state, the framework marks computed attributes that are null in the configuration as unknown in the plan. This is intended to prevent unexpected Terraform errors. Providers can later enter any values that may be known.
- Apply attribute plan modifiers.
- Apply resource plan modifiers.
When the Resource
interface Update
method runs to apply a change, all attribute state values must match their associated planned values or Terraform will generate a Provider produced inconsistent result
error. You can mark values as unknown in the plan if the full expected value is not known.
Refer to the Resource Instance Change Lifecycle document for more details about the concepts and processes relevant to the plan and apply workflows.
NOTE: Providers and data sources do not use the same planning mechanism as resources within Terraform. Neither support the concept of plan modification. Data sources should set any planned values in the Read
method.
Attribute Plan Modification
You can supply the tfsdk.Attribute
type PlanModifiers
field with a list of plan modifiers for that attribute. For example:
// Typically within the tfsdk.Schema returned by GetSchema() for a resource.
tfsdk.Attribute{
// ... other Attribute configuration ...
PlanModifiers: []AttributePlanModifiers{
resource.RequiresReplace(),
},
}
If defined, plan modifiers are applied to the current attribute. If any nested attributes define plan modifiers, then those are applied afterwards. Any plan modifiers that return an error will prevent Terraform from applying further modifiers of that attribute as well as any nested attribute plan modifiers.
Common Use Case Attribute Plan Modifiers
The framework implements some common use case modifiers:
resource.RequiresReplace()
: If the value of the attribute changes, in-place update is not possible and instead the resource should be replaced for the change to occur. Refer to the Go documentation for full details on its behavior.resource.RequiresReplaceIf()
: Similar toresource.RequiresReplace()
, however it also accepts provider-defined conditional logic. Refer to the Go documentation for full details on its behavior.resource.UseStateForUnknown()
: Copies the prior state value, if not null. This is useful for reducing(known after apply)
plan outputs for computed attributes which are known to not change over time.
Creating Attribute Plan Modifiers
To create an attribute plan modifier, you must implement the tfsdk.AttributePlanModifier
interface. For example:
// stringDefaultModifier is a plan modifier that sets a default value for a
// types.StringType attribute when it is not configured. The attribute must be
// marked as Optional and Computed. When setting the state during the resource
// Create, Read, or Update methods, this default value must also be included or
// the Terraform CLI will generate an error.
type stringDefaultModifier struct {
Default string
}
// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact.
func (m stringDefaultModifier) Description(ctx context.Context) string {
return fmt.Sprintf("If value is not configured, defaults to %s", m.Default)
}
// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact.
func (m stringDefaultModifier) MarkdownDescription(ctx context.Context) string {
return fmt.Sprintf("If value is not configured, defaults to `%s`", m.Default)
}
// Modify runs the logic of the plan modifier.
// Access to the configuration, plan, and state is available in `req`, while
// `resp` contains fields for updating the planned value, triggering resource
// replacement, and returning diagnostics.
func (m stringDefaultModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
// types.String must be the attr.Value produced by the attr.Type in the schema for this attribute
// for generic plan modifiers, use
// https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ConvertValue
// to convert into a known type.
var str types.String
diags := tfsdk.ValueAs(ctx, req.AttributePlan, &str)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
if !str.Null {
return
}
resp.AttributePlan = types.String{Value: m.Default}
}
Optionally, you may also want to create a helper function to instantiate the plan modifier. For example:
func stringDefault(defaultValue string) stringDefaultModifier {
return stringDefaultModifier{
Default: defaultValue,
}
}
Resource Plan Modification
Resources also support plan modification across all attributes. This is helpful when working with logic that applies to the resource as a whole, or in Terraform 1.3 and later, to return diagnostics during resource destruction. Implement the resource.ResourceWithModifyPlan
interface to support resource-level plan modification. For example:
// Ensure the Resource satisfies the resource.ResourceWithModifyPlan interface.
// Other methods to implement the resource.Resource interface are omitted for brevity
var _ resource.ResourceWithModifyPlan = exampleResource{}
type exampleResource struct {}
func (r exampleResource) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) {
// Fill in logic.
}
Resource Destroy Plan Diagnostics
Support for handling resource destruction during planning is available in Terraform 1.3 and later.
Implement the ModifyPlan
method by checking if the tfsdk.ModifyResourcePlanRequest
type Plan
field is a null
value:
func (r exampleResource) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) {
// If the entire plan is null, the resource is planned for destruction.
if req.Plan.Raw.IsNull() {
// Return an example warning diagnostic to practitioners.
resp.Diagnostics.AddWarning(
"Resource Destruction Considerations",
"Applying this resource destruction will only remove the resource from the Terraform state "+
"and will not call the deletion API due to API limitations. Manually use the web "+
"interface to fully destroy this resource.",
)
}
}
Ensure the response plan remains entirely null
when the request plan is entirely null
.