Skip to content

cloudflare_ruleset: legacy cache query_string.include: "*" is read into a state the schema cannot express - imported rulesets always drift #7334

Description

@herangstl

Confirmation

  • This is a bug with an existing resource and is not a feature request or enhancement. Feature requests should be submitted with Cloudflare Support or your account team.
  • I have searched the issue tracker and my issue isn't already found.
  • I have replicated my issue using the latest version of the provider and it is still present.

Terraform and Cloudflare provider version

Terraform v1.7.5
on linux_amd64
+ provider registry.terraform.io/cloudflare/cloudflare v5.23.0

The read-side behaviour below was measured on v5.23.0. The config-side constraint (the ExactlyOneOf validation) is reproduced on v5.24.0 as well, and the v5.24.0 changelog lists no related change. Also reproduced under Terraform v1.15.9.

Affected resource(s)

  • cloudflare_ruleset (phase http_request_cache_settings, action_parameters.cache_key.custom_key.query_string)

Terraform configuration files

resource "cloudflare_ruleset" "cache_example" {
  zone_id = "0123456789abcdef0123456789abcdef"
  name    = "default"
  kind    = "zone"
  phase   = "http_request_cache_settings"

  rules = [
    {
      ref        = "example"
      action     = "set_cache_settings"
      expression = "http.request.uri wildcard \"*.example.com/*\""
      enabled    = true
      action_parameters = {
        cache = true
        cache_key = {
          custom_key = {
            query_string = {
              include = { all = true } # <- no possible value round-trips, see below
            }
            ignore_query_strings_order = true
          }
        }
      }
    },
  ]
}

Link to debug output

Not applicable - the behaviour is fully characterised by the plan JSON excerpts below. Happy to provide TF_LOG output on request.

Panic output

n/a

Expected output

Importing a ruleset whose API representation uses the legacy string form

"query_string": { "include": "*" }

should round-trip: the provider should decode "*" as { "all": true } (the two forms are documented as equivalent), and a plan right after the import should show no changes.

Actual output

The provider decodes the legacy "*" into an empty include object. In the plan JSON the imported state reads:

"include": { "all": null, "list": null }

That state cannot be expressed in configuration, so an imported ruleset can never reach No changes:

  1. include = { all = true } -> permanent update:
    .rules[i].action_parameters.cache_key.custom_key.query_string.include.all: null -> true
  2. omitting include entirely -> permanent update:
    ...query_string.include: { all: null, list: null } -> null
  3. include = {} -> validation error:
    Error: Invalid Attribute Combination - No attribute specified when one (and only one) of [...query_string.include.list.<.all] is required

All three variants measured; there is no fourth spelling. Any adopt-only / import workflow for rulesets that still carry the legacy API form is impossible - we had to leave those rulesets unmanaged.

Steps to reproduce

  1. Take a zone whose cache-settings entrypoint was created while the API still stored the legacy string form (ours were created mid-2024 via the dashboard; GET /zones/{zone}/rulesets/phases/http_request_cache_settings/entrypoint returns "include": "*" for them to this day).
  2. import the ruleset (import block or terraform import).
  3. Run terraform plan with any of the three configuration variants above.
  4. Observe the permanent diff (variants 1 and 2) or the validation error (variant 3).

Important factoids

Newly created rulesets get the object form and are unaffected; this only bites rulesets that predate the object form and have not been rewritten since. Editing the rule in the dashboard does not normalise the stored form.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions