diff --git a/.changelog/4565.txt b/.changelog/4565.txt index 667c22bc4c..d4ed3c86fa 100644 --- a/.changelog/4565.txt +++ b/.changelog/4565.txt @@ -1,4 +1,4 @@ - ```release-note:new-resource +```release-note:new-resource cloudflare_snippet ``` diff --git a/.changelog/4697.txt b/.changelog/4697.txt new file mode 100644 index 0000000000..2fd951a01f --- /dev/null +++ b/.changelog/4697.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/cloudflare_ruleset: improve diffs when only some rules are changed +``` + +```release-note:note +resource/cloudflare_ruleset: rules must now be given an explicit `ref` to avoid their IDs changing across ruleset updates, see https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/ +``` diff --git a/.changelog/4721.txt b/.changelog/4721.txt new file mode 100644 index 0000000000..7898c46db0 --- /dev/null +++ b/.changelog/4721.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/access_identity_provider: document scim_config fields +``` diff --git a/.changelog/4737.txt b/.changelog/4737.txt new file mode 100644 index 0000000000..336287c6ad --- /dev/null +++ b/.changelog/4737.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_teams_list: use PUT call to update list items +``` diff --git a/.changelog/4741.txt b/.changelog/4741.txt new file mode 100644 index 0000000000..4c9de3b447 --- /dev/null +++ b/.changelog/4741.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/cloudflare_leaked_credential_check_rule: Fix bug in update method +``` \ No newline at end of file diff --git a/.changelog/4755.txt b/.changelog/4755.txt new file mode 100644 index 0000000000..408abf9e33 --- /dev/null +++ b/.changelog/4755.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump golang.org/x/crypto from 0.21.0 to 0.31.0 in /tools +``` diff --git a/.changelog/4756.txt b/.changelog/4756.txt new file mode 100644 index 0000000000..98bcefa2ba --- /dev/null +++ b/.changelog/4756.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump golang.org/x/crypto from 0.30.0 to 0.31.0 +``` diff --git a/.changelog/4762.txt b/.changelog/4762.txt new file mode 100644 index 0000000000..7eac00a8bf --- /dev/null +++ b/.changelog/4762.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump github.com/hashicorp/terraform-plugin-framework-validators from 0.15.0 to 0.16.0 +``` diff --git a/.changelog/4802.txt b/.changelog/4802.txt new file mode 100644 index 0000000000..6c25bb2133 --- /dev/null +++ b/.changelog/4802.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump golang.org/x/net from 0.32.0 to 0.33.0 +``` diff --git a/.changelog/4803.txt b/.changelog/4803.txt new file mode 100644 index 0000000000..f283139993 --- /dev/null +++ b/.changelog/4803.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump github.com/cloudflare/cloudflare-go from 0.111.0 to 0.112.0 +``` diff --git a/.github/workflows/next-acceptance-tests.yml b/.github/workflows/next-acceptance-tests.yml index 39c837c671..fd68938eb5 100644 --- a/.github/workflows/next-acceptance-tests.yml +++ b/.github/workflows/next-acceptance-tests.yml @@ -43,4 +43,4 @@ jobs: id: go - run: go install gotest.tools/gotestsum@latest - run: go get github.com/cloudflare/cloudflare-go/v3@next - - run: TF_ACC=1 gotestsum ./internal/services/{argo_smart_routing,argo_tiered_caching,bot_management,d1_database,dns_firewall,dns_record,healthcheck,list,origin_ca_certificate,queue,r2_bucket,secondary_dns_acl,secondary_dns_peer,secondary_dns_tsig,tiered_cache,total_tls,zone,zone_cache_variants,zone_setting,zone_subscription} -run "^TestAcc" -count 1 -v -timeout 120m + - run: TF_ACC=1 gotestsum ./internal/services/{argo_smart_routing,argo_tiered_caching,bot_management,d1_database,dns_firewall,dns_record,healthcheck,list,origin_ca_certificate,queue,r2_bucket,tiered_cache,total_tls,zone,zone_cache_variants,zone_setting,zone_subscription} -run "^TestAcc" -count 1 -v -timeout 120m diff --git a/CHANGELOG.md b/CHANGELOG.md index b36f2497ca..67a986f4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,36 @@ -## 4.48.0 (Unreleased) +## 4.49.0 (Unreleased) + +## 4.48.0 (December 11th, 2024) + +NOTES: + +* resource/cloudflare_ruleset: rules must now be given an explicit `ref` to avoid their IDs changing across ruleset updates, see https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/ ([#4697](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4697)) + +FEATURES: + +* **New Resource:** `cloudflare_leaked_credential_check` ([#4674](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4674)) +* **New Resource:** `cloudflare_leaked_credential_check_rule` ([#4676](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4676)) +* **New Resource:** `cloudflare_snippet` ([#4565](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4565)) +* **New Resource:** `cloudflare_snippet_rules` ([#4565](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4565)) + +ENHANCEMENTS: + +* resource/access_application: add support for destinations and domain_type ([#4661](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4661)) +* resource/access_identity_provider: document scim_config fields ([#4721](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4721)) +* resource/cloudflare_access_policy: adds support for Access infrastructure `allow_email_alias` connection rule flag ([#4665](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4665)) +* resource/cloudflare_ruleset: improve diffs when only some rules are changed ([#4697](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4697)) +* resource/cloudflare_teams_list: use PUT call to update list items ([#4737](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4737)) +* resource/cloudflare_zero_trust_access_policy: adds support for Access infrastructure `allow_email_alias` connection rule flag ([#4665](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4665)) + +BUG FIXES: + +* resource/cloudflare_authenticated_origin_pulls: Fix issue where resources are disabled instead of being destroyed on `tf destroy` ([#4649](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4649)) +* resource/cloudflare_leaked_credential_check_rule: Fix bug in update method ([#4741](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4741)) + +DEPENDENCIES: + +* provider: bump github.com/cloudflare/cloudflare-go from 0.110.0 to 0.111.0 ([#4709](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4709)) +* provider: bump golang.org/x/net from 0.31.0 to 0.32.0 ([#4718](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4718)) ## 4.47.0 (November 27th, 2024) diff --git a/docs/resources/access_identity_provider.md b/docs/resources/access_identity_provider.md index 1665150f55..cec84badcc 100644 --- a/docs/resources/access_identity_provider.md +++ b/docs/resources/access_identity_provider.md @@ -128,12 +128,12 @@ Read-Only: Optional: -- `enabled` (Boolean) -- `group_member_deprovision` (Boolean) -- `identity_update_behavior` (String) -- `seat_deprovision` (Boolean) -- `secret` (String, Sensitive) -- `user_deprovision` (Boolean) +- `enabled` (Boolean) A flag to enable or disable SCIM for the identity provider. +- `group_member_deprovision` (Boolean) Deprecated. Use `identity_update_behavior`. +- `identity_update_behavior` (String) Indicates how a SCIM event updates a user identity used for policy evaluation. Use "automatic" to automatically update a user's identity and augment it with fields from the SCIM user resource. Use "reauth" to force re-authentication on group membership updates, user identity update will only occur after successful re-authentication. With "reauth" identities will not contain fields from the SCIM user resource. With "no_action" identities will not be changed by SCIM updates in any way and users will not be prompted to reauthenticate. +- `seat_deprovision` (Boolean) A flag to remove a user's seat in Zero Trust when they have been deprovisioned in the Identity Provider. This cannot be enabled unless user_deprovision is also enabled. +- `secret` (String, Sensitive) A read-only token generated when the SCIM integration is enabled for the first time. It is redacted on subsequent requests. If you lose this you will need to refresh it token at /access/identity_providers/:idpID/refresh_scim_secret. +- `user_deprovision` (Boolean) A flag to enable revoking a user's session in Access and Gateway when they have been deprovisioned in the Identity Provider. ## Import diff --git a/docs/resources/access_policy.md b/docs/resources/access_policy.md index 44ac5b241f..531387752f 100644 --- a/docs/resources/access_policy.md +++ b/docs/resources/access_policy.md @@ -245,6 +245,9 @@ Required: Required: - `usernames` (List of String) Contains the Unix usernames that may be used when connecting over SSH. + +Optional: + - `allow_email_alias` (Boolean) Allows connecting to Unix username that matches the authenticating email prefix. diff --git a/docs/resources/cloud_connector_rules.md b/docs/resources/cloud_connector_rules.md index 6625c152da..e4d2ee0cae 100644 --- a/docs/resources/cloud_connector_rules.md +++ b/docs/resources/cloud_connector_rules.md @@ -2,12 +2,12 @@ page_title: "cloudflare_cloud_connector_rules Resource - Cloudflare" subcategory: "" description: |- - The Cloud Connector Rules add link to doc resource allows you to create and manage cloud connector rules for a zone. + The Cloud Connector Rules resource allows you to create and manage cloud connector rules for a zone. --- # cloudflare_cloud_connector_rules (Resource) -The [Cloud Connector Rules](add link to doc) resource allows you to create and manage cloud connector rules for a zone. +The [Cloud Connector Rules](https://developers.cloudflare.com/rules/cloud-connector/) resource allows you to create and manage cloud connector rules for a zone. ## Example Usage diff --git a/docs/resources/filter.md b/docs/resources/filter.md index b17bc92490..9ec58fbd06 100644 --- a/docs/resources/filter.md +++ b/docs/resources/filter.md @@ -13,7 +13,7 @@ Filter expressions that can be referenced across multiple features, e.g. Firewall Rules. See [what is a filter](https://developers.cloudflare.com/firewall/api/cf-filters/what-is-a-filter/) for more details and available fields and operators. -~> `cloudflare_filter` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_filter` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/docs/resources/firewall_rule.md b/docs/resources/firewall_rule.md index 3a42b92e20..c83b4397e2 100644 --- a/docs/resources/firewall_rule.md +++ b/docs/resources/firewall_rule.md @@ -20,7 +20,7 @@ rule creation. Filter expressions needs to be created first before using Firewall Rule. -~> `cloudflare_firewall_rule` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_firewall_rule` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/docs/resources/rate_limit.md b/docs/resources/rate_limit.md index 3b38f9430b..010dee68ae 100644 --- a/docs/resources/rate_limit.md +++ b/docs/resources/rate_limit.md @@ -13,7 +13,7 @@ Provides a Cloudflare rate limit resource for a given zone. This can be used to limit the traffic you receive zone-wide, or matching more specific types of requests/responses. -~> `cloudflare_rate_limit` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_rate_limit` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/docs/resources/ruleset.md b/docs/resources/ruleset.md index 52006e56e3..6a8962d906 100644 --- a/docs/resources/ruleset.md +++ b/docs/resources/ruleset.md @@ -2,23 +2,19 @@ page_title: "cloudflare_ruleset Resource - Cloudflare" subcategory: "" description: |- - The Cloudflare Ruleset Engine https://developers.cloudflare.com/firewall/cf-rulesets + The Cloudflare Ruleset Engine (https://developers.cloudflare.com/ruleset-engine/about/) allows you to create and deploy rules and rulesets. - The engine syntax, inspired by the Wireshark Display Filter language, is the - same syntax used in custom Firewall Rules. Cloudflare uses the Ruleset Engine - in different products, allowing you to configure several products using the same - basic syntax. + Cloudflare uses the Ruleset Engine in different products, allowing + you to configure several products using the same basic syntax. --- # cloudflare_ruleset (Resource) -The [Cloudflare Ruleset Engine](https://developers.cloudflare.com/firewall/cf-rulesets) +The Cloudflare Ruleset Engine (https://developers.cloudflare.com/ruleset-engine/about/) allows you to create and deploy rules and rulesets. -The engine syntax, inspired by the Wireshark Display Filter language, is the -same syntax used in custom Firewall Rules. Cloudflare uses the Ruleset Engine -in different products, allowing you to configure several products using the same -basic syntax. +Cloudflare uses the Ruleset Engine in different products, allowing +you to configure several products using the same basic syntax. ## Example Usage @@ -26,47 +22,51 @@ basic syntax. # Magic Transit resource "cloudflare_ruleset" "magic_transit_example" { account_id = "f037e56e89293a057740de681ac9abbe" - name = "account magic transit" - description = "example magic transit ruleset description" - kind = "root" + name = "My example Magic Transit ruleset" + description = "My example Magic Transit ruleset description" phase = "magic_transit" + kind = "root" rules { - action = "allow" - expression = "tcp.dstport in { 32768..65535 }" + ref = "allow_tcp_ephemeral_ports" description = "Allow TCP Ephemeral Ports" + expression = "tcp.dstport in { 32768..65535 }" + action = "allow" } } # Zone-level WAF Managed Ruleset resource "cloudflare_ruleset" "zone_level_managed_waf" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "managed WAF" - description = "managed WAF ruleset description" - kind = "zone" + name = "My example managed WAF ruleset" + description = "My example managed WAF ruleset description" phase = "http_request_firewall_managed" + kind = "zone" rules { - action = "execute" + ref = "execute_managed_ruleset" + description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset" + expression = "(http.host eq \"example.host.com\")" + action = "execute" action_parameters { id = "efb7b8c949ac4650a09736fc376e9aee" } - expression = "(http.host eq \"example.host.com\")" - description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset" - enabled = true } } # Zone-level WAF with tag-based overrides resource "cloudflare_ruleset" "zone_level_managed_waf_with_category_based_overrides" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "managed WAF with tag-based overrides" - description = "managed WAF with tag-based overrides ruleset description" - kind = "zone" + name = "My example managed WAF ruleset with tag-based overrides" + description = "My example managed WAF ruleset with tag-based overrides ruleset description" phase = "http_request_firewall_managed" + kind = "zone" rules { - action = "execute" + ref = "execute_managed_ruleset" + description = "Execute Cloudflare Managed Ruleset with overrides to change Wordpress rules to block" + expression = "(http.host eq \"example.host.com\")" + action = "execute" action_parameters { id = "efb7b8c949ac4650a09736fc376e9aee" overrides { @@ -83,23 +83,23 @@ resource "cloudflare_ruleset" "zone_level_managed_waf_with_category_based_overri } } } - - expression = "(http.host eq \"example.host.com\")" - description = "overrides to only enable wordpress rules to block" - enabled = false + enabled = false } } # Rewrite the URI path component to a static path resource "cloudflare_ruleset" "transform_uri_rule_path" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for URI path" - description = "change the URI path to a new static path" - kind = "zone" + name = "My example transform ruleset" + description = "My example transform ruleset description" phase = "http_request_transform" + kind = "zone" rules { - action = "rewrite" + ref = "transform_old_path" + description = "Transform old path" + expression = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-path\")" + action = "rewrite" action_parameters { uri { path { @@ -107,23 +107,22 @@ resource "cloudflare_ruleset" "transform_uri_rule_path" { } } } - - expression = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-path\")" - description = "example URI path transform rule" - enabled = true } } # Rewrite the URI query component to a static query resource "cloudflare_ruleset" "transform_uri_rule_query" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for URI query parameter" - description = "change the URI query to a new static query" - kind = "zone" + name = "My example transform ruleset" + description = "My example transform ruleset description" phase = "http_request_transform" + kind = "zone" rules { - action = "rewrite" + ref = "transform_uri_query_parameter" + description = "Transform URI query parameter" + expression = "(http.host eq \"example.host.com\")" + action = "rewrite" action_parameters { uri { query { @@ -131,23 +130,22 @@ resource "cloudflare_ruleset" "transform_uri_rule_query" { } } } - - expression = "(http.host eq \"example.host.com\")" - description = "URI transformation query example" - enabled = true } } # Rewrite HTTP headers to a modified values resource "cloudflare_ruleset" "transform_uri_http_headers" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for HTTP headers" - description = "modify HTTP headers before reaching origin" + name = "My example transform ruleset" + description = "My example transform ruleset description" + phase = "http_request_transform" kind = "zone" - phase = "http_request_late_transform" rules { - action = "rewrite" + ref = "transform_request_headers" + description = "Transform request headers" + expression = "(http.host eq \"example.host.com\")" + action = "rewrite" action_parameters { headers { name = "example-http-header-1" @@ -166,23 +164,22 @@ resource "cloudflare_ruleset" "transform_uri_http_headers" { operation = "remove" } } - - expression = "(http.host eq \"example.host.com\")" - description = "example request header transform rule" - enabled = false } } # HTTP rate limit for an API route resource "cloudflare_ruleset" "rate_limiting_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "restrict API requests count" - description = "apply HTTP rate limiting for a route" - kind = "zone" + name = "My example rate limit ruleset" + description = "My example rate limit ruleset description" phase = "http_ratelimit" + kind = "zone" rules { - action = "block" + ref = "rate_limit_api_requests" + description = "Rate limit API requests" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "block" ratelimit { characteristics = [ "cf.colo.id", @@ -192,23 +189,22 @@ resource "cloudflare_ruleset" "rate_limiting_example" { requests_per_period = 100 mitigation_timeout = 600 } - - expression = "(http.request.uri.path matches \"^/api/\")" - description = "rate limit for API" - enabled = true } } # Change origin for an API route resource "cloudflare_ruleset" "http_origin_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Change to some origin" - description = "Change origin for a route" - kind = "zone" + name = "My example origin ruleset" + description = "My example origin ruleset description" phase = "http_request_origin" + kind = "zone" rules { - action = "route" + ref = "change_origin" + description = "Change to some.host" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "route" action_parameters { host_header = "some.host" origin { @@ -216,22 +212,22 @@ resource "cloudflare_ruleset" "http_origin_example" { port = 80 } } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "change origin to some.host" - enabled = true } } # Custom fields logging resource "cloudflare_ruleset" "custom_fields_logging_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "log custom fields" - description = "add custom fields to logging" - kind = "zone" + name = "My example log custom field ruleset" + description = "My example log custom field ruleset description" phase = "http_log_custom_fields" + kind = "zone" rules { - action = "log_custom_field" + ref = "log_custom_fields" + description = "Log custom fields" + expression = "(http.host eq \"example.host.com\")" + action = "log_custom_field" action_parameters { request_fields = [ "content-type", @@ -249,23 +245,22 @@ resource "cloudflare_ruleset" "custom_fields_logging_example" { "__cfruid" ] } - - expression = "(http.host eq \"example.host.com\")" - description = "log custom fields rule" - enabled = true } } -# Custom cache keys + settings +# Custom cache keys and settings resource "cloudflare_ruleset" "cache_settings_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "set cache settings" - description = "set cache settings for the request" - kind = "zone" + name = "My example cache settings ruleset" + description = "My example cache settings ruleset description" phase = "http_request_cache_settings" + kind = "zone" rules { - action = "set_cache_settings" + ref = "cache_settings" + description = "Set cache settings rule" + expression = "(http.host eq \"example.host.com\")" + action = "set_cache_settings" action_parameters { edge_ttl { mode = "override_origin" @@ -325,44 +320,44 @@ resource "cloudflare_ruleset" "cache_settings_example" { } origin_error_page_passthru = false } - expression = "(http.host eq \"example.host.com\")" - description = "set cache settings rule" - enabled = true } } # Redirects based on a List resource resource "cloudflare_ruleset" "redirect_from_list_example" { account_id = "f037e56e89293a057740de681ac9abbe" - name = "redirects" - description = "Redirect ruleset" - kind = "root" + name = "My example redirect ruleset" + description = "My example redirect ruleset description" phase = "http_request_redirect" + kind = "root" rules { - action = "redirect" + ref = "redirects_from_list" + description = "Apply redirects from redirect_list" + expression = "http.request.full_uri in $redirect_list" + action = "redirect" action_parameters { from_list { name = "redirect_list" key = "http.request.full_uri" } } - expression = "http.request.full_uri in $redirect_list" - description = "Apply redirects from redirect_list" - enabled = true } } # Dynamic Redirects from value resource resource "cloudflare_ruleset" "redirect_from_value_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "redirects" - description = "Redirect ruleset" - kind = "zone" + name = "My example dynamic redirect ruleset" + description = "My example dynamic redirect ruleset description" phase = "http_request_dynamic_redirect" + kind = "zone" rules { - action = "redirect" + ref = "redirect_from_value" + description = "Apply redirect from value" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "redirect" action_parameters { from_value { status_code = 301 @@ -372,62 +367,63 @@ resource "cloudflare_ruleset" "redirect_from_value_example" { preserve_query_string = true } } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "Apply redirect from value" - enabled = true } } # Serve some custom error response resource "cloudflare_ruleset" "http_custom_error_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Serve some error response" - description = "Serve some error response" - kind = "zone" + name = "My example custom errors ruleset" + description = "My example custom errors ruleset description" phase = "http_custom_errors" + kind = "zone" + rules { - action = "serve_error" + ref = "serve_some_error_response" + description = "Serve some error response" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "serve_error" action_parameters { content = "some error html" content_type = "text/html" status_code = "530" } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "serve some error response" - enabled = true } } # Set Configuration Rules for an API route resource "cloudflare_ruleset" "http_config_rules_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "set config rules" - description = "set config rules for request" - kind = "zone" + name = "My example config settings ruleset" + description = "My example config settings ruleset description" phase = "http_config_settings" + kind = "zone" rules { - action = "set_config" + ref = "set_config_settings" + description = "Set config settings" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "set_config" action_parameters { email_obfuscation = true bic = true } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "set config rules for matching request" - enabled = true } } -# Set compress algorithm for response. +# Set compress algorithm for response resource "cloudflare_ruleset" "response_compress_brotli_html" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Brotli response compression for HTML" - description = "Response compression ruleset" - kind = "zone" + name = "My example response compression ruleset" + description = "My example response compression description" phase = "http_response_compression" + kind = "zone" rules { - action = "compress_response" + ref = "prefer_brotli_for_html" + description = "Prefer Brotli compression for HTML" + expression = "http.response.content_type.media_type == \"text/html\"" + action = "compress_response" action_parameters { algorithms { name = "brotli" @@ -436,9 +432,6 @@ resource "cloudflare_ruleset" "response_compress_brotli_html" { name = "auto" } } - expression = "http.response.content_type.media_type == \"text/html\"" - description = "Prefer brotli compression for HTML" - enabled = true } } ``` @@ -483,8 +476,6 @@ Optional: Read-Only: - `id` (String) Unique rule identifier. -- `last_updated` (String) The most recent update to this rule. -- `version` (String) Version of the ruleset to deploy. ### Nested Schema for `rules.action_parameters` @@ -544,7 +535,6 @@ Optional: - `status_code` (Number) HTTP status code of the custom error response. - `sxg` (Boolean) Turn on or off the SXG feature. - `uri` (Block List) List of URI properties to configure for the ruleset rule when performing URL rewrite transformations. (see [below for nested schema](#nestedblock--rules--action_parameters--uri)) -- `version` (String) Version of the ruleset to deploy. ### Nested Schema for `rules.action_parameters.algorithms` diff --git a/docs/resources/snippet.md b/docs/resources/snippet.md new file mode 100644 index 0000000000..379fe1bb37 --- /dev/null +++ b/docs/resources/snippet.md @@ -0,0 +1,37 @@ +--- +page_title: "cloudflare_snippet Resource - Cloudflare" +subcategory: "" +description: |- + The Snippet https://developers.cloudflare.com/rules/snippets/ resource allows you to create and manage snippet for a zone. +--- + +# cloudflare_snippet (Resource) + +The [Snippet](https://developers.cloudflare.com/rules/snippets/) resource allows you to create and manage snippet for a zone. + + + +## Schema + +### Required + +- `main_module` (String) Main module file name of the snippet. +- `name` (String) Name of the snippet. +- `zone_id` (String) The zone identifier to target for the resource. + +### Optional + +- `files` (Block Set) List of Snippet Files (see [below for nested schema](#nestedblock--files)) + + +### Nested Schema for `files` + +Required: + +- `name` (String) Name of the snippet file. + +Optional: + +- `content` (String) Content of the snippet file. + + diff --git a/docs/resources/snippet_rules.md b/docs/resources/snippet_rules.md new file mode 100644 index 0000000000..dbf1dd6723 --- /dev/null +++ b/docs/resources/snippet_rules.md @@ -0,0 +1,37 @@ +--- +page_title: "cloudflare_snippet_rules Resource - Cloudflare" +subcategory: "" +description: |- + The Snippet Rules https://developers.cloudflare.com/rules/snippets/ resource allows you to create and manage snippet rules for a zone. +--- + +# cloudflare_snippet_rules (Resource) + +The [Snippet Rules](https://developers.cloudflare.com/rules/snippets/) resource allows you to create and manage snippet rules for a zone. + + + +## Schema + +### Required + +- `zone_id` (String) The zone identifier to target for the resource. + +### Optional + +- `rules` (Block Set) List of Snippet Rules (see [below for nested schema](#nestedblock--rules)) + + +### Nested Schema for `rules` + +Required: + +- `expression` (String) Criteria for an HTTP request to trigger the snippet rule. Uses the Firewall Rules expression language based on Wireshark display filters. +- `snippet_name` (String) Name of the snippet invoked by this rule. + +Optional: + +- `description` (String) Brief summary of the snippet rule and its intended use. +- `enabled` (Boolean) Whether the headers rule is active. + + diff --git a/docs/resources/zero_trust_access_identity_provider.md b/docs/resources/zero_trust_access_identity_provider.md index 53d328b43d..506ea1a68a 100644 --- a/docs/resources/zero_trust_access_identity_provider.md +++ b/docs/resources/zero_trust_access_identity_provider.md @@ -128,12 +128,12 @@ Read-Only: Optional: -- `enabled` (Boolean) -- `group_member_deprovision` (Boolean) -- `identity_update_behavior` (String) -- `seat_deprovision` (Boolean) -- `secret` (String, Sensitive) -- `user_deprovision` (Boolean) +- `enabled` (Boolean) A flag to enable or disable SCIM for the identity provider. +- `group_member_deprovision` (Boolean) Deprecated. Use `identity_update_behavior`. +- `identity_update_behavior` (String) Indicates how a SCIM event updates a user identity used for policy evaluation. Use "automatic" to automatically update a user's identity and augment it with fields from the SCIM user resource. Use "reauth" to force re-authentication on group membership updates, user identity update will only occur after successful re-authentication. With "reauth" identities will not contain fields from the SCIM user resource. With "no_action" identities will not be changed by SCIM updates in any way and users will not be prompted to reauthenticate. +- `seat_deprovision` (Boolean) A flag to remove a user's seat in Zero Trust when they have been deprovisioned in the Identity Provider. This cannot be enabled unless user_deprovision is also enabled. +- `secret` (String, Sensitive) A read-only token generated when the SCIM integration is enabled for the first time. It is redacted on subsequent requests. If you lose this you will need to refresh it token at /access/identity_providers/:idpID/refresh_scim_secret. +- `user_deprovision` (Boolean) A flag to enable revoking a user's session in Access and Gateway when they have been deprovisioned in the Identity Provider. ## Import diff --git a/docs/resources/zero_trust_access_policy.md b/docs/resources/zero_trust_access_policy.md index 6b370b963a..a700d9074c 100644 --- a/docs/resources/zero_trust_access_policy.md +++ b/docs/resources/zero_trust_access_policy.md @@ -206,6 +206,9 @@ Required: Required: - `usernames` (List of String) Contains the Unix usernames that may be used when connecting over SSH. + +Optional: + - `allow_email_alias` (Boolean) Allows connecting to Unix username that matches the authenticating email prefix. diff --git a/examples/resources/cloudflare_ruleset/resource.tf b/examples/resources/cloudflare_ruleset/resource.tf index fbed325d81..10aa6ff169 100644 --- a/examples/resources/cloudflare_ruleset/resource.tf +++ b/examples/resources/cloudflare_ruleset/resource.tf @@ -1,47 +1,51 @@ # Magic Transit resource "cloudflare_ruleset" "magic_transit_example" { account_id = "f037e56e89293a057740de681ac9abbe" - name = "account magic transit" - description = "example magic transit ruleset description" - kind = "root" + name = "My example Magic Transit ruleset" + description = "My example Magic Transit ruleset description" phase = "magic_transit" + kind = "root" rules { - action = "allow" - expression = "tcp.dstport in { 32768..65535 }" + ref = "allow_tcp_ephemeral_ports" description = "Allow TCP Ephemeral Ports" + expression = "tcp.dstport in { 32768..65535 }" + action = "allow" } } # Zone-level WAF Managed Ruleset resource "cloudflare_ruleset" "zone_level_managed_waf" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "managed WAF" - description = "managed WAF ruleset description" - kind = "zone" + name = "My example managed WAF ruleset" + description = "My example managed WAF ruleset description" phase = "http_request_firewall_managed" + kind = "zone" rules { - action = "execute" + ref = "execute_managed_ruleset" + description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset" + expression = "(http.host eq \"example.host.com\")" + action = "execute" action_parameters { id = "efb7b8c949ac4650a09736fc376e9aee" } - expression = "(http.host eq \"example.host.com\")" - description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset" - enabled = true } } # Zone-level WAF with tag-based overrides resource "cloudflare_ruleset" "zone_level_managed_waf_with_category_based_overrides" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "managed WAF with tag-based overrides" - description = "managed WAF with tag-based overrides ruleset description" - kind = "zone" + name = "My example managed WAF ruleset with tag-based overrides" + description = "My example managed WAF ruleset with tag-based overrides ruleset description" phase = "http_request_firewall_managed" + kind = "zone" rules { - action = "execute" + ref = "execute_managed_ruleset" + description = "Execute Cloudflare Managed Ruleset with overrides to change Wordpress rules to block" + expression = "(http.host eq \"example.host.com\")" + action = "execute" action_parameters { id = "efb7b8c949ac4650a09736fc376e9aee" overrides { @@ -58,23 +62,23 @@ resource "cloudflare_ruleset" "zone_level_managed_waf_with_category_based_overri } } } - - expression = "(http.host eq \"example.host.com\")" - description = "overrides to only enable wordpress rules to block" - enabled = false + enabled = false } } # Rewrite the URI path component to a static path resource "cloudflare_ruleset" "transform_uri_rule_path" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for URI path" - description = "change the URI path to a new static path" - kind = "zone" + name = "My example transform ruleset" + description = "My example transform ruleset description" phase = "http_request_transform" + kind = "zone" rules { - action = "rewrite" + ref = "transform_old_path" + description = "Transform old path" + expression = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-path\")" + action = "rewrite" action_parameters { uri { path { @@ -82,23 +86,22 @@ resource "cloudflare_ruleset" "transform_uri_rule_path" { } } } - - expression = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-path\")" - description = "example URI path transform rule" - enabled = true } } # Rewrite the URI query component to a static query resource "cloudflare_ruleset" "transform_uri_rule_query" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for URI query parameter" - description = "change the URI query to a new static query" - kind = "zone" + name = "My example transform ruleset" + description = "My example transform ruleset description" phase = "http_request_transform" + kind = "zone" rules { - action = "rewrite" + ref = "transform_uri_query_parameter" + description = "Transform URI query parameter" + expression = "(http.host eq \"example.host.com\")" + action = "rewrite" action_parameters { uri { query { @@ -106,23 +109,22 @@ resource "cloudflare_ruleset" "transform_uri_rule_query" { } } } - - expression = "(http.host eq \"example.host.com\")" - description = "URI transformation query example" - enabled = true } } # Rewrite HTTP headers to a modified values resource "cloudflare_ruleset" "transform_uri_http_headers" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "transform rule for HTTP headers" - description = "modify HTTP headers before reaching origin" + name = "My example transform ruleset" + description = "My example transform ruleset description" + phase = "http_request_transform" kind = "zone" - phase = "http_request_late_transform" rules { - action = "rewrite" + ref = "transform_request_headers" + description = "Transform request headers" + expression = "(http.host eq \"example.host.com\")" + action = "rewrite" action_parameters { headers { name = "example-http-header-1" @@ -141,23 +143,22 @@ resource "cloudflare_ruleset" "transform_uri_http_headers" { operation = "remove" } } - - expression = "(http.host eq \"example.host.com\")" - description = "example request header transform rule" - enabled = false } } # HTTP rate limit for an API route resource "cloudflare_ruleset" "rate_limiting_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "restrict API requests count" - description = "apply HTTP rate limiting for a route" - kind = "zone" + name = "My example rate limit ruleset" + description = "My example rate limit ruleset description" phase = "http_ratelimit" + kind = "zone" rules { - action = "block" + ref = "rate_limit_api_requests" + description = "Rate limit API requests" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "block" ratelimit { characteristics = [ "cf.colo.id", @@ -167,23 +168,22 @@ resource "cloudflare_ruleset" "rate_limiting_example" { requests_per_period = 100 mitigation_timeout = 600 } - - expression = "(http.request.uri.path matches \"^/api/\")" - description = "rate limit for API" - enabled = true } } # Change origin for an API route resource "cloudflare_ruleset" "http_origin_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Change to some origin" - description = "Change origin for a route" - kind = "zone" + name = "My example origin ruleset" + description = "My example origin ruleset description" phase = "http_request_origin" + kind = "zone" rules { - action = "route" + ref = "change_origin" + description = "Change to some.host" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "route" action_parameters { host_header = "some.host" origin { @@ -191,22 +191,22 @@ resource "cloudflare_ruleset" "http_origin_example" { port = 80 } } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "change origin to some.host" - enabled = true } } # Custom fields logging resource "cloudflare_ruleset" "custom_fields_logging_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "log custom fields" - description = "add custom fields to logging" - kind = "zone" + name = "My example log custom field ruleset" + description = "My example log custom field ruleset description" phase = "http_log_custom_fields" + kind = "zone" rules { - action = "log_custom_field" + ref = "log_custom_fields" + description = "Log custom fields" + expression = "(http.host eq \"example.host.com\")" + action = "log_custom_field" action_parameters { request_fields = [ "content-type", @@ -224,23 +224,22 @@ resource "cloudflare_ruleset" "custom_fields_logging_example" { "__cfruid" ] } - - expression = "(http.host eq \"example.host.com\")" - description = "log custom fields rule" - enabled = true } } -# Custom cache keys + settings +# Custom cache keys and settings resource "cloudflare_ruleset" "cache_settings_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "set cache settings" - description = "set cache settings for the request" - kind = "zone" + name = "My example cache settings ruleset" + description = "My example cache settings ruleset description" phase = "http_request_cache_settings" + kind = "zone" rules { - action = "set_cache_settings" + ref = "cache_settings" + description = "Set cache settings rule" + expression = "(http.host eq \"example.host.com\")" + action = "set_cache_settings" action_parameters { edge_ttl { mode = "override_origin" @@ -300,44 +299,44 @@ resource "cloudflare_ruleset" "cache_settings_example" { } origin_error_page_passthru = false } - expression = "(http.host eq \"example.host.com\")" - description = "set cache settings rule" - enabled = true } } # Redirects based on a List resource resource "cloudflare_ruleset" "redirect_from_list_example" { account_id = "f037e56e89293a057740de681ac9abbe" - name = "redirects" - description = "Redirect ruleset" - kind = "root" + name = "My example redirect ruleset" + description = "My example redirect ruleset description" phase = "http_request_redirect" + kind = "root" rules { - action = "redirect" + ref = "redirects_from_list" + description = "Apply redirects from redirect_list" + expression = "http.request.full_uri in $redirect_list" + action = "redirect" action_parameters { from_list { name = "redirect_list" key = "http.request.full_uri" } } - expression = "http.request.full_uri in $redirect_list" - description = "Apply redirects from redirect_list" - enabled = true } } # Dynamic Redirects from value resource resource "cloudflare_ruleset" "redirect_from_value_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "redirects" - description = "Redirect ruleset" - kind = "zone" + name = "My example dynamic redirect ruleset" + description = "My example dynamic redirect ruleset description" phase = "http_request_dynamic_redirect" + kind = "zone" rules { - action = "redirect" + ref = "redirect_from_value" + description = "Apply redirect from value" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "redirect" action_parameters { from_value { status_code = 301 @@ -347,62 +346,63 @@ resource "cloudflare_ruleset" "redirect_from_value_example" { preserve_query_string = true } } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "Apply redirect from value" - enabled = true } } # Serve some custom error response resource "cloudflare_ruleset" "http_custom_error_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Serve some error response" - description = "Serve some error response" - kind = "zone" + name = "My example custom errors ruleset" + description = "My example custom errors ruleset description" phase = "http_custom_errors" + kind = "zone" + rules { - action = "serve_error" + ref = "serve_some_error_response" + description = "Serve some error response" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "serve_error" action_parameters { content = "some error html" content_type = "text/html" status_code = "530" } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "serve some error response" - enabled = true } } # Set Configuration Rules for an API route resource "cloudflare_ruleset" "http_config_rules_example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "set config rules" - description = "set config rules for request" - kind = "zone" + name = "My example config settings ruleset" + description = "My example config settings ruleset description" phase = "http_config_settings" + kind = "zone" rules { - action = "set_config" + ref = "set_config_settings" + description = "Set config settings" + expression = "(http.request.uri.path matches \"^/api/\")" + action = "set_config" action_parameters { email_obfuscation = true bic = true } - expression = "(http.request.uri.path matches \"^/api/\")" - description = "set config rules for matching request" - enabled = true } } -# Set compress algorithm for response. +# Set compress algorithm for response resource "cloudflare_ruleset" "response_compress_brotli_html" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" - name = "Brotli response compression for HTML" - description = "Response compression ruleset" - kind = "zone" + name = "My example response compression ruleset" + description = "My example response compression description" phase = "http_response_compression" + kind = "zone" rules { - action = "compress_response" + ref = "prefer_brotli_for_html" + description = "Prefer Brotli compression for HTML" + expression = "http.response.content_type.media_type == \"text/html\"" + action = "compress_response" action_parameters { algorithms { name = "brotli" @@ -411,8 +411,5 @@ resource "cloudflare_ruleset" "response_compress_brotli_html" { name = "auto" } } - expression = "http.response.content_type.media_type == \"text/html\"" - description = "Prefer brotli compression for HTML" - enabled = true } } diff --git a/go.mod b/go.mod index 5e7cef3553..a49e85ddbf 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.3 require ( github.com/agext/levenshtein v1.2.3 // indirect - github.com/cloudflare/cloudflare-go v0.111.0 + github.com/cloudflare/cloudflare-go v0.112.0 github.com/fatih/color v1.16.0 // indirect github.com/google/uuid v1.6.0 github.com/hashicorp/errwrap v1.1.0 // indirect @@ -24,8 +24,8 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/pkg/errors v0.9.1 github.com/zclconf/go-cty v1.15.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.32.0 + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect @@ -41,10 +41,11 @@ require ( github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-framework v1.13.0 - github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-mux v0.17.0 github.com/hashicorp/terraform-plugin-testing v1.11.0 + github.com/jinzhu/copier v0.4.0 github.com/stretchr/testify v1.10.0 ) @@ -67,7 +68,7 @@ require ( github.com/aws/smithy-go v1.21.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect diff --git a/go.sum b/go.sum index 3d782cecd6..3c000b46b8 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cloudflare/cloudflare-go v0.111.0 h1:bFgl5OyR7iaV9DkTaoI2jU8X4rXDzEaFDaPfMTp+Ewo= -github.com/cloudflare/cloudflare-go v0.111.0/go.mod h1:w5c4Vm00JjZM+W0mPi6QOC+eWLncGQPURtgDck3z5xU= +github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g= +github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0= github.com/cloudflare/cloudflare-go/v2 v2.4.0 h1:gys/26GoVDklgfq8NYV39WgvOEwzK/XAqYObmnI6iFg= github.com/cloudflare/cloudflare-go/v2 v2.4.0/go.mod h1:AoIzb05z/rvdJLztPct4tSa+3IqXJJ6c+pbUFMOlTr8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -74,8 +74,8 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -127,8 +127,8 @@ github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2 github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= -github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= -github.com/hashicorp/terraform-plugin-framework-validators v0.15.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 h1:O9QqGoYDzQT7lwTXUsZEtgabeWW96zUBh47Smn2lkFA= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY= github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= @@ -149,6 +149,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -221,8 +223,8 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= @@ -230,8 +232,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/framework/service/cloud_connector_rules/schema.go b/internal/framework/service/cloud_connector_rules/schema.go index f5f27a3aee..c55340e96d 100644 --- a/internal/framework/service/cloud_connector_rules/schema.go +++ b/internal/framework/service/cloud_connector_rules/schema.go @@ -19,7 +19,7 @@ import ( func (r *CloudConnectorRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: heredoc.Doc(` - The [Cloud Connector Rules](add link to doc) resource allows you to create and manage cloud connector rules for a zone. + The [Cloud Connector Rules](https://developers.cloudflare.com/rules/cloud-connector/) resource allows you to create and manage cloud connector rules for a zone. `), Version: 1, diff --git a/internal/framework/service/leaked_credential_check_rule/resource.go b/internal/framework/service/leaked_credential_check_rule/resource.go index 454fc00701..1e90cf7625 100644 --- a/internal/framework/service/leaked_credential_check_rule/resource.go +++ b/internal/framework/service/leaked_credential_check_rule/resource.go @@ -113,6 +113,14 @@ func (r *LeakedCredentialCheckRuleResource) Update(ctx context.Context, req reso if resp.Diagnostics.HasError() { return } + var state LeakedCredentialCheckRulesModel + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + data.ID = state.ID + zoneID := cloudflare.ZoneIdentifier(data.ZoneID.ValueString()) _, err := r.client.V1.LeakedCredentialCheckUpdateDetection(ctx, zoneID, cloudflare.LeakedCredentialCheckUpdateDetectionParams{ LeakedCredentialCheckDetectionEntry: cloudflare.LeakedCredentialCheckDetectionEntry{ diff --git a/internal/framework/service/leaked_credential_check_rule/resource_test.go b/internal/framework/service/leaked_credential_check_rule/resource_test.go index 62714a892d..14643192bf 100644 --- a/internal/framework/service/leaked_credential_check_rule/resource_test.go +++ b/internal/framework/service/leaked_credential_check_rule/resource_test.go @@ -46,7 +46,7 @@ func testSweepCloudflareLCCRules(r string) error { tflog.Error(ctx, fmt.Sprintf("Error deleting a user-defined detection patter for Leaked Credential Check: %s", err)) } } - + return nil } @@ -68,6 +68,18 @@ func TestAccCloudflareLeakedCredentialCheckRule_Basic(t *testing.T) { resource.TestCheckResourceAttr(name+"_first", "username", "lookup_json_string(http.request.body.raw, \"user\")"), resource.TestCheckResourceAttr(name+"_first", "password", "lookup_json_string(http.request.body.raw, \"pass\")"), + resource.TestCheckResourceAttr(name+"_second", "zone_id", zoneID), + resource.TestCheckResourceAttr(name+"_second", "username", "lookup_json_string(http.request.body.raw, \"id\")"), + resource.TestCheckResourceAttr(name+"_second", "password", "lookup_json_string(http.request.body.raw, \"secret\")"), + ), + }, + { + Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCUpdateOneRule(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name+"_first", "zone_id", zoneID), + resource.TestCheckResourceAttr(name+"_first", "username", "lookup_json_string(http.request.body.raw, \"username\")"), + resource.TestCheckResourceAttr(name+"_first", "password", "lookup_json_string(http.request.body.raw, \"password\")"), + resource.TestCheckResourceAttr(name+"_second", "zone_id", zoneID), resource.TestCheckResourceAttr(name+"_second", "username", "lookup_json_string(http.request.body.raw, \"id\")"), resource.TestCheckResourceAttr(name+"_second", "password", "lookup_json_string(http.request.body.raw, \"secret\")"), @@ -100,3 +112,18 @@ func testAccLCCTwoSimpleRules(name string) string { password = "lookup_json_string(http.request.body.raw, \"secret\")" }`, name) } + +func testAccLCCUpdateOneRule(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rule" "%[1]s_first" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + username = "lookup_json_string(http.request.body.raw, \"username\")" + password = "lookup_json_string(http.request.body.raw, \"password\")" + } + + resource "cloudflare_leaked_credential_check_rule" "%[1]s_second" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + }`, name) +} diff --git a/internal/framework/service/rulesets/migrate.go b/internal/framework/service/rulesets/migrate.go new file mode 100644 index 0000000000..0ca3ba0916 --- /dev/null +++ b/internal/framework/service/rulesets/migrate.go @@ -0,0 +1,1123 @@ +package rulesets + +import ( + "context" + "fmt" + + cfv1 "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/modifiers/defaults" + "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jinzhu/copier" +) + +type RulesetResourceModelV0 struct { + AccountID types.String `tfsdk:"account_id"` + Description types.String `tfsdk:"description"` + ID types.String `tfsdk:"id"` + Kind types.String `tfsdk:"kind"` + Name types.String `tfsdk:"name"` + Phase types.String `tfsdk:"phase"` + Rules []*RulesModelV0 `tfsdk:"rules"` + ZoneID types.String `tfsdk:"zone_id"` +} + +type RulesModelV0 struct { + Version types.String `tfsdk:"version"` + Action types.String `tfsdk:"action"` + ActionParameters []*ActionParametersModelV0 `tfsdk:"action_parameters"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + ExposedCredentialCheck []*ExposedCredentialCheckModel `tfsdk:"exposed_credential_check"` + Expression types.String `tfsdk:"expression"` + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` + Logging []*LoggingModel `tfsdk:"logging"` + Ratelimit []*RatelimitModel `tfsdk:"ratelimit"` + Ref types.String `tfsdk:"ref"` +} + +type ActionParametersModelV0 struct { + Version types.String `tfsdk:"version"` + AdditionalCacheablePorts types.Set `tfsdk:"additional_cacheable_ports"` + AutomaticHTTPSRewrites types.Bool `tfsdk:"automatic_https_rewrites"` + AutoMinify []*ActionParameterAutoMinifyModel `tfsdk:"autominify"` + BIC types.Bool `tfsdk:"bic"` + BrowserTTL []*ActionParameterBrowserTTLModel `tfsdk:"browser_ttl"` + Cache types.Bool `tfsdk:"cache"` + CacheKey []*ActionParameterCacheKeyModel `tfsdk:"cache_key"` + CacheReserve []*ActionParameterCacheReserveModel `tfsdk:"cache_reserve"` + Content types.String `tfsdk:"content"` + ContentType types.String `tfsdk:"content_type"` + CookieFields types.Set `tfsdk:"cookie_fields"` + DisableApps types.Bool `tfsdk:"disable_apps"` + DisableRailgun types.Bool `tfsdk:"disable_railgun"` + DisableRUM types.Bool `tfsdk:"disable_rum"` + DisableZaraz types.Bool `tfsdk:"disable_zaraz"` + EdgeTTL []*ActionParameterEdgeTTLModel `tfsdk:"edge_ttl"` + Fonts types.Bool `tfsdk:"fonts"` + EmailObfuscation types.Bool `tfsdk:"email_obfuscation"` + FromList []*ActionParameterFromListModel `tfsdk:"from_list"` + FromValue []*ActionParameterFromValueModel `tfsdk:"from_value"` + Headers []*ActionParametersHeadersModel `tfsdk:"headers"` + HostHeader types.String `tfsdk:"host_header"` + HotlinkProtection types.Bool `tfsdk:"hotlink_protection"` + ID types.String `tfsdk:"id"` + Increment types.Int64 `tfsdk:"increment"` + MatchedData []*ActionParametersMatchedDataModel `tfsdk:"matched_data"` + Mirage types.Bool `tfsdk:"mirage"` + OpportunisticEncryption types.Bool `tfsdk:"opportunistic_encryption"` + Origin []*ActionParameterOriginModel `tfsdk:"origin"` + OriginCacheControl types.Bool `tfsdk:"origin_cache_control"` + OriginErrorPagePassthru types.Bool `tfsdk:"origin_error_page_passthru"` + Overrides []*ActionParameterOverridesModel `tfsdk:"overrides"` + Phases types.Set `tfsdk:"phases"` + Polish types.String `tfsdk:"polish"` + Products types.Set `tfsdk:"products"` + ReadTimeout types.Int64 `tfsdk:"read_timeout"` + RequestFields types.Set `tfsdk:"request_fields"` + RespectStrongEtags types.Bool `tfsdk:"respect_strong_etags"` + Response []*ActionParameterResponseModel `tfsdk:"response"` + ResponseFields types.Set `tfsdk:"response_fields"` + RocketLoader types.Bool `tfsdk:"rocket_loader"` + Rules map[string]types.String `tfsdk:"rules"` + Ruleset types.String `tfsdk:"ruleset"` + Rulesets types.Set `tfsdk:"rulesets"` + SecurityLevel types.String `tfsdk:"security_level"` + ServerSideExcludes types.Bool `tfsdk:"server_side_excludes"` + ServeStale []*ActionParameterServeStaleModel `tfsdk:"serve_stale"` + SNI []*ActionParameterSNIModel `tfsdk:"sni"` + SSL types.String `tfsdk:"ssl"` + StatusCode types.Int64 `tfsdk:"status_code"` + SXG types.Bool `tfsdk:"sxg"` + URI []*ActionParametersURIModel `tfsdk:"uri"` + Algorithms []*ActionParametersCompressionAlgorithmModel `tfsdk:"algorithms"` +} + +func (r *RulesetResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Computed: true, + MarkdownDescription: consts.IDSchemaDescription, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + consts.AccountIDSchemaKey: schema.StringAttribute{ + MarkdownDescription: consts.AccountIDSchemaDescription, + Optional: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.Expression(path.MatchRoot(consts.ZoneIDSchemaKey)), + ), + }, + }, + consts.ZoneIDSchemaKey: schema.StringAttribute{ + MarkdownDescription: consts.ZoneIDSchemaDescription, + Optional: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.Expression(path.MatchRoot(consts.AccountIDSchemaKey)), + ), + }, + }, + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the ruleset.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Brief summary of the ruleset and its intended use.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Computed: true, + }, + "kind": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(cfv1.RulesetKindValues()...), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + MarkdownDescription: fmt.Sprintf("Type of Ruleset to create. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetKindValues())), + }, + "phase": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(cfv1.RulesetPhaseValues()...), + sbfmDeprecationWarningValidator{}, + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + MarkdownDescription: fmt.Sprintf("Point in the request/response lifecycle where the ruleset will be created. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetPhaseValues())), + }, + }, + Blocks: map[string]schema.Block{ + "rules": schema.ListNestedBlock{ + MarkdownDescription: "List of rules to apply to the ruleset.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique rule identifier.", + }, + "version": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Version of the ruleset to deploy.", + }, + "ref": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Rule reference.", + }, + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Whether the rule is active.", + PlanModifiers: []planmodifier.Bool{ + defaults.DefaultBool(true), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Brief summary of the ruleset rule and its intended use.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "expression": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Criteria for an HTTP request to trigger the ruleset rule action. Uses the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + }, + "action": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("Action to perform in the ruleset rule. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetRuleActionValues())), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(cfv1.RulesetRuleActionValues()...), + }, + Optional: true, + }, + "last_updated": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The most recent update to this rule.", + }, + }, + Blocks: map[string]schema.Block{ + "action_parameters": schema.ListNestedBlock{ + MarkdownDescription: "List of parameters that configure the behavior of the ruleset rule action.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "additional_cacheable_ports": schema.SetAttribute{ + ElementType: types.Int64Type, + Optional: true, + MarkdownDescription: "Specifies uncommon ports to allow cacheable assets to be served from.", + }, + "automatic_https_rewrites": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off Cloudflare Automatic HTTPS rewrites.", + }, + "bic": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Inspect the visitor's browser for headers commonly associated with spammers and certain bots.", + }, + "cache": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Whether to cache if expression matches.", + }, + "content": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Content of the custom error response.", + }, + "content_type": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Content-Type of the custom error response.", + }, + "cookie_fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of cookie values to include as part of custom fields logging.", + }, + "disable_apps": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn off all active Cloudflare Apps.", + }, + "disable_railgun": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn off railgun feature of the Cloudflare Speed app.", + }, + "disable_zaraz": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn off zaraz feature.", + }, + "disable_rum": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn off RUM feature.", + }, + "fonts": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Toggle fonts.", + }, + "email_obfuscation": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off the Cloudflare Email Obfuscation feature of the Cloudflare Scrape Shield app.", + }, + "host_header": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Host Header that request origin receives.", + }, + "hotlink_protection": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off the hotlink protection feature.", + }, + consts.IDSchemaKey: schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Identifier of the action parameter to modify.", + }, + "increment": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "", + }, + "mirage": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off Cloudflare Mirage of the Cloudflare Speed app.", + }, + "opportunistic_encryption": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off the Cloudflare Opportunistic Encryption feature of the Edge Certificates tab in the Cloudflare SSL/TLS app.", + }, + "origin_cache_control": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Enable or disable the use of a more compliant Cache Control parsing mechanism, enabled by default for most zones.", + }, + "phases": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: fmt.Sprintf("Point in the request/response lifecycle where the ruleset will be created. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetPhaseValues())), + }, + "polish": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Apply options from the Polish feature of the Cloudflare Speed app.", + }, + "products": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: fmt.Sprintf("Products to target with the actions. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetActionParameterProductValues())), + }, + "read_timeout": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Specifies a maximum timeout for reading content from an origin server.", + }, + "request_fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of request headers to include as part of custom fields logging, in lowercase.", + }, + "respect_strong_etags": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Respect strong ETags.", + }, + "response_fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of response headers to include as part of custom fields logging, in lowercase.", + }, + "rocket_loader": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off Cloudflare Rocket Loader in the Cloudflare Speed app.", + }, + "rules": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "Map of managed WAF rule ID to comma-delimited string of ruleset rule IDs. Example: `rules = { \"efb7b8c949ac4650a09736fc376e9aee\" = \"5de7edfa648c4d6891dc3e7f84534ffa,e3a567afc347477d9702d9047e97d760\" }`.", + }, + "ruleset": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Which ruleset ID to target.", + }, + "rulesets": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of managed WAF rule IDs to target. Only valid when the `\"action\"` is set to skip.", + }, + "security_level": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Control options for the Security Level feature from the Security app.", + }, + "server_side_excludes": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off the Server Side Excludes feature of the Cloudflare Scrape Shield app.", + }, + "ssl": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Control options for the SSL feature of the Edge Certificates tab in the Cloudflare SSL/TLS app.", + }, + "status_code": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "HTTP status code of the custom error response.", + }, + "sxg": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Turn on or off the SXG feature.", + }, + "origin_error_page_passthru": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Pass-through error page for origin.", + }, + "version": schema.StringAttribute{ + Computed: true, + Optional: true, + MarkdownDescription: "Version of the ruleset to deploy.", + }, + }, + Blocks: map[string]schema.Block{ + "algorithms": schema.ListNestedBlock{ + MarkdownDescription: "Compression algorithms to use in order of preference.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: fmt.Sprintf("Name of the compression algorithm to use. %s", utils.RenderAvailableDocumentationValuesStringSlice([]string{"zstd", "gzip", "brotli", "auto", "default", "none"})), + Validators: []validator.String{ + stringvalidator.OneOf("zstd", "gzip", "brotli", "auto", "default", "none"), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "uri": schema.ListNestedBlock{ + MarkdownDescription: "List of URI properties to configure for the ruleset rule when performing URL rewrite transformations.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "origin": schema.BoolAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "path": schema.ListNestedBlock{ + MarkdownDescription: "URI path configuration when performing a URL rewrite.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Static string value of the updated URI path or query string component.", + }, + "expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Expression that defines the updated (dynamic) value of the URI path or query string component. Uses the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "query": schema.ListNestedBlock{ + MarkdownDescription: "Query string configuration when performing a URL rewrite.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Static string value of the updated URI path or query string component.", + }, + "expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Expression that defines the updated (dynamic) value of the URI path or query string component. Uses the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "headers": schema.ListNestedBlock{ + MarkdownDescription: "List of HTTP header modifications to perform in the ruleset rule. Note: Headers are order dependent and must be provided sorted alphabetically ascending based on the `name` value.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Name of the HTTP request header to target.", + }, + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Static value to provide as the HTTP request header value.", + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("expression"))), + }, + }, + "expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Use a value dynamically determined by the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("value"))), + }, + }, + "operation": schema.StringAttribute{ + Optional: true, + MarkdownDescription: fmt.Sprintf("Action to perform on the HTTP request header. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetRuleActionParametersHTTPHeaderOperationValues())), + }, + }, + }, + }, + "matched_data": schema.ListNestedBlock{ + MarkdownDescription: "List of properties to configure WAF payload logging.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "public_key": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Public key to use within WAF Ruleset payload logging to view the HTTP request parameters. You can generate a public key [using the `matched-data-cli` command-line tool](https://developers.cloudflare.com/waf/managed-rulesets/payload-logging/command-line/generate-key-pair) or [in the Cloudflare dashboard](https://developers.cloudflare.com/waf/managed-rulesets/payload-logging/configure).", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "response": schema.ListNestedBlock{ + MarkdownDescription: "List of parameters that configure the response given to end users.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "status_code": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "HTTP status code to send in the response.", + }, + "content_type": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "HTTP content type to send in the response.", + }, + "content": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Body content to include in the response.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "autominify": schema.ListNestedBlock{ + MarkdownDescription: "Indicate which file extensions to minify automatically.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "html": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "HTML minification.", + }, + "css": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "CSS minification.", + }, + "js": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "JS minification.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "edge_ttl": schema.ListNestedBlock{ + MarkdownDescription: "List of edge TTL parameters to apply to the request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "mode": schema.StringAttribute{ + Required: true, + Validators: []validator.String{stringvalidator.OneOf("override_origin", "respect_origin", "bypass_by_default")}, + MarkdownDescription: fmt.Sprintf("Mode of the edge TTL. %s", utils.RenderAvailableDocumentationValuesStringSlice([]string{"override_origin", "respect_origin", "bypass_by_default"})), + }, + "default": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{int64validator.AtLeast(1)}, + MarkdownDescription: "Default edge TTL.", + }, + }, + Validators: []validator.Object{EdgeTTLValidator{}}, + Blocks: map[string]schema.Block{ + "status_code_ttl": schema.ListNestedBlock{ + MarkdownDescription: "Edge TTL for the status codes.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "status_code": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Status code for which the edge TTL is applied.", + Validators: []validator.Int64{ + int64validator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("status_code_range"))), + }, + }, + "value": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Status code edge TTL value.", + }, + }, + Blocks: map[string]schema.Block{ + "status_code_range": schema.ListNestedBlock{ + MarkdownDescription: "Status code range for which the edge TTL is applied.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "from": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "From status code.", + }, + "to": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "To status code.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("status_code"))), + }, + }, + }, + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "browser_ttl": schema.ListNestedBlock{ + MarkdownDescription: "List of browser TTL parameters to apply to the request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "mode": schema.StringAttribute{ + Required: true, + Validators: []validator.String{stringvalidator.OneOf("override_origin", "respect_origin", "bypass")}, + MarkdownDescription: fmt.Sprintf("Mode of the browser TTL. %s", utils.RenderAvailableDocumentationValuesStringSlice([]string{"override_origin", "respect_origin", "bypass"})), + }, + "default": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{int64validator.AtLeast(1)}, + MarkdownDescription: "Default browser TTL. This value is required when override_origin is set", + }, + }, + Validators: []validator.Object{BrowserTTLValidator{}}, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "serve_stale": schema.ListNestedBlock{ + MarkdownDescription: "List of serve stale parameters to apply to the request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "disable_stale_while_updating": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Disable stale while updating.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "cache_key": schema.ListNestedBlock{ + MarkdownDescription: "List of cache key parameters to apply to the request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "cache_by_device_type": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Cache by device type.", + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("custom_key").AtAnyListIndex().AtName("user"))), + }, + }, + "ignore_query_strings_order": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Ignore query strings order.", + }, + "cache_deception_armor": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Cache deception armor.", + }, + }, + Blocks: map[string]schema.Block{ + "custom_key": schema.ListNestedBlock{ + MarkdownDescription: "Custom key parameters for the request.", + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "query_string": schema.ListNestedBlock{ + MarkdownDescription: "Query string parameters for the custom key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "include": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of query string parameters to include in the custom key.", + Validators: []validator.Set{ + setvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("exclude"))), + }, + }, + "exclude": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of query string parameters to exclude from the custom key.", + Validators: []validator.Set{ + setvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("include"))), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "header": schema.ListNestedBlock{ + MarkdownDescription: "Header parameters for the custom key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "include": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of headers to include in the custom key.", + }, + "check_presence": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of headers to check for presence in the custom key.", + }, + "exclude_origin": schema.BoolAttribute{ + Computed: true, + Optional: true, + MarkdownDescription: "Exclude the origin header from the custom key.", + PlanModifiers: []planmodifier.Bool{ + defaults.DefaultBool(false), + }, + }, + "contains": schema.MapAttribute{ + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Optional: true, + MarkdownDescription: "Dictionary of headers mapping to lists of values to check for presence in the custom key.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "cookie": schema.ListNestedBlock{ + MarkdownDescription: "Cookie parameters for the custom key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "include": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of cookies to include in the custom key.", + }, + "check_presence": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of cookies to check for presence in the custom key.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "user": schema.ListNestedBlock{ + MarkdownDescription: "User parameters for the custom key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "device_type": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Add device type to the custom key.", + }, + "geo": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Add geo data to the custom key.", + }, + "lang": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Add language data to the custom key.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtParent().AtParent().AtName("cache_by_device_type"))), + }, + }, + "host": schema.ListNestedBlock{ + MarkdownDescription: "Host parameters for the custom key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "resolved": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Resolve hostname to IP address.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "cache_reserve": schema.ListNestedBlock{ + MarkdownDescription: "List of cache reserve parameters to apply to the request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "eligible": schema.BoolAttribute{ + Required: true, + MarkdownDescription: "Determines whether Cloudflare will write the eligible resource to cache reserve.", + }, + "minimum_file_size": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "The minimum file size, in bytes, eligible for storage in cache reserve. If omitted and \"eligible\" is true, Cloudflare will use 0 bytes by default.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "from_list": schema.ListNestedBlock{ + MarkdownDescription: "Use a list to lookup information for the action.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Name of the list.", + }, + "key": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Expression to use for the list lookup.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "from_value": schema.ListNestedBlock{ + MarkdownDescription: "Use a value to lookup information for the action.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "status_code": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Status code for redirect.", + }, + "preserve_query_string": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Preserve query string for redirect URL.", + }, + }, + Blocks: map[string]schema.Block{ + "target_url": schema.ListNestedBlock{ + MarkdownDescription: "Target URL for redirect.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Static value to provide as the HTTP request header value.", + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("expression"))), + }, + }, + "expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Use a value dynamically determined by the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expression(path.MatchRelative().AtParent().AtName("value"))), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "overrides": schema.ListNestedBlock{ + MarkdownDescription: "List of override configurations to apply to the ruleset.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Defines if the current ruleset-level override enables or disables the ruleset.", + }, + "action": schema.StringAttribute{ + Optional: true, + MarkdownDescription: fmt.Sprintf("Action to perform in the rule-level override. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetRuleActionValues())), + }, + "sensitivity_level": schema.StringAttribute{ + Optional: true, + MarkdownDescription: fmt.Sprintf("Sensitivity level to override for all ruleset rules. %s.", utils.RenderAvailableDocumentationValuesStringSlice([]string{"default", "medium", "low", "eoff"})), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive("default", "medium", "low", "eoff"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "categories": schema.ListNestedBlock{ + MarkdownDescription: "List of tag-based overrides.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "category": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Tag name to apply the ruleset rule override to.", + }, + "action": schema.StringAttribute{ + Optional: true, + MarkdownDescription: fmt.Sprintf("Action to perform in the tag-level override. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetRuleActionValues())), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(cfv1.RulesetRuleActionValues()...), + }, + }, + "enabled": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Defines if the current tag-level override enables or disables the ruleset rules with the specified tag.", + }, + }, + }, + }, + "rules": schema.ListNestedBlock{ + MarkdownDescription: "List of rule-based overrides.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Rule ID to apply the override to.", + }, + "action": schema.StringAttribute{ + Optional: true, + MarkdownDescription: fmt.Sprintf("Action to perform in the rule-level override. %s.", utils.RenderAvailableDocumentationValuesStringSlice(cfv1.RulesetRuleActionValues())), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(cfv1.RulesetRuleActionValues()...), + }, + }, + "enabled": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Defines if the current rule-level override enables or disables the rule.", + }, + "score_threshold": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Anomaly score threshold to apply in the ruleset rule override. Only applicable to modsecurity-based rulesets.", + }, + "sensitivity_level": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Sensitivity level for a ruleset rule override.", + }, + }, + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "origin": schema.ListNestedBlock{ + MarkdownDescription: "List of properties to change request origin.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "host": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Origin Hostname where request is sent.", + }, + "port": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Origin Port where request is sent.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "sni": schema.ListNestedBlock{ + MarkdownDescription: "List of properties to manange Server Name Indication.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Value to define for SNI.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "ratelimit": schema.ListNestedBlock{ + MarkdownDescription: "List of parameters that configure HTTP rate limiting behaviour.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "characteristics": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "List of parameters that define how Cloudflare tracks the request rate for this rule.", + }, + "period": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "The period of time to consider (in seconds) when evaluating the request rate.", + }, + "requests_per_period": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "The number of requests over the period of time that will trigger the Rate Limiting rule.", + }, + "score_per_period": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "The maximum aggregate score over the period of time that will trigger Rate Limiting rule.", + }, + "score_response_header_name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Name of HTTP header in the response, set by the origin server, with the score for the current request.", + }, + "mitigation_timeout": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Once the request rate is reached, the Rate Limiting rule blocks further requests for the period of time defined in this field.", + }, + "counting_expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Criteria for counting HTTP requests to trigger the Rate Limiting action. Uses the Firewall Rules expression language based on Wireshark display filters. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language) documentation for all available fields, operators, and functions.", + }, + "requests_to_origin": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "Whether to include requests to origin within the Rate Limiting count.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "exposed_credential_check": schema.ListNestedBlock{ + MarkdownDescription: "List of parameters that configure exposed credential checks.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "username_expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: `Firewall Rules expression language based on Wireshark display filters for where to check for the "username" value. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language).`, + }, + "password_expression": schema.StringAttribute{ + Optional: true, + MarkdownDescription: `Firewall Rules expression language based on Wireshark display filters for where to check for the "password" value. Refer to the [Firewall Rules language](https://developers.cloudflare.com/firewall/cf-firewall-language).`, + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "logging": schema.ListNestedBlock{ + MarkdownDescription: "List parameters to configure how the rule generates logs. Only valid for skip action.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Override the default logging behavior when a rule is matched.", + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + }, + }, + }, + }, + }, + + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var priorState RulesetResourceModelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &priorState)...) + if resp.Diagnostics.HasError() { + return + } + + for _, rule := range priorState.Rules { + rule.LastUpdated = types.StringNull() + rule.Version = types.StringNull() + + for _, actionParameter := range rule.ActionParameters { + actionParameter.Version = types.StringNull() + } + } + + oldRules := priorState.Rules + newRules := []*RulesModel{} + + if err := copier.Copy(&newRules, &oldRules); err != nil { + resp.Diagnostics.AddError("failed to copy existing rules into new struct", err.Error()) + } + + upgradedStateData := RulesetResourceModel{ + AccountID: priorState.AccountID, + Description: priorState.Description, + ID: priorState.ID, + Kind: priorState.Kind, + Name: priorState.Name, + Phase: priorState.Phase, + Rules: newRules, + ZoneID: priorState.ZoneID, + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateData)...) + }, + }, + } +} diff --git a/internal/framework/service/rulesets/model.go b/internal/framework/service/rulesets/model.go index 82083cf054..36c27b1f1e 100644 --- a/internal/framework/service/rulesets/model.go +++ b/internal/framework/service/rulesets/model.go @@ -14,7 +14,6 @@ type RulesetResourceModel struct { } type RulesModel struct { - Version types.String `tfsdk:"version"` Action types.String `tfsdk:"action"` ActionParameters []*ActionParametersModel `tfsdk:"action_parameters"` Description types.String `tfsdk:"description"` @@ -22,14 +21,12 @@ type RulesModel struct { ExposedCredentialCheck []*ExposedCredentialCheckModel `tfsdk:"exposed_credential_check"` Expression types.String `tfsdk:"expression"` ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` Logging []*LoggingModel `tfsdk:"logging"` Ratelimit []*RatelimitModel `tfsdk:"ratelimit"` Ref types.String `tfsdk:"ref"` } type ActionParametersModel struct { - Version types.String `tfsdk:"version"` AdditionalCacheablePorts types.Set `tfsdk:"additional_cacheable_ports"` AutomaticHTTPSRewrites types.Bool `tfsdk:"automatic_https_rewrites"` AutoMinify []*ActionParameterAutoMinifyModel `tfsdk:"autominify"` diff --git a/internal/framework/service/rulesets/resource.go b/internal/framework/service/rulesets/resource.go index ab0f93edfa..84902ef3dc 100644 --- a/internal/framework/service/rulesets/resource.go +++ b/internal/framework/service/rulesets/resource.go @@ -2,13 +2,11 @@ package rulesets import ( "context" - "encoding/json" "errors" "fmt" "reflect" "sort" "strings" - "time" cfv1 "github.com/cloudflare/cloudflare-go" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/expanders" @@ -130,10 +128,9 @@ func (r *RulesetResource) Create(ctx context.Context, req resource.CreateRequest Phase: rulesetPhase, } - rulesetData := data.toRuleset(ctx) - - if len(rulesetData.Rules) > 0 { - rs.Rules = rulesetData.Rules + rulesetRules := data.toRulesetRules(ctx) + if len(rulesetRules) > 0 { + rs.Rules = rulesetRules } ruleset, rulesetCreateErr := r.client.V1.CreateRuleset(ctx, identifier, rs) @@ -146,7 +143,7 @@ func (r *RulesetResource) Create(ctx context.Context, req resource.CreateRequest params := cfv1.UpdateEntrypointRulesetParams{ Phase: rulesetPhase, Description: rulesetDescription, - Rules: rulesetData.Rules, + Rules: rulesetRules, } var err error @@ -225,12 +222,6 @@ func (r *RulesetResource) Update(ctx context.Context, req resource.UpdateRequest accountID := plan.AccountID zoneID := plan.ZoneID.ValueString() - remappedRules, e := remapPreservedRuleRefs(ctx, state, plan) - if e != nil { - resp.Diagnostics.AddError("failed to remap rule IDs from state", e.Error()) - return - } - var identifier *cfv1.ResourceContainer if accountID.ValueString() != "" { identifier = cfv1.AccountIdentifier(accountID.ValueString()) @@ -241,7 +232,7 @@ func (r *RulesetResource) Update(ctx context.Context, req resource.UpdateRequest params := cfv1.UpdateRulesetParams{ ID: state.ID.ValueString(), Description: plan.Description.ValueString(), - Rules: remappedRules, + Rules: plan.toRulesetRules(ctx), } rs, err := r.client.V1.UpdateRuleset(ctx, identifier, params) if err != nil { @@ -321,13 +312,6 @@ func toRulesetResourceModel(ctx context.Context, zoneID, accountID basetypes.Str Expression: flatteners.String(ruleResponse.Expression), Description: types.StringValue(ruleResponse.Description), Enabled: flatteners.Bool(ruleResponse.Enabled), - Version: flatteners.String(cfv1.String(ruleResponse.Version)), - } - - if ruleResponse.LastUpdated != nil { - rule.LastUpdated = types.StringValue(ruleResponse.LastUpdated.String()) - } else { - rule.LastUpdated = types.StringNull() } // action_parameters @@ -359,7 +343,6 @@ func toRulesetResourceModel(ctx context.Context, zoneID, accountID basetypes.Str OriginErrorPagePassthru: flatteners.Bool(ruleResponse.ActionParameters.OriginErrorPagePassthru), RespectStrongEtags: flatteners.Bool(ruleResponse.ActionParameters.RespectStrongETags), ReadTimeout: flatteners.Int64(int64(cfv1.Uint(ruleResponse.ActionParameters.ReadTimeout))), - Version: flatteners.String(cfv1.String(ruleResponse.ActionParameters.Version)), }) if !reflect.ValueOf(ruleResponse.ActionParameters.Polish).IsNil() { @@ -790,18 +773,13 @@ func toRulesetResourceModel(ctx context.Context, zoneID, accountID basetypes.Str // // The reverse of this method is `toRulesetResourceModel` which handles building // a state representation using the API response. -func (r *RulesetResourceModel) toRuleset(ctx context.Context) cfv1.Ruleset { - var rs cfv1.Ruleset +func (r *RulesetResourceModel) toRulesetRules(ctx context.Context) []cfv1.RulesetRule { var rules []cfv1.RulesetRule - - rs.ID = r.ID.ValueString() for _, rule := range r.Rules { rules = append(rules, rule.toRulesetRule(ctx)) } - rs.Rules = rules - - return rs + return rules } // toRulesetRule takes a state representation of a Ruleset Rule and transforms @@ -810,7 +788,6 @@ func (r *RulesModel) toRulesetRule(ctx context.Context) cfv1.RulesetRule { rr := cfv1.RulesetRule{ ID: r.ID.ValueString(), Ref: r.Ref.ValueString(), - Version: r.Version.ValueStringPointer(), Action: r.Action.ValueString(), Expression: r.Expression.ValueString(), Description: r.Description.ValueString(), @@ -864,12 +841,6 @@ func (r *RulesModel) toRulesetRule(ctx context.Context) cfv1.RulesetRule { rr.ActionParameters.Ruleset = ap.Ruleset.ValueString() } - if !ap.Version.IsNull() { - if ap.Version.ValueString() != "" { - rr.ActionParameters.Version = cfv1.StringPtr(ap.Version.ValueString()) - } - } - if !ap.Increment.IsNull() { rr.ActionParameters.Increment = int(ap.Increment.ValueInt64()) } @@ -1373,146 +1344,48 @@ func (r *RulesModel) toRulesetRule(ctx context.Context) cfv1.RulesetRule { } } - if !r.LastUpdated.IsNull() { - if lastUpdated, err := time.Parse( - "2006-01-02 15:04:05.999999999 -0700 MST", - r.LastUpdated.ValueString(), - ); err == nil { - rr.LastUpdated = &lastUpdated - } - } - return rr } -// ruleRefs is a lookup table for rule IDs with two operations, add and pop. - -// We use add to populate the table from the old value of rules. We use pop to -// look up the ref for the new value of a rule (and remove it from the table). -// -// Internally, both operations serialize the rule to JSON and use the resulting -// string as the lookup key; the ref itself and other computed fields are -// excluded from the JSON. -// -// If a ruleset has multiple copies of the same rule, the copies have a single -// lookup key associated with multiple refs; we preserve order when adding and -// popping the refs. -type ruleRefs struct { - refs map[string][]string -} - -// newRuleRefs creates a new ruleRefs. -func newRuleRefs(rulesetRules []cfv1.RulesetRule, explicitRefs map[string]struct{}) (ruleRefs, error) { - r := ruleRefs{make(map[string][]string)} - for _, rule := range rulesetRules { - if rule.Ref == "" { - // This is unexpected. We only invoke this function for the old - // values of rules, which have their refs populated. - return ruleRefs{}, errors.New("unable to determine ID or ref of existing rule") - } - - if _, ok := explicitRefs[rule.Ref]; ok { - // We should not add explicitly-set refs, to avoid them being - // "stolen" by other rules. - continue - } - - if err := r.add(rule); err != nil { - return ruleRefs{}, err - } - } - - return r, nil -} - -// add stores a ref for the given rule. -func (r *ruleRefs) add(rule cfv1.RulesetRule) error { - key, err := ruleToKey(rule) - if err != nil { - return err - } - - r.refs[key] = append(r.refs[key], rule.Ref) - return nil -} - -// pop removes a ref for the given rule and returns it. If no ref was found for -// the rule, pop returns an empty string. -func (r *ruleRefs) pop(rule cfv1.RulesetRule) (string, error) { - key, err := ruleToKey(rule) - if err != nil { - return "", err +func (r *RulesetResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + var state *RulesetResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - refs := r.refs[key] - if len(refs) == 0 { - return "", nil + var plan *RulesetResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return } - ref, refs := refs[0], refs[1:] - r.refs[key] = refs - - return ref, nil -} - -// isEmpty returns true if the store does not contain any rule refs. -func (r *ruleRefs) isEmpty() bool { - return len(r.refs) == 0 -} - -// ruleToKey converts a ruleset rule to a key that can be used to track -// equivalent rules. Internally, it serializes the rule to JSON after removing -// computed fields. -func ruleToKey(rule cfv1.RulesetRule) (string, error) { - // For the purposes of preserving existing rule refs, we don't want to - // include computed fields as a part of the key value. - rule.ID = "" - rule.Ref = "" - rule.Version = nil - rule.LastUpdated = nil - - data, err := json.Marshal(rule) - if err != nil { - return "", err + // Do nothing if there is no state or no plan. + if state == nil || plan == nil { + return } - return string(data), nil -} - -// remapPreservedRuleRefs tries to preserve the refs of rules that have not -// changed in the ruleset, while also allowing users to explicitly set the ref -// if they choose to. -func remapPreservedRuleRefs(ctx context.Context, state, plan *RulesetResourceModel) ([]cfv1.RulesetRule, error) { - currentRuleset := state.toRuleset(ctx) - plannedRuleset := plan.toRuleset(ctx) - - plannedExplicitRefs := make(map[string]struct{}) - for _, rule := range plannedRuleset.Rules { - if rule.Ref != "" { - plannedExplicitRefs[rule.Ref] = struct{}{} + ruleIDsByRef := make(map[string]types.String) + for _, rule := range state.Rules { + if ref := rule.Ref.ValueString(); ref != "" { + ruleIDsByRef[ref] = rule.ID } } - refs, err := newRuleRefs(currentRuleset.Rules, plannedExplicitRefs) - if err != nil { - return nil, err - } - - if refs.isEmpty() { - // There are no rule refs when the ruleset is first created. - return plannedRuleset.Rules, nil - } - - for i := range plannedRuleset.Rules { - rule := &plannedRuleset.Rules[i] + for _, rule := range plan.Rules { + // Do nothing if the rule's ID is a known planned value. + if !rule.ID.IsUnknown() { + continue + } - // We should not override refs that have been explicitly set. - if rule.Ref == "" { - if rule.Ref, err = refs.pop(*rule); err != nil { - return nil, err + // If the rule's ref matches a rule in the state, populate the planned + // value of its ID with the corresponding ID from the state. + if ref := rule.Ref.ValueString(); ref != "" { + if id, ok := ruleIDsByRef[ref]; ok { + rule.ID = id } } } - return plannedRuleset.Rules, nil + resp.Diagnostics.Append(resp.Plan.Set(ctx, plan)...) } diff --git a/internal/framework/service/rulesets/resource_test.go b/internal/framework/service/rulesets/resource_test.go index a6646520c3..49ea8be9a2 100644 --- a/internal/framework/service/rulesets/resource_test.go +++ b/internal/framework/service/rulesets/resource_test.go @@ -11,7 +11,6 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest" "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestMain(m *testing.M) { @@ -851,167 +850,6 @@ func TestAccCloudflareRuleset_RateLimitMitigationTimeoutOfZero(t *testing.T) { }) } -func TestAccCloudflareRuleset_PreserveRuleRefs(t *testing.T) { - // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the WAF - // service does not yet support the API tokens and it results in - // misleading state error messages. - if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { - t.Setenv("CLOUDFLARE_API_TOKEN", "") - } - - rnd := utils.GenerateRandomResourceName() - zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") - resourceName := "cloudflare_ruleset." + rnd - - var adminRuleRef, loginRuleRef, adminRuleCopyRef, adminRuleExplicitRef string - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - // Create a ruleset with two rules (one for /admin, one for - // /login) and get their refs. - Config: testAccCheckCloudflareRulesetTwoCustomRules(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", getValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", getValue(&loginRuleRef)), - ), - }, - { - // Reverse the order of rules. The refs should remain the same, - // just in reverse order. - Config: testAccCheckCloudflareRulesetTwoCustomRulesReversed(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&loginRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&adminRuleRef)), - ), - }, - { - // Revert to the original version. - Config: testAccCheckCloudflareRulesetTwoCustomRules(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Append a copy of the admin rule. The first two refs should - // not change. - Config: testAccCheckCloudflareRulesetThreeCustomRules(rnd, zoneID, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.2.ref", notEqualsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.2.ref", getValue(&adminRuleCopyRef)), - ), - }, - { - // Disable the login rule. Its ref will change, but the admin - // rule refs should remain the same. - Config: testAccCheckCloudflareRulesetThreeCustomRules(rnd, zoneID, false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", notEqualsValue(&loginRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.2.ref", equalsValue(&adminRuleCopyRef)), - ), - }, - { - // Revert to the original version. The preserved admin rule ref - // should stay the same, and the login rule ref should change. - Config: testAccCheckCloudflareRulesetTwoCustomRules(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", notEqualsValue(&loginRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", getValue(&loginRuleRef)), - ), - }, - { - // Give the admin rule a ref. - Config: testAccCheckCloudflareRulesetTwoCustomRulesWithRef(rnd, zoneID, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "rules.0.ref", "foo"), - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", getValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Disable the admin rule. Its ref should stay the same. - Config: testAccCheckCloudflareRulesetTwoCustomRulesWithRef(rnd, zoneID, false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Prepend a copy of the admin rule without an explicit ref. The - // original rule should keep its explicit ref and the new rule - // should get a new ref. - Config: testAccCheckCloudflareRulesetThreeCustomRulesWithRef(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", notEqualsValue(&adminRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", notEqualsValue(&adminRuleCopyRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", notEqualsValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.2.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Remove the prepended admin rule and re-enable the original - // admin rule. - Config: testAccCheckCloudflareRulesetTwoCustomRulesWithRef(rnd, zoneID, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Revert to the original version. The refs should remain - // exactly the same. - Config: testAccCheckCloudflareRulesetTwoCustomRules(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&adminRuleExplicitRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&loginRuleRef)), - ), - }, - { - // Reverse the order of rules. The refs should remain the same, - // just in reverse order. - Config: testAccCheckCloudflareRulesetTwoCustomRulesReversed(rnd, zoneID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "rules.0.ref", equalsValue(&loginRuleRef)), - resource.TestCheckResourceAttrWith(resourceName, "rules.1.ref", equalsValue(&adminRuleExplicitRef)), - ), - }, - }, - }) -} - -func getValue(result *string) func(string) error { - return func(value string) error { - *result = value - return nil - } -} - -func equalsValue(expected *string) func(string) error { - return func(value string) error { - if value != *expected { - return fmt.Errorf("expected '%s' got '%s'", *expected, value) - } - return nil - } -} - -func notEqualsValue(expected *string) func(string) error { - return func(value string) error { - if value == *expected { - return fmt.Errorf("expected != '%s'", *expected) - } - return nil - } -} - func TestAccCloudflareRuleset_CustomErrors(t *testing.T) { // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the WAF // service does not yet support the API tokens and it results in @@ -1477,7 +1315,7 @@ func TestAccCloudflareRuleset_ActionParametersHTTPDDoSOverride(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "rules.0.action", "execute"), resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.id", "4d21379b4f9f4bb088e0729962c8b3cf"), - resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.overrides.0.rules.0.id", "fdfdac75430c4c47a959592f0aa5e68a"), + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.overrides.0.rules.0.id", "dd4d0a93c065441fb7c99729d96c7c08"), resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.overrides.0.rules.0.sensitivity_level", "low"), resource.TestCheckResourceAttr(resourceName, "rules.0.expression", "true"), resource.TestCheckResourceAttr(resourceName, "rules.0.description", "override HTTP DDoS ruleset rule"), @@ -3560,123 +3398,6 @@ func testAccCheckCloudflareRulesetRateLimitWithMitigationTimeoutOfZero(rnd, name }`, rnd, name, zoneID, zoneName) } -func testAccCheckCloudflareRulesetTwoCustomRules(rnd, zoneID string) string { - return fmt.Sprintf(` - resource "cloudflare_ruleset" "%[1]s" { - zone_id = "%[2]s" - name = "Terraform provider test" - description = "%[1]s ruleset description" - kind = "zone" - phase = "http_request_firewall_custom" - rules { - action = "log" - enabled = true - expression = "(http.request.uri.path eq \"/admin\")" - } - rules { - action = "challenge" - enabled = true - expression = "(http.request.uri.path eq \"/login\")" - } - }`, rnd, zoneID) -} - -func testAccCheckCloudflareRulesetTwoCustomRulesReversed(rnd, zoneID string) string { - return fmt.Sprintf(` - resource "cloudflare_ruleset" "%[1]s" { - zone_id = "%[2]s" - name = "Terraform provider test" - description = "%[1]s ruleset description" - kind = "zone" - phase = "http_request_firewall_custom" - rules { - action = "challenge" - enabled = true - expression = "(http.request.uri.path eq \"/login\")" - } - rules { - action = "log" - enabled = true - expression = "(http.request.uri.path eq \"/admin\")" - } - }`, rnd, zoneID) -} - -func testAccCheckCloudflareRulesetThreeCustomRules(rnd, zoneID string, enableLoginRule bool) string { - return fmt.Sprintf(` - resource "cloudflare_ruleset" "%[1]s" { - zone_id = "%[2]s" - name = "Terraform provider test" - description = "%[1]s ruleset description" - kind = "zone" - phase = "http_request_firewall_custom" - rules { - action = "log" - enabled = true - expression = "(http.request.uri.path eq \"/admin\")" - } - rules { - action = "challenge" - enabled = %[3]t - expression = "(http.request.uri.path eq \"/login\")" - } - rules { - action = "log" - enabled = true - expression = "(http.request.uri.path eq \"/admin\")" - } - }`, rnd, zoneID, enableLoginRule) -} - -func testAccCheckCloudflareRulesetTwoCustomRulesWithRef(rnd, zoneID string, enableAdminRule bool) string { - return fmt.Sprintf(` - resource "cloudflare_ruleset" "%[1]s" { - zone_id = "%[2]s" - name = "Terraform provider test" - description = "%[1]s ruleset description" - kind = "zone" - phase = "http_request_firewall_custom" - rules { - action = "log" - enabled = %[3]t - expression = "(http.request.uri.path eq \"/admin\")" - ref = "foo" - } - rules { - action = "challenge" - enabled = true - expression = "(http.request.uri.path eq \"/login\")" - } - }`, rnd, zoneID, enableAdminRule) -} - -func testAccCheckCloudflareRulesetThreeCustomRulesWithRef(rnd, zoneID string) string { - return fmt.Sprintf(` - resource "cloudflare_ruleset" "%[1]s" { - zone_id = "%[2]s" - name = "Terraform provider test" - description = "%[1]s ruleset description" - kind = "zone" - phase = "http_request_firewall_custom" - rules { - action = "log" - enabled = false - expression = "(http.request.uri.path eq \"/admin\")" - } - rules { - action = "log" - enabled = false - expression = "(http.request.uri.path eq \"/admin\")" - ref = "foo" - } - rules { - action = "challenge" - enabled = true - expression = "(http.request.uri.path eq \"/login\")" - } - }`, rnd, zoneID) -} - func testAccCheckCloudflareRulesetActionParametersOverridesActionEnabled(rnd, name, zoneID, zoneName string) string { return fmt.Sprintf(` resource "cloudflare_ruleset" "%[1]s" { @@ -3784,7 +3505,7 @@ func testAccCheckCloudflareRulesetActionParametersHTTPDDosOverride(rnd, name, zo id = "4d21379b4f9f4bb088e0729962c8b3cf" overrides { rules { - id = "fdfdac75430c4c47a959592f0aa5e68a" # requests with odd HTTP headers or URI path + id = "dd4d0a93c065441fb7c99729d96c7c08" # HTTP requests from known botnet sensitivity_level = "low" } } @@ -5057,7 +4778,3 @@ func testAccCloudflareRulesetCacheSettingsBypassBrowserInvalid(rnd, zoneID strin } `, rnd, zoneID) } - -func testAccCheckCloudflareRulesetDestroy(s *terraform.State) error { - return nil -} diff --git a/internal/framework/service/rulesets/schema.go b/internal/framework/service/rulesets/schema.go index 928813301c..c430417a2b 100644 --- a/internal/framework/service/rulesets/schema.go +++ b/internal/framework/service/rulesets/schema.go @@ -26,14 +26,13 @@ import ( func (r *RulesetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, MarkdownDescription: heredoc.Doc(` - The [Cloudflare Ruleset Engine](https://developers.cloudflare.com/firewall/cf-rulesets) + The Cloudflare Ruleset Engine (https://developers.cloudflare.com/ruleset-engine/about/) allows you to create and deploy rules and rulesets. - The engine syntax, inspired by the Wireshark Display Filter language, is the - same syntax used in custom Firewall Rules. Cloudflare uses the Ruleset Engine - in different products, allowing you to configure several products using the same - basic syntax. + Cloudflare uses the Ruleset Engine in different products, allowing + you to configure several products using the same basic syntax. `), Attributes: map[string]schema.Attribute{ consts.IDSchemaKey: schema.StringAttribute{ @@ -109,10 +108,6 @@ func (r *RulesetResource) Schema(ctx context.Context, req resource.SchemaRequest Computed: true, MarkdownDescription: "Unique rule identifier.", }, - "version": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Version of the ruleset to deploy.", - }, "ref": schema.StringAttribute{ Optional: true, Computed: true, @@ -145,10 +140,6 @@ func (r *RulesetResource) Schema(ctx context.Context, req resource.SchemaRequest }, Optional: true, }, - "last_updated": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "The most recent update to this rule.", - }, }, Blocks: map[string]schema.Block{ "action_parameters": schema.ListNestedBlock{ @@ -311,11 +302,6 @@ func (r *RulesetResource) Schema(ctx context.Context, req resource.SchemaRequest Optional: true, MarkdownDescription: "Pass-through error page for origin.", }, - "version": schema.StringAttribute{ - Computed: true, - Optional: true, - MarkdownDescription: "Version of the ruleset to deploy.", - }, }, Blocks: map[string]schema.Block{ "algorithms": schema.ListNestedBlock{ diff --git a/internal/sdkv2provider/resource_cloudflare_filter.go b/internal/sdkv2provider/resource_cloudflare_filter.go index 0916199b30..9892590241 100644 --- a/internal/sdkv2provider/resource_cloudflare_filter.go +++ b/internal/sdkv2provider/resource_cloudflare_filter.go @@ -30,7 +30,7 @@ func resourceCloudflareFilter() *schema.Resource { for more details and available fields and operators. `), DeprecationMessage: heredoc.Doc(fmt.Sprintf(` - %s resource is in a deprecation phase until January 15th, 2025. + %s resource is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the %s resource. diff --git a/internal/sdkv2provider/resource_cloudflare_firewall_rule.go b/internal/sdkv2provider/resource_cloudflare_firewall_rule.go index 7758e7d5dc..a007af3a23 100644 --- a/internal/sdkv2provider/resource_cloudflare_firewall_rule.go +++ b/internal/sdkv2provider/resource_cloudflare_firewall_rule.go @@ -35,7 +35,7 @@ func resourceCloudflareFirewallRule() *schema.Resource { Rule. `), DeprecationMessage: heredoc.Doc(fmt.Sprintf(` - %s resource is in a deprecation phase until January 15th, 2025. + %s resource is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the %s resource. diff --git a/internal/sdkv2provider/resource_cloudflare_rate_limit.go b/internal/sdkv2provider/resource_cloudflare_rate_limit.go index 8b9994dafe..bff3c01338 100644 --- a/internal/sdkv2provider/resource_cloudflare_rate_limit.go +++ b/internal/sdkv2provider/resource_cloudflare_rate_limit.go @@ -30,7 +30,7 @@ func resourceCloudflareRateLimit() *schema.Resource { specific types of requests/responses. `), DeprecationMessage: heredoc.Doc(fmt.Sprintf(` - %s resource is in a deprecation phase until January 15th, 2025. + %s resource is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the %s resource. diff --git a/internal/sdkv2provider/resource_cloudflare_teams_list.go b/internal/sdkv2provider/resource_cloudflare_teams_list.go index 14a1447b3f..3138368dbc 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_list.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_list.go @@ -64,8 +64,8 @@ func resourceCloudflareTeamsListCreate(ctx context.Context, d *schema.ResourceDa itemsWithoutDescription := d.Get("items").(*schema.Set).List() itemsWithDescriptionValues := d.Get("items_with_description").(*schema.Set).List() - allItems := append([]interface{}{}, itemsWithoutDescription...) - allItems = append(allItems, itemsWithDescriptionValues...) + allItems := append([]interface{}{}, itemsWithDescriptionValues...) + allItems = append(allItems, itemsWithoutDescription...) for _, v := range allItems { item, err := convertItemCFTeamsListItems(v) if err != nil { @@ -135,12 +135,25 @@ func resourceCloudflareTeamsListUpdate(ctx context.Context, d *schema.ResourceDa Name: d.Get("name").(string), Type: d.Get("type").(string), Description: d.Get("description").(string), + Items: []cloudflare.TeamsListItem{}, } - tflog.Debug(ctx, fmt.Sprintf("Updating Cloudflare Teams List from struct: %+v", updatedTeamsList)) - accountID := d.Get(consts.AccountIDSchemaKey).(string) + itemsWithDescriptionValues := d.Get("items_with_description").(*schema.Set).List() + itemsWithoutDescription := d.Get("items").(*schema.Set).List() + allItems := append([]interface{}{}, itemsWithDescriptionValues...) + allItems = append(allItems, itemsWithoutDescription...) + for _, v := range allItems { + item, err := convertItemCFTeamsListItems(v) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err)) + } + updatedTeamsList.Items = append(updatedTeamsList.Items, *item) + } + + tflog.Debug(ctx, fmt.Sprintf("Updating Cloudflare Teams List from struct: %+v", updatedTeamsList)) + identifier := cloudflare.AccountIdentifier(accountID) teamsList, err := client.UpdateTeamsList(ctx, identifier, updatedTeamsList) if err != nil { @@ -150,62 +163,6 @@ func resourceCloudflareTeamsListUpdate(ctx context.Context, d *schema.ResourceDa return diag.FromErr(fmt.Errorf("failed to find Teams List ID in update response; resource was empty")) } - if d.HasChanges("items", "items_with_description") { - oldItemsIface, newItemsIface := d.GetChange("items") - oldItemsWithDescriptionIface, newItemsWithDescriptionIface := d.GetChange("items_with_description") - - oldItems := oldItemsIface.(*schema.Set).List() - newItems := newItemsIface.(*schema.Set).List() - oldItemsWithDescription := oldItemsWithDescriptionIface.(*schema.Set).List() - newItemsWithDescription := newItemsWithDescriptionIface.(*schema.Set).List() - - convertedOldItems := []cloudflare.TeamsListItem{} - convertedNewItems := []cloudflare.TeamsListItem{} - - for _, v := range oldItems { - item, err := convertItemCFTeamsListItems(v) - if err != nil { - return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err)) - } - convertedOldItems = append(convertedOldItems, *item) - } - - for _, v := range oldItemsWithDescription { - item, err := convertItemCFTeamsListItems(v) - if err != nil { - return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err)) - } - convertedOldItems = append(convertedOldItems, *item) - } - - for _, v := range newItems { - item, err := convertItemCFTeamsListItems(v) - if err != nil { - return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err)) - } - convertedNewItems = append(convertedNewItems, *item) - } - - for _, v := range newItemsWithDescription { - item, err := convertItemCFTeamsListItems(v) - if err != nil { - return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err)) - } - convertedNewItems = append(convertedNewItems, *item) - } - - patchTeamsList := cloudflare.PatchTeamsListParams{ID: d.Id()} - setListItemDiff(&patchTeamsList, convertedOldItems, convertedNewItems) - - l, err := client.PatchTeamsList(ctx, identifier, patchTeamsList) - - if err != nil { - return diag.FromErr(fmt.Errorf("error updating Teams List for account %q: %w", accountID, err)) - } - - teamsList.Items = l.Items - } - return resourceCloudflareTeamsListRead(ctx, d, meta) } @@ -246,26 +203,6 @@ func resourceCloudflareTeamsListImport(ctx context.Context, d *schema.ResourceDa return []*schema.ResourceData{d}, nil } -func setListItemDiff(patchList *cloudflare.PatchTeamsListParams, oldItems, newItems []cloudflare.TeamsListItem) { - counts := make(map[cloudflare.TeamsListItem]int) - - for _, item := range newItems { - counts[item] += 1 - } - for _, item := range oldItems { - counts[item] -= 1 - } - - for item, val := range counts { - if val > 0 { - patchList.Append = append(patchList.Append, item) - } - if val < 0 { - patchList.Remove = append(patchList.Remove, item.Value) - } - } -} - func convertItemCFTeamsListItems(item any) (*cloudflare.TeamsListItem, error) { switch item.(type) { case string: diff --git a/internal/sdkv2provider/schema_cloudflare_access_identity_provider.go b/internal/sdkv2provider/schema_cloudflare_access_identity_provider.go index 104cd7bbb4..11e9e6e5e6 100644 --- a/internal/sdkv2provider/schema_cloudflare_access_identity_provider.go +++ b/internal/sdkv2provider/schema_cloudflare_access_identity_provider.go @@ -196,31 +196,37 @@ func resourceCloudflareAccessIdentityProviderSchema() map[string]*schema.Schema Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enabled": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + Description: "A flag to enable or disable SCIM for the identity provider.", }, "secret": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Sensitive: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + Sensitive: true, + Description: "A read-only token generated when the SCIM integration is enabled for the first time. It is redacted on subsequent requests. If you lose this you will need to refresh it token at /access/identity_providers/:idpID/refresh_scim_secret.", }, "user_deprovision": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + Description: "A flag to enable revoking a user's session in Access and Gateway when they have been deprovisioned in the Identity Provider.", }, "seat_deprovision": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + Description: "A flag to remove a user's seat in Zero Trust when they have been deprovisioned in the Identity Provider. This cannot be enabled unless user_deprovision is also enabled.", }, "group_member_deprovision": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + Description: "Deprecated. Use `identity_update_behavior`.", }, "identity_update_behavior": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates how a SCIM event updates a user identity used for policy evaluation. Use \"automatic\" to automatically update a user's identity and augment it with fields from the SCIM user resource. Use \"reauth\" to force re-authentication on group membership updates, user identity update will only occur after successful re-authentication. With \"reauth\" identities will not contain fields from the SCIM user resource. With \"no_action\" identities will not be changed by SCIM updates in any way and users will not be prompted to reauthenticate.", ValidateDiagFunc: func(val interface{}, path cty.Path) diag.Diagnostics { s, ok := val.(string) diff --git a/templates/resources/filter.md.tmpl b/templates/resources/filter.md.tmpl index 39f7167092..b8e8cf39a8 100644 --- a/templates/resources/filter.md.tmpl +++ b/templates/resources/filter.md.tmpl @@ -9,7 +9,7 @@ description: |- {{ .Description | trimspace }} -~> `cloudflare_filter` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_filter` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/templates/resources/firewall_rule.md.tmpl b/templates/resources/firewall_rule.md.tmpl index 286338e7bf..e246704590 100644 --- a/templates/resources/firewall_rule.md.tmpl +++ b/templates/resources/firewall_rule.md.tmpl @@ -9,7 +9,7 @@ description: |- {{ .Description | trimspace }} -~> `cloudflare_firewall_rule` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_firewall_rule` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/templates/resources/rate_limit.md.tmpl b/templates/resources/rate_limit.md.tmpl index 1ee9912082..6f87a4c714 100644 --- a/templates/resources/rate_limit.md.tmpl +++ b/templates/resources/rate_limit.md.tmpl @@ -9,7 +9,7 @@ description: |- {{ .Description | trimspace }} -~> `cloudflare_rate_limit` is in a deprecation phase until January 15th, 2025. +~> `cloudflare_rate_limit` is in a deprecation phase until June 15th, 2025. During this time period, this resource is still fully supported but you are strongly advised to move to the `cloudflare_ruleset` resource. Full details can be found in the diff --git a/tools/cmd/sync-github-issue-to-jira/main.go b/tools/cmd/sync-github-issue-to-jira/main.go index 38f8ec40f7..ecd8dac62c 100644 --- a/tools/cmd/sync-github-issue-to-jira/main.go +++ b/tools/cmd/sync-github-issue-to-jira/main.go @@ -128,7 +128,7 @@ var ( owner: "opayne", }, "service/workers": { - teamName: "Workers Core Platform", + teamName: "Workers Deploy & Config", owner: "laszlo", }, "service/tunnel": { @@ -152,8 +152,8 @@ var ( owner: "njones", }, "service/pages": { - teamName: "Cloudflare Pages", - owner: "nrogers", + teamName: "Workers Deploy & Config", + owner: "laszlo", }, "service/bot_management": { teamName: "Bot Management", diff --git a/tools/go.mod b/tools/go.mod index 2ae6f55778..af4f68e3dd 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -238,16 +238,16 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 8d93732c4e..11dc8a6ea2 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -990,8 +990,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1038,8 +1038,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1091,8 +1091,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1118,8 +1118,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1195,8 +1195,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1204,8 +1204,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1220,8 +1220,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1304,8 +1304,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools/gopls v0.13.2 h1:Pyvx6MKvatbX3zzZmdGiFRfQZl0ohPlt2sFxO/5j6Ro= golang.org/x/tools/gopls v0.13.2/go.mod h1:oX62MpkeKkpDdUDJNgcjAvXQ5bs+QgH+L/QMkLQPgQs= golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 h1:A9kONVi4+AnuOr1dopsibH6hLi1Huy54cbeJxnq4vmU=