Terraform
Implement resource update
In this tutorial, you will add update capabilities to the order resource of a provider that interacts with the API of a fictional coffee-shop application called Hashicups. To do this, you will:
- Verify your schema and model. 
 Verify thelast_updatedattribute is in theorderresource schema and model. The provider will update this attribute to the current date time whenever the order resource is updated.
- Implement resource update. 
 This update method uses the HashiCups client library to invoke aPUTrequest to the/orders/{orderId}endpoint with the updated order items in the request body. After the update is successful, it updates the resource's state.
- Enhance plan output with plan modifier. 
 This clarifies the plan output of theidattribute to remove its difference by keeping the existing state value on updates.
- Verify update functionality. 
 This ensures the resource is working as expected.
Prerequisites
To follow this tutorial, you need:
- Go 1.21+ installed and configured.
- Terraform v1.8+ installed locally.
- Docker and Docker Compose to run an instance of HashiCups locally.
Navigate to your terraform-provider-hashicups directory.
Your code should match the 05-create-read-order
directory
from the example repository.
If you're stuck at any point during this tutorial, refer to the update-order branch to see the changes implemented in this tutorial.
Verify schema and model
Verify your Schema method includes an attribute named last_updated.
internal/provider/order_resource.go
func (r *orderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "id": schema.StringAttribute{
                Computed: true,
            },
            "last_updated": schema.StringAttribute{
                Computed: true,
            },
            "items": schema.ListNestedAttribute{
                // ...
Verify that your orderResourceModel type includes a field to handle the last_updated attribute.
internal/provider/order_resource.go
type orderResourceModel struct {
    ID          types.String     `tfsdk:"id"`
    Items       []orderItemModel `tfsdk:"items"`
    LastUpdated types.String     `tfsdk:"last_updated"`
}
Implement update functionality
The provider uses the Update method to update an existing resource based on the schema data.
The update method follows these steps:
- Retrieves values from the plan. The method will attempt to retrieve values from the plan and convert it to an orderResourceModel. The model includes the order'sidattribute, which specifies which order to update.
- Generates an API request body from the plan values. The method loops through each plan item and maps it to a hashicups.OrderItem. This is what the API client needs to update an existing order.
- Updates the order. The method invokes the API client's UpdateOrdermethod with the order's ID and OrderItems.
- Maps the response body to resource schema attributes. After the method updates the order, it maps the hashicups.Orderresponse to[]OrderItemso the provider can update the Terraform state.
- Sets the LastUpdated attribute. The method sets the Order's LastUpdated attribute to the current system time.
- Sets Terraform's state with the updated order.
Open the internal/provider/order_resource.go file.
Replace your Order resource's Update method in order_resource.go with the following.
internal/provider/order_resource.go
func (r *orderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
    // Retrieve values from plan
    var plan orderResourceModel
    diags := req.Plan.Get(ctx, &plan)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }
    // Generate API request body from plan
    var hashicupsItems []hashicups.OrderItem
    for _, item := range plan.Items {
        hashicupsItems = append(hashicupsItems, hashicups.OrderItem{
            Coffee: hashicups.Coffee{
                ID: int(item.Coffee.ID.ValueInt64()),
            },
            Quantity: int(item.Quantity.ValueInt64()),
        })
    }
    // Update existing order
    _, err := r.client.UpdateOrder(plan.ID.ValueString(), hashicupsItems)
    if err != nil {
        resp.Diagnostics.AddError(
            "Error Updating HashiCups Order",
            "Could not update order, unexpected error: "+err.Error(),
        )
        return
    }
    // Fetch updated items from GetOrder as UpdateOrder items are not
    // populated.
    order, err := r.client.GetOrder(plan.ID.ValueString())
    if err != nil {
        resp.Diagnostics.AddError(
            "Error Reading HashiCups Order",
            "Could not read HashiCups order ID "+plan.ID.ValueString()+": "+err.Error(),
        )
        return
    }
    // Update resource state with updated items and timestamp
    plan.Items = []orderItemModel{}
    for _, item := range order.Items {
        plan.Items = append(plan.Items, orderItemModel{
            Coffee: orderItemCoffeeModel{
                ID:          types.Int64Value(int64(item.Coffee.ID)),
                Name:        types.StringValue(item.Coffee.Name),
                Teaser:      types.StringValue(item.Coffee.Teaser),
                Description: types.StringValue(item.Coffee.Description),
                Price:       types.Float64Value(item.Coffee.Price),
                Image:       types.StringValue(item.Coffee.Image),
            },
            Quantity: types.Int64Value(int64(item.Quantity)),
        })
    }
    plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
    diags = resp.State.Set(ctx, plan)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }
}
Build and install the updated provider.
$ go install .
Verify update functionality
Navigate to the examples/order directory. This contains a sample Terraform configuration for the Terraform HashiCups provider.
$ cd examples/order
Replace your hashicups_order.edu resource in examples/order/main.tf. This configuration changes the drinks and quantities in the order.
examples/order/main.tf
resource "hashicups_order" "edu" {
  items = [{
    coffee = {
      id = 3
    }
    quantity = 2
    },
    {
      coffee = {
        id = 2
      }
      quantity = 3
  }]
}
Plan the configuration.
$ terraform plan
hashicups_order.edu: Refreshing state... [id=1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
Terraform will perform the following actions:
  # hashicups_order.edu will be updated in-place
  ~ resource "hashicups_order" "edu" {
      ~ id           = "1" -> (known after apply)
      ~ items        = [
          ~ {
              ~ coffee   = {
                  + description = (known after apply)
                    id          = 3
                  ~ image       = "/vault.png" -> (known after apply)
                  ~ name        = "Vaulatte" -> (known after apply)
                  ~ price       = 200 -> (known after apply)
                  ~ teaser      = "Nothing gives you a safe and secure feeling like a Vaulatte" -> (known after apply)
                }
                # (1 unchanged attribute hidden)
            },
          ~ {
              ~ coffee   = {
                  + description = (known after apply)
                  ~ id          = 1 -> 2
                  ~ image       = "/hashicorp.png" -> (known after apply)
                  ~ name        = "HCP Aeropress" -> (known after apply)
                  ~ price       = 200 -> (known after apply)
                  ~ teaser      = "Automation in a cup" -> (known after apply)
                }
              ~ quantity = 2 -> 3
            },
        ]
      ~ last_updated = "Thursday, 09-Feb-23 11:32:05 EST" -> (known after apply)
    }
Plan: 0 to add, 1 to change, 0 to destroy.
Note that the id attribute is showing a plan difference where the value is
going from the known value to an unknown value ((known after apply)).
      ~ id           = "1" -> (known after apply)
During updates, this attribute value is not expected to change. To prevent
practitioner confusion, in the next section you will update the id attribute's
definition to keep the existing state value on update.
Enhance plan output
Terraform Plugin Framework attributes which are not configurable and that should
not show updates from the existing state value should implement the
UseStateForUnknown() plan modifier.
Open the internal/provider/order_resource.go file.
Replace the id attribute definition in the Schema method with the following.
internal/provider/order_resource.go
// Schema defines the schema for the resource.
func (r *orderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "id": schema.StringAttribute{
                Computed: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
Replace the import statement with the following.
internal/provider/order_resource.go
import (
    "context"
    "fmt"
    "strconv"
    "time"
    "github.com/hashicorp-demoapp/hashicups-client-go"
    "github.com/hashicorp/terraform-plugin-framework/resource"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
    "github.com/hashicorp/terraform-plugin-framework/types"
)
In your terminal, navigate to the terraform-provider-hashicups directory.
cd ../..
Build and install the updated provider.
go install .
Verify update functionality
Navigate back to the examples/order directory.
cd examples/order
Run a Terraform apply to update your order. The plan will not mark the id attribute as (known after apply) any longer. Your provider will update your order and set a new value for the last_updated attribute.
$ terraform apply -auto-approve
##...
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
edu_order = {
  "id" = "1"
  "items" = tolist([
    {
      "coffee" = {
        "description" = ""
        "id" = 3
        "image" = "/vault.png"
        "name" = "Vaulatte"
        "price" = 200
        "teaser" = "Nothing gives you a safe and secure feeling like a Vaulatte"
      }
      "quantity" = 2
    },
    {
      "coffee" = {
        "description" = ""
        "id" = 2
        "image" = "/packer.png"
        "name" = "Packer Spiced Latte"
        "price" = 350
        "teaser" = "Packed with goodness to spice up your images"
      }
      "quantity" = 3
    },
  ])
  "last_updated" = "Thursday, 09-Feb-23 11:39:35 EST"
}
Verify that the provider updated your order by invoking the HashiCups API.
$ curl -X GET -H "Authorization: ${HASHICUPS_TOKEN}" localhost:19090/orders/1
{"id":1,"items":[{"coffee":{"id":3,"name":"Vaulatte","teaser":"Nothing gives you a safe and secure feeling like a Vaulatte","collection":"Foundations","origin":"Spring 2015","color":"#FFD814","description":"","price":200,"image":"/vault.png","ingredients":[{"ingredient_id":1},{"ingredient_id":2}]},"quantity":2},{"coffee":{"id":2,"name":"Packer Spiced Latte","teaser":"Packed with goodness to spice up your images","collection":"Origins","origin":"Summer 2013","color":"#1FA7EE","description":"","price":350,"image":"/packer.png","ingredients":[{"ingredient_id":1},{"ingredient_id":2},{"ingredient_id":4}]},"quantity":3}]}%
This is the same API call that your Terraform provider made to update your order's state.
Navigate back to the terraform-provider-hashicups directory.
$ cd ../..
Next steps
Congratulations! You have enhanced the order resource with update
capabilities.
If you were stuck during this tutorial, checkout the
06-update-order
directory in the example repository to see the code implemented in this
tutorial.
- To learn more about the Terraform Plugin Framework, refer to the Terraform Plugin Framework documentation.
- For a full capability comparison between the SDKv2 and the Plugin Framework, refer to the Which SDK Should I Use? documentation.
- The example repository contains directories corresponding to each tutorial in this collection.
- Submit any Terraform Plugin Framework bug reports or feature requests to the development team in the Terraform Plugin Framework Github repository.
- Submit any Terraform Plugin Framework questions in the Terraform Plugin Framework Discuss forum.