• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Terraform
  • Install
  • Tutorials
    • About the Docs
    • Configuration Language
    • Terraform CLI
    • Terraform Cloud
    • Terraform Enterprise
    • CDK for Terraform
    • Provider Use
    • Plugin Development
    • Registry Publishing
    • Integration Program
  • Registry(opens in new tab)
  • Try Cloud(opens in new tab)
  • Sign up
Plugin Development

SDKv2

Skip to main content
  • SDKv2
  • Migrate to the Framework
    (opens in new tab)
    • Overview
    • Customizing Differences
    • Import
    • Retries and Customizable Timeouts
    • State Migration
  • Debugging Providers

  • Resources

  • Tutorial Library
  • Certifications
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  • Terraform Registry
    (opens in new tab)
  1. Developer
  2. Terraform
  3. Plugin Development
  4. SDKv2
  5. Resources
  6. Retries and Customizable Timeouts
  • Plugin SDK
  • v2.23.x
  • v2.22.x
  • v2.21.x
  • v2.20.x
  • v2.19.x
  • v2.18.x
  • v2.17.x
  • v2.16.x
  • v2.15.x

ยปResources - Retries and Customizable Timeouts

The reality of cloud infrastructure is that it typically takes time to perform operations such as booting operating systems, discovering services, and replicating state across network edges. As the provider developer you should take known delays in resource APIs into account in the CRUD functions of the resource. Terraform supports configurable timeouts to assist in these situations.

package example

import (
    "fmt"

    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceExampleInstance() *schema.Resource {
    return &schema.Resource{
        CreateContext: resourceExampleInstanceCreate,
        ReadContext:   resourceExampleInstanceRead,
        UpdateContext: resourceExampleInstanceUpdate,
        DeleteContext: resourceExampleInstanceDelete,

        Schema: map[string]*schema.Schema{
            "name": {
                Type:     schema.TypeString,
                Required: true,
            },
        },
        Timeouts: &schema.ResourceTimeout{
            Create: schema.DefaultTimeout(45 * time.Minute),
        },
    }
}

In the above example we see the usage of the timeouts in the schema being configured for what is deemed the appropriate amount of time for the Create function. Read, Update, and Delete are also configurable as well as a Default. These configured timeouts can be fetched in the CRUD function logic using the (*schema.ResourceData).Timeout() method, such as d.Timeout(schema.TimeoutCreate). Practitioners can override these timeout values with resource timeouts configuration, such as:

resource "example_thing" "example" {
  # ...

  timeouts {
    create = "60m"
  }
}

Default Timeouts and Deadline Exceeded Errors

The SDK imposes the following default timeout behaviors for CRUD functions:

CRUD FunctionDefault Timeout
Create20 minutes
CreateContext20 minutes
CreateWithoutTimeoutN/A
Delete20 minutes
DeleteContext20 minutes
DeleteWithoutTimeoutN/A
Read20 minutes
ReadContext20 minutes
ReadWithoutTimeoutN/A
Update20 minutes
UpdateContext20 minutes
UpdateWithoutTimeoutN/A

The *schema/Resource.Timeouts field can customize the default timeout on CRUD functions with default timeouts.

If a CRUD function timeout is exceeded, the SDK will automatically return a context.DeadlineExceeded error. To practitioners, this is shown in the Terraform CLI output as a context: deadline exceeded error. Since the context timeout and associated error handling occur outside CRUD logic in the SDK, it is not possible to capture or change this error behavior. If it is unclear how long CRUD operations may take, it is recommended to either increase the default timeout using the Timeouts field, or switch to using the WithoutTimeout CRUD functions.

Retry

The retry helper takes a timeout and a retry function.

  • The timeout value specifies the maximum time Terraform will invoke the retry function. You can retrieve the timeout from the *schema.ResourceData struct by passing the timeout key (schema.TimeoutCreate) to the Timeout method.
  • The retry function returns either a resource.NonRetryableError for unexpected errors/states or a resource.RetryableError for expected errrors/states. If the function returns a resource.RetryableError, it will re-run the function.

In the context of a CREATE function, once the backend responds with the desired state, invoke the READ function. If READ errors, return that error wrapped with resource.NonRetryableError. Otherwise, return nil (no error) from the retry function.

func resourceExampleInstanceCreate(d *schema.ResourceData, meta any) error {
    name := d.Get("name").(string)
    client := meta.(*ExampleClient)
    resp, err := client.CreateInstance(name)

    if err != nil {
        return fmt.Errorf("Error creating instance: %s", err)
    }

    return resource.Retry(d.Timeout(schema.TimeoutCreate) - time.Minute, func() *resource.RetryError {
        resp, err := client.DescribeInstance(name)

        if err != nil {
            return resource.NonRetryableError(fmt.Errorf("Error describing instance: %s", err))
        }

        if resp.Status != "CREATED" {
            return resource.RetryableError(fmt.Errorf("Expected instance to be created but was in state %s", resp.Status))
        }

        err = resourceExampleInstanceRead(d, meta)
        if err != nil {
            return resource.NonRetryableError(err)
        } else {
            return nil
        }
    })
}

Important If using a CRUD function with a timeout, any Retry() or RetryContext() function timeouts should be configured below that duration to avoid returning the SDK context: deadline exceeded error instead of the retry logic error.

StateChangeConf

resource.Retry is useful for simple scenarios, particularly when the API response is either success or failure, but sometimes handling an APIs latency or eventual consistency requires more fine tuning. resource.Retry is in fact a wrapper for a another helper: resource.StateChangeConf.

Use resource.StateChangeConf when your resource has multiple states to progress though, you require fine grained control of retry and delay timing, or you want to ensure a minimum number of occurrences of a target state is reached (this is very common when dealing with eventually consistent APIs, where a response can reply back with an old state between calls before becoming consistent).

func resourceExampleInstanceCreate(d *schema.ResourceData, meta any) error {
    name := d.Get("name").(string)
    client := meta.(*ExampleClient)
    resp, err := client.CreateInstance(name)

    createStateConf := &resource.StateChangeConf{
        Pending: []string{
            client.ExampleInstanceStateRequesting,
            client.ExampleInstanceStatePending,
            client.ExampleInstanceStateCreating,
            client.ExampleInstanceStateVerifying,
        },
        Target: []string{
            client.ExampleInstanceStateCreateComplete,
        },
        Refresh: func() (any, string, error) {
            resp, err := client.DescribeInstance(name)
            if err != nil {
                0, "", err
            }
            return resp, resp.Status, nil
        },
        Timeout:    d.Timeout(schema.TimeoutCreate) - time.Minute,
        Delay:      10 * time.Second,
        MinTimeout: 5 * time.Second,
        ContinuousTargetOccurence: 5,
    }
    _, err = createStateConf.WaitForState()
    if err != nil {
        return fmt.Errorf("Error waiting for example instance (%s) to be created: %s", d.Id(), err)
    }

    return resourceExampleInstanceRead(d, meta)
}

Important If using a CRUD function with a timeout, any StateChangeConf timeouts should be configured below that duration to avoid returning the SDK context: deadline exceeded error instead of the retry logic error.

Edit this page on GitHub

On this page

  1. Resources - Retries and Customizable Timeouts
  2. Default Timeouts and Deadline Exceeded Errors
  3. Retry
  4. StateChangeConf
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)