terraform

Terraform tries to remove whole block instead of single attributes


I'm creating a custom terraform provider using terraform-plugin-sdk.

Please forgive me if this is obvious or an expected behaviour, I am new to this.

Removing a whole block works as expected and the CRUD is working. However, during plan and if I update a single attributes, the plan shows the removal of the whole block and add it back.

func ResourceTest() *schema.Resource {
    return &schema.Resource{
        Create: ResourceTestCreate,
        Read:   ResourceTestRead,
        Update: ResourceTestUpdate,
        Delete: ResourceTestDelete,

        Schema: map[string]*schema.Schema{
            "name": {
                Type:        schema.TypeString,
                Required:    true,
                ForceNew:    true,
            },
            "other_attributes": {}
            "backend": {
                Type:        schema.TypeString,
                Required:    true,
                ForceNew:    true,
            },
            "my_block": {
                Type:     schema.TypeSet,
                Optional: true,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "attribute1": {
                            Type:        schema.TypeString,
                            Required:    true,
                        },
                        "attribute2": {
                            Type:        schema.TypeInt,
                            Required:    true,
                        },
                        "attribute3": {
                            Type:        schema.TypeString,
                            Required:    true,
                        },
                    },
                },
            },
        },
    }
}

Terraform definition:

resource "cutom_provider_test" "test" {
  name = "test"
  my_block {
    attribute1        = "cool"
    attribute2        = 1
    attribute3        = "test"

  }
  my_block {
    attribute1        = "cool2"
    attribute2        = 1
    attribute3        = "test2"

  }

}

Plan results:

When I remove the whole block the result is as expected (plan output is to remove the whole block.

When I update a single attribute (attribute1) with a new value the result is trying to remove the whole block an re-create it with the new attribute1 value.

Expected result: When I update a single attribute (attribute2) with a new value the result should only update the single attribute.

Any help is greatly appreciated!


Solution

  • You have defined my_block as being represented as a set of objects.

    The main distinguishing feature of a set is that any given value is either in the set or not in the set. The elements of the set have no identity other than their value.

    This differs from Terraform's other two collection types: lists allow identifying elements by their position in the list (their index), while maps allow identifying elements by their unique keys.

    Because set elements have no identity other than their value, there can therefore be no concept of changing the value of a set element: if you change its value then you've changed its identity, and so that's indistinguishable from removing the old value and adding the new value.


    If you want to be able to represent the idea of updating an existing object then you'll need to either use a list type or a map type.

    Using a list type is appropriate only if the remote system that the provider is wrapping also considers the objects to be identified by their position in the sequence, because otherwise the diff will not properly reflect how the remote system will understand the change.

    Using a map type is often the better choice because it allows you to choose any string to be the identifier for the elements, and so you can match whatever unique identifier the remote system uses for these objects and so Terraform's description of the change will match how the remote system understands it. However, you've chosen to use Terraform's legacy plugin SDK instead of the modern plugin framework and so using a map of objects is not possible. You would need to use the plugin framework if you want to use a map, and then you could use a "map nested" attribute instead of a block type.