Confirmation
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:
include = { all = true } -> permanent update:
.rules[i].action_parameters.cache_key.custom_key.query_string.include.all: null -> true
- omitting
include entirely -> permanent update:
...query_string.include: { all: null, list: null } -> null
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
- 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).
import the ruleset (import block or terraform import).
- Run
terraform plan with any of the three configuration variants above.
- 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
Confirmation
Terraform and Cloudflare provider version
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)
http_request_cache_settings,action_parameters.cache_key.custom_key.query_string)Terraform configuration files
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
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:That state cannot be expressed in configuration, so an imported ruleset can never reach
No changes:include = { all = true }-> permanent update:.rules[i].action_parameters.cache_key.custom_key.query_string.include.all: null -> trueincludeentirely -> permanent update:...query_string.include: { all: null, list: null } -> nullinclude = {}-> validation error:Error: Invalid Attribute Combination - No attribute specified when one (and only one) of [...query_string.include.list.<.all] is requiredAll 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
GET /zones/{zone}/rulesets/phases/http_request_cache_settings/entrypointreturns"include": "*"for them to this day).importthe ruleset (import block orterraform import).terraform planwith any of the three configuration variants above.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
cloudflare_ruleset: exclude list format inaction_parametersseems to conflict with result from API #6087 (closed) - the sibling problem on theexcludeside, API result conflicting with the provider's expected formatquery_string includeAPI-call format issue