Skip to content

Allow plan diff for lists to be customized #1213

@zakcutner

Description

@zakcutner

Module version

v1.15.0

Use-cases

The cloudflare_ruleset resource takes an ordered list of rules as an attribute:

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:

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):

  # 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:

  # 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):

This has also come up as an issue for the cloudflare_ruleset resource:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions