hashicorp / hashicorp/terraform-plugin-framework

Allow plan diff for lists to be customized

Open
#1,213 2 comments 4 reactions 0 assignees View on GitHub
enhancement
Dominant language
Go
Stars
384
Forks
107
Avg merge
3m
Merged PRs (30d)
1

Description

### Module version

```
v1.15.0
```

### Use-cases

The [`cloudflare_ruleset` resource](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) takes an ordered list of rules as an attribute:

```terraform
resource "cloudflare_ruleset" "my_ruleset" {
zone_id = var.zone_id
name = "My Ruleset"
phase = "http_request_firewall_custom"
kind = "zone"
rules = [
{
expression = "ip.src eq 1.1.1.1"
action = "block"
ref = "one"
},
{
expression = "ip.src eq 2.2.2.2"
action = "block"
ref = "two"
}
]
}
```

Often, users want to insert or delete a rule midway through the list:

```terraform
resource "cloudflare_ruleset" "my_ruleset" {
zone_id = var.zone_id
name = "My Ruleset"
phase = "http_request_firewall_custom"
kind = "zone"
rules = [
{
expression = "ip.src eq 2.2.2.2"
action = "block"
ref = "two"
}
]
}
```

However, Terraform cannot "match" the rules in the state to those in the plan, so shows a complex diff where many rules are shifted up/down (this gets worse the more rules there are in the ruleset):

```terraform
# cloudflare_ruleset.my_ruleset will be updated in-place
~ resource "cloudflare_ruleset" "my_ruleset" {
id = "d94e34a9782e4156a58473bb13070748"
~ last_updated = "2025-08-28T18:55:06Z" -> (known after apply)
name = "My Ruleset"
~ rules = [
~ {
~ expression = "ip.src eq 1.1.1.1" -> "ip.src eq 2.2.2.2"
~ id = "5663e5c68fd64d5cbbd055820cf836a4" -> "eaefa038f18d422aab1f9fc5183f6ae8"
~ ref = "one" -> "two"
# (4 unchanged attributes hidden)
},
- {
- action = "block" -> null
- action_parameters = {} -> null
- enabled = true -> null
- expression = "ip.src eq 2.2.2.2" -> null
- id = "eaefa038f18d422aab1f9fc5183f6ae8" -> null
- ref = "two" -> null
# (1 unchanged attribute hidden)
},
]
~ version = "1" -> (known after apply)
# (4 unchanged attributes hidden)
}
```

In this case though, the provider has knowledge of how the rules in the state map to the rules in the plan, by using the `ref` attribute of each rule.

Therefore, it would be ideal for the provider to have a way to pass this information to Terraform, so it can show a better diff:

```terraform
# cloudflare_ruleset.my_ruleset will be updated in-place
~ resource "cloudflare_ruleset" "my_ruleset" {
id = "d94e34a9782e4156a58473bb13070748"
~ last_updated = "2025-08-28T18:55:06Z" -> (known after apply)
name = "My Ruleset"
~ rules = [
- {
- action = "block" -> null
- action_parameters = {} -> null
- enabled = true -> null
- expression = "ip.src eq 1.1.1.1" -> null
- id = "5663e5c68fd64d5cbbd055820cf836a4" -> null
- ref = "one" -> null
# (1 unchanged attribute hidden)
},
# (1 unchanged element hidden)
]
~ version = "1" -> (known after apply)
# (4 unchanged attributes hidden)
}
```

### Attempted Solutions

I am not aware of any ways to achieve this today, unfortunately.

### Proposal

I think this is something that could potentially be customized in a `ModifyPlan` function. In the `ModifyPlanResponse.Plan`, perhaps each list element could take an optional parameter with the index of the element in the state that it corresponds to.

Alternatively, perhaps `schema.ListNestedAttribute` could take an optional parameter which specifies how to match elements in the state and the plan.

### References

There are several related issues around diffing of lists (but not about allowing providers to customize this AFAICT):
- https://github.com/hashicorp/terraform/issues/14275
- https://github.com/hashicorp/terraform/issues/33779

This has also come up as an issue for the `cloudflare_ruleset` resource:
- https://github.com/cloudflare/terraform-provider-cloudflare/issues/5921

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.