Terraform
Update Resources
In-place update is part of the basic Terraform lifecycle for managing resources. During the terraform apply
command, Terraform calls the provider ApplyResourceChange
RPC, in which the framework calls the resource.Resource
interface Update
method. The request contains Terraform prior state, configuration, and plan data. The response contains updated state data. The data is defined by the schema of the resource.
To ensure that Terraform plans replacement of a resource, rather than perform an in-place update, use the resource.RequiresReplace()
attribute plan modifier in the schema or implement resource plan modification.
Other resource lifecycle implementations include:
- Create resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data.
- Read resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data.
- Delete resources by receiving Terraform prior state data and performing deletion logic.
Define Update Method
Implement the Update
method by:
- Accessing the data from the
resource.UpdateRequest
type. Most use cases should access the plan data in theresource.UpdateRequest.Plan
field. - Performing logic or external calls to modify the resource.
- Writing state data into the
resource.UpdateResponse.State
field.
If the logic needs to return warning or error diagnostics, they can added into the resource.UpdateResponse.Diagnostics
field.
In this example, the Update
function makes a HTTP call and returns successfully if the status code was 200 OK:
// ThingResource defines the resource implementation.
// Some resource.Resource interface methods are omitted for brevity.
type ThingResource struct {
// client is configured via a Configure method, which is not shown in this
// example for brevity. Refer to the Configure Resources documentation for
// additional details for setting up resources with external clients.
client *http.Client
}
// ThingResourceModel describes the Terraform resource data model to match the
// resource schema.
type ThingResourceModel struct {
Name types.String `tfsdk:"name"`
Id types.String `tfsdk:"id"`
}
// ThingResourceAPIModel describes the API data model.
type ThingResourceAPIModel struct {
Name string `json:"name"`
Id string `json:"id"`
}
func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
MarkdownDescription: "Name of the thing to be saved in the service.",
Required: true,
},
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Service generated identifier for the thing.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
MarkdownDescription: "Manages a thing.",
}
}
func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data ThingResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
// Convert from Terraform data model into API data model
updateReq := ThingResourceAPIModel{
Id: data.Id.ValueString(),
Name: data.Name.ValueString(),
}
httpReqBody, err := json.Marshal(updateReq)
if err != nil {
resp.Diagnostics.AddError(
"Unable to Update Resource",
"An unexpected error occurred while creating the resource update request. "+
"Please report this issue to the provider developers.\n\n"+
"JSON Error: "+err.Error(),
)
return
}
// Create HTTP request
httpReq := http.NewRequestWithContext(
ctx,
http.MethodPut,
"http://example.com/things",
bytes.NewBuffer(httpReqBody),
)
// Send HTTP request
httpResp, err := d.client.Do(httpReq)
defer httpResp.Body.Close()
if err != nil {
resp.Diagnostics.AddError(
"Unable to Update Resource",
"An unexpected error occurred while attempting to update the resource. "+
"Please retry the operation or report this issue to the provider developers.\n\n"+
"HTTP Error: "+err.Error(),
)
return
}
// Return error if the HTTP status code is not 200 OK
if httpResp.StatusCode != http.StatusOK {
resp.Diagnostics.AddError(
"Unable to Update Resource",
"An unexpected error occurred while attempting to update the resource. "+
"Please retry the operation or report this issue to the provider developers.\n\n"+
"HTTP Status: "+httpResp.Status,
)
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Caveats
Note these caveats when implementing the Update
method:
- An error is returned if the response state is not set when
Update
is called by the framework. If the resource does not support modification and should always be recreated on configuration value updates, theUpdate
logic can be left empty and ensure all configurable schema attributes implement theresource.RequiresReplace()
attribute plan modifier. - An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response.
- An error is returned if the response state has the
RemoveResource()
method called. This method is not valid during update. Return an error if the resource is no longer exists. - An error is returned unless every null or known value in the request plan is saved exactly as-is into the response state. Only unknown plan values can be modified.
Recommendations
Note these recommendations when implementing the Update
method:
- Get request data from the Terraform plan data over configuration data as the schema or resource may include plan modification logic which sets plan values.
- Only successfully modified parts of the resource should be return updated data in the state response.
- Use the
resource.UseStateForUnknown()
attribute plan modifier forComputed
attributes that are known to not change during resource updates. This will enhance the Terraform plan to not show=> (known after apply)
differences.
Additional Use Cases
This section highlights implementation details for specific use cases.
Detect Specific Attribute Changes
Certain update APIs require that only value changes are sent in the update request or require individual update API calls. Compare the attribute plan value to the attribute prior state value to determine individual attribute changes.
In this example, the entire plan and prior state are fetched then the attribute values are compared:
type ThingResourceModel struct {
Name types.String `tfsdk:"name"`
// ... other attributes ...
}
func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state ThingResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Compare name attribute value between plan and prior state
if !plan.Name.Equal(state.Name) {
// name attribute was changed
}
// ... further logic ...
}
In this example, the attribute is individually fetched from the plan and prior state then the values are compared:
func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var namePlan, nameState types.String
resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("name"), &namePlan)...)
resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &nameState)...)
if resp.Diagnostics.HasError() {
return
}
// Compare name attribute value between plan and prior state
if !namePlan.Equal(nameState) {
// name attribute was changed
}
// ... further logic ...
}