diff --git a/.changelog/2220.txt b/.changelog/2220.txt
new file mode 100644
index 0000000000..50a4594bdd
--- /dev/null
+++ b/.changelog/2220.txt
@@ -0,0 +1,3 @@
+```release-note:new-data-source
+cloudflare_rulesets
+```
diff --git a/docs/data-sources/record.md b/docs/data-sources/record.md
index 0ab72eaf3c..140719b49e 100644
--- a/docs/data-sources/record.md
+++ b/docs/data-sources/record.md
@@ -13,7 +13,7 @@ Use this data source to lookup a single [DNS Record](https://api.cloudflare.com/
```terraform
data "cloudflare_record" "example" {
- zone_id = var.zone_id
+ zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
hostname = "example.com"
}
```
diff --git a/docs/data-sources/rulesets.md b/docs/data-sources/rulesets.md
new file mode 100644
index 0000000000..f4fa19ec32
--- /dev/null
+++ b/docs/data-sources/rulesets.md
@@ -0,0 +1,432 @@
+---
+page_title: "cloudflare_rulesets Data Source - Cloudflare"
+subcategory: ""
+description: |-
+ Use this datasource to lookup Rulesets in an account or zone.
+---
+
+# cloudflare_rulesets (Data Source)
+
+Use this datasource to lookup Rulesets in an account or zone.
+
+## Example Usage
+
+```terraform
+data "cloudflare_rulesets" "example" {
+ zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
+
+ filter {
+ name = ".*OWASP.*"
+ }
+}
+```
+
+
+## Schema
+
+### Optional
+
+- `account_id` (String) The account identifier to target for the resource. Must provide only one of `zone_id`, `account_id`.
+- `filter` (Block List, Max: 1) (see [below for nested schema](#nestedblock--filter))
+- `include_rules` (Boolean) Include rule data in response.
+- `zone_id` (String) The zone identifier to target for the resource. Must provide only one of `zone_id`, `account_id`.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `rulesets` (List of Object) (see [below for nested schema](#nestedatt--rulesets))
+
+
+### Nested Schema for `filter`
+
+Optional:
+
+- `id` (String) The ID of the Ruleset to target.
+- `kind` (String) Type of Ruleset to create. Available values: `custom`, `managed`, `root`, `schema`, `zone`.
+- `name` (String) Name of the ruleset.
+- `phase` (String) Point in the request/response lifecycle where the ruleset will be created. Available values: `ddos_l4`, `ddos_l7`, `http_custom_errors`, `http_log_custom_fields`, `http_request_cache_settings`, `http_request_firewall_custom`, `http_request_firewall_managed`, `http_request_late_transform`, `http_request_late_transform_managed`, `http_request_main`, `http_request_origin`, `http_request_dynamic_redirect`, `http_request_redirect`, `http_request_sanitize`, `http_request_transform`, `http_response_firewall_managed`, `http_response_headers_transform`, `http_response_headers_transform_managed`, `magic_transit`, `http_ratelimit`, `http_request_sbfm`, `http_config_settings`.
+- `version` (String) Version of the ruleset to filter on.
+
+
+
+### Nested Schema for `rulesets`
+
+Read-Only:
+
+- `description` (String)
+- `id` (String)
+- `kind` (String)
+- `name` (String)
+- `phase` (String)
+- `rules` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules))
+- `version` (String)
+
+
+### Nested Schema for `rulesets.rules`
+
+Read-Only:
+
+- `action` (String)
+- `action_parameters` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters))
+- `description` (String)
+- `enabled` (Boolean)
+- `exposed_credential_check` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--exposed_credential_check))
+- `expression` (String)
+- `id` (String)
+- `last_updated` (String)
+- `logging` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--logging))
+- `ratelimit` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--ratelimit))
+- `ref` (String)
+- `version` (String)
+
+
+### Nested Schema for `rulesets.rules.action_parameters`
+
+Read-Only:
+
+- `automatic_https_rewrites` (Boolean)
+- `autominify` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--autominify))
+- `bic` (Boolean)
+- `browser_ttl` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--browser_ttl))
+- `cache` (Boolean)
+- `cache_key` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--cache_key))
+- `content` (String)
+- `content_type` (String)
+- `cookie_fields` (Set of String)
+- `disable_apps` (Boolean)
+- `disable_railgun` (Boolean)
+- `disable_zaraz` (Boolean)
+- `edge_ttl` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--edge_ttl))
+- `email_obfuscation` (Boolean)
+- `from_list` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--from_list))
+- `from_value` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--from_value))
+- `headers` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--headers))
+- `host_header` (String)
+- `hotlink_protection` (Boolean)
+- `id` (String)
+- `increment` (Number)
+- `matched_data` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--matched_data))
+- `mirage` (Boolean)
+- `opportunistic_encryption` (Boolean)
+- `origin` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--origin))
+- `origin_error_page_passthru` (Boolean)
+- `overrides` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--overrides))
+- `phases` (Set of String)
+- `polish` (String)
+- `products` (Set of String)
+- `request_fields` (Set of String)
+- `respect_strong_etags` (Boolean)
+- `response` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--response))
+- `response_fields` (Set of String)
+- `rocket_loader` (Boolean)
+- `rules` (Map of String)
+- `ruleset` (String)
+- `rulesets` (Set of String)
+- `security_level` (String)
+- `serve_stale` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--serve_stale))
+- `server_side_excludes` (Boolean)
+- `sni` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--sni))
+- `ssl` (String)
+- `status_code` (Number)
+- `sxg` (Boolean)
+- `uri` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--uri))
+- `version` (String)
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `css` (Boolean)
+- `html` (Boolean)
+- `js` (Boolean)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `default` (Number)
+- `mode` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `cache_by_device_type` (Boolean)
+- `cache_deception_armor` (Boolean)
+- `custom_key` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key))
+- `ignore_query_strings_order` (Boolean)
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key`
+
+Read-Only:
+
+- `cookie` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key--cookie))
+- `header` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key--header))
+- `host` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key--host))
+- `query_string` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key--query_string))
+- `user` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--custom_key--user))
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key.user`
+
+Read-Only:
+
+- `check_presence` (List of String)
+- `include` (List of String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key.user`
+
+Read-Only:
+
+- `check_presence` (List of String)
+- `exclude_origin` (Boolean)
+- `include` (List of String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key.user`
+
+Read-Only:
+
+- `resolved` (Boolean)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key.user`
+
+Read-Only:
+
+- `exclude` (List of String)
+- `include` (List of String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.custom_key.user`
+
+Read-Only:
+
+- `device_type` (Boolean)
+- `geo` (Boolean)
+- `lang` (Boolean)
+
+
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `default` (Number)
+- `mode` (String)
+- `status_code_ttl` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--status_code_ttl))
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.status_code_ttl`
+
+Read-Only:
+
+- `status_code` (Number)
+- `status_code_range` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--status_code_ttl--status_code_range))
+- `value` (Number)
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.status_code_ttl.value`
+
+Read-Only:
+
+- `from` (Number)
+- `to` (Number)
+
+
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `key` (String)
+- `name` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `preserve_query_string` (Boolean)
+- `status_code` (Number)
+- `target_url` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--target_url))
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.target_url`
+
+Read-Only:
+
+- `expression` (String)
+- `value` (String)
+
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `expression` (String)
+- `name` (String)
+- `operation` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `public_key` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `host` (String)
+- `port` (Number)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `action` (String)
+- `categories` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--categories))
+- `enabled` (Boolean)
+- `rules` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--rules))
+- `sensitivity_level` (String)
+- `status` (String)
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.categories`
+
+Read-Only:
+
+- `action` (String)
+- `category` (String)
+- `enabled` (Boolean)
+- `status` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.rules`
+
+Read-Only:
+
+- `action` (String)
+- `enabled` (Boolean)
+- `id` (String)
+- `score_threshold` (Number)
+- `sensitivity_level` (String)
+- `status` (String)
+
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `content` (String)
+- `content_type` (String)
+- `status_code` (Number)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `disable_stale_while_updating` (Boolean)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `value` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version`
+
+Read-Only:
+
+- `origin` (Boolean)
+- `path` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--path))
+- `query` (List of Object) (see [below for nested schema](#nestedobjatt--rulesets--rules--action_parameters--version--query))
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.path`
+
+Read-Only:
+
+- `expression` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `rulesets.rules.action_parameters.version.query`
+
+Read-Only:
+
+- `expression` (String)
+- `value` (String)
+
+
+
+
+
+### Nested Schema for `rulesets.rules.exposed_credential_check`
+
+Read-Only:
+
+- `password_expression` (String)
+- `username_expression` (String)
+
+
+
+### Nested Schema for `rulesets.rules.logging`
+
+Read-Only:
+
+- `enabled` (Boolean)
+- `status` (String)
+
+
+
+### Nested Schema for `rulesets.rules.ratelimit`
+
+Read-Only:
+
+- `characteristics` (Set of String)
+- `counting_expression` (String)
+- `mitigation_timeout` (Number)
+- `period` (Number)
+- `requests_per_period` (Number)
+- `requests_to_origin` (Boolean)
+- `score_per_period` (Number)
+- `score_response_header_name` (String)
+
+
diff --git a/docs/resources/pages_project.md b/docs/resources/pages_project.md
index d5ec46a7ad..9c91862daa 100644
--- a/docs/resources/pages_project.md
+++ b/docs/resources/pages_project.md
@@ -117,7 +117,7 @@ resource "cloudflare_pages_project" "deployment_configs" {
### Required
- `account_id` (String) The account identifier to target for the resource.
-- `name` (String) Name of the project.
+- `name` (String) Name of the project. **Modifying this attribute will force creation of a new resource.**
- `production_branch` (String) The name of the branch that is used for the production environment.
### Optional
diff --git a/docs/resources/ruleset.md b/docs/resources/ruleset.md
index c98ca08405..0401ea2a6c 100644
--- a/docs/resources/ruleset.md
+++ b/docs/resources/ruleset.md
@@ -456,6 +456,7 @@ Optional:
- `description` (String) Brief summary of the ruleset rule and its intended use.
- `enabled` (Boolean) Whether the rule is active.
- `exposed_credential_check` (Block List, Max: 1) List of parameters that configure exposed credential checks. (see [below for nested schema](#nestedblock--rules--exposed_credential_check))
+- `last_updated` (String) The most recent update to this rule.
- `logging` (Block List, Max: 1) List parameters to configure how the rule generates logs. (see [below for nested schema](#nestedblock--rules--logging))
- `ratelimit` (Block List, Max: 1) List of parameters that configure HTTP rate limiting behaviour. (see [below for nested schema](#nestedblock--rules--ratelimit))
diff --git a/examples/data-sources/cloudflare_record/data-source.tf b/examples/data-sources/cloudflare_record/data-source.tf
index b45b6a4436..ab01614588 100644
--- a/examples/data-sources/cloudflare_record/data-source.tf
+++ b/examples/data-sources/cloudflare_record/data-source.tf
@@ -1,4 +1,4 @@
data "cloudflare_record" "example" {
- zone_id = var.zone_id
+ zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
hostname = "example.com"
}
diff --git a/examples/data-sources/cloudflare_rulesets/data-source.tf b/examples/data-sources/cloudflare_rulesets/data-source.tf
new file mode 100644
index 0000000000..1185577fcc
--- /dev/null
+++ b/examples/data-sources/cloudflare_rulesets/data-source.tf
@@ -0,0 +1,7 @@
+data "cloudflare_rulesets" "example" {
+ zone_id = "0da42c8d2132a9ddaf714f9e7c920711"
+
+ filter {
+ name = ".*OWASP.*"
+ }
+}
diff --git a/internal/sdkv2provider/data_source_rulesets.go b/internal/sdkv2provider/data_source_rulesets.go
new file mode 100644
index 0000000000..bfcbbcb786
--- /dev/null
+++ b/internal/sdkv2provider/data_source_rulesets.go
@@ -0,0 +1,285 @@
+package sdkv2provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "github.com/MakeNowJust/heredoc/v2"
+ "github.com/cloudflare/cloudflare-go"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudflareRulesets() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceCloudflareRulesetsRead,
+
+ Description: heredoc.Doc(`
+ Use this datasource to lookup Rulesets in an account or zone.
+ `),
+
+ Schema: map[string]*schema.Schema{
+ "account_id": {
+ Description: "The account identifier to target for the resource.",
+ Type: schema.TypeString,
+ Optional: true,
+ ExactlyOneOf: []string{"zone_id", "account_id"},
+ },
+ "zone_id": {
+ Description: "The zone identifier to target for the resource.",
+ Type: schema.TypeString,
+ Optional: true,
+ ExactlyOneOf: []string{"zone_id", "account_id"},
+ },
+
+ "include_rules": {
+ Description: "Include rule data in response",
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+
+ "filter": {
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "The ID of the Ruleset to target.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Name of the ruleset.",
+ },
+ "phase": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: fmt.Sprintf("Point in the request/response lifecycle where the ruleset will be created. %s", renderAvailableDocumentationValuesStringSlice(cloudflare.RulesetPhaseValues())),
+ },
+ "kind": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: fmt.Sprintf("Type of Ruleset to create. %s", renderAvailableDocumentationValuesStringSlice(cloudflare.RulesetKindValues())),
+ },
+ "version": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Version of the ruleset to filter on.",
+ },
+ },
+ },
+ },
+
+ "rulesets": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "ID of the ruleset.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Name of the ruleset.",
+ },
+ "description": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Brief summary of the ruleset and its intended use.",
+ },
+ "kind": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: fmt.Sprintf("Type of Ruleset. %s", renderAvailableDocumentationValuesStringSlice(cloudflare.RulesetKindValues())),
+ },
+ "version": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Version of the ruleset.",
+ },
+ "phase": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: fmt.Sprintf("Point in the request/response lifecycle where the ruleset executes. %s", renderAvailableDocumentationValuesStringSlice(cloudflare.RulesetPhaseValues())),
+ },
+ "rules": resourceCloudflareRulesetSchema()["rules"],
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceCloudflareRulesetsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*cloudflare.API)
+ zoneID := d.Get("zone_id").(string)
+ accountID := d.Get("account_id").(string)
+ includeRules := d.Get("include_rules").(bool)
+ filter, err := expandFilterRulesets(d.Get("filter"))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ var rulesetsList []cloudflare.Ruleset
+ if accountID != "" {
+ rulesetsList, err = client.ListAccountRulesets(ctx, accountID)
+ } else {
+ rulesetsList, err = client.ListZoneRulesets(ctx, zoneID)
+ }
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ rulesets := make([]interface{}, 0)
+ rulesetIds := make([]string, 0)
+ for _, ruleset := range rulesetsList {
+ if filter.ID != "" && filter.ID != ruleset.ID {
+ continue
+ }
+ if filter.Phase != "" && filter.Phase != ruleset.Phase {
+ continue
+ }
+ if filter.Kind != "" && filter.Kind != ruleset.Kind {
+ continue
+ }
+ if filter.Version != "" && filter.Version != ruleset.Version {
+ continue
+ }
+ if filter.Name != nil && !filter.Name.Match([]byte(ruleset.Name)) {
+ continue
+ }
+
+ rulesetIds = append(rulesetIds, ruleset.ID)
+ resultRuleset := map[string]interface{}{
+ "id": ruleset.ID,
+ "name": ruleset.Name,
+ "description": ruleset.Description,
+ "kind": ruleset.Kind,
+ "version": ruleset.Version,
+ "phase": ruleset.Phase,
+ }
+
+ if includeRules {
+ var fullRuleset cloudflare.Ruleset
+ if accountID != "" {
+ fullRuleset, err = client.GetAccountRuleset(ctx, accountID, ruleset.ID)
+ } else {
+ fullRuleset, err = client.GetZoneRuleset(ctx, zoneID, ruleset.ID)
+ }
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ rules := make([]interface{}, 0)
+ for _, rule := range fullRuleset.Rules {
+ fullRulesetRule := map[string]interface{}{
+ "id": rule.ID,
+ "version": rule.Version,
+ "action": rule.Action,
+ "expression": rule.Expression,
+ "description": rule.Description,
+ "last_updated": rule.LastUpdated.String(),
+ "ref": rule.Ref,
+ "enabled": rule.Enabled,
+ }
+
+ if rule.RateLimit != nil {
+ rl := make([]interface{}, 0)
+ fullRulesetRule["ratelimit"] = append(rl, map[string]interface{}{
+ "characteristics": rule.RateLimit.Characteristics,
+ "requests_per_period": rule.RateLimit.RequestsPerPeriod,
+ "period": rule.RateLimit.Period,
+ "mitigation_timeout": rule.RateLimit.MitigationTimeout,
+ "counting_expression": rule.RateLimit.CountingExpression,
+ "requests_to_origin": rule.RateLimit.RequestsToOrigin,
+ })
+ }
+
+ if rule.ExposedCredentialCheck != nil {
+ ecc := make([]interface{}, 0)
+ fullRulesetRule["exposed_credential_check"] = append(ecc, map[string]interface{}{
+ "username_expression": rule.ExposedCredentialCheck.UsernameExpression,
+ "password_expression": rule.ExposedCredentialCheck.PasswordExpression,
+ })
+ }
+ if rule.Logging != nil {
+ lg := make([]interface{}, 0)
+ fullRulesetRule["logging"] = append(lg, map[string]interface{}{
+ "enabled": rule.Logging.Enabled,
+ "status": loggingStatus(rule.Logging),
+ })
+ }
+ rules = append(rules, fullRulesetRule)
+ }
+ resultRuleset["rules"] = rules
+ }
+
+ rulesets = append(rulesets, resultRuleset)
+ }
+
+ err = d.Set("rulesets", rulesets)
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("error setting Ruleset Ids: %w", err))
+ }
+
+ d.SetId(stringListChecksum(rulesetIds))
+ return nil
+}
+
+func loggingStatus(logging *cloudflare.RulesetRuleLogging) string {
+ if logging == nil || logging.Enabled == nil {
+ return "default"
+ }
+ if *logging.Enabled {
+ return "enabled"
+ }
+ return "disabled"
+}
+
+func expandFilterRulesets(d interface{}) (*searchFilterRulesets, error) {
+ cfg := d.([]interface{})
+ filter := &searchFilterRulesets{}
+ if len(cfg) == 0 || cfg[0] == nil {
+ return filter, nil
+ }
+
+ m := cfg[0].(map[string]interface{})
+ if name, ok := m["name"]; ok {
+ match, err := regexp.Compile(name.(string))
+ if err != nil {
+ return nil, err
+ }
+
+ filter.Name = match
+ }
+
+ if kind, ok := m["kind"]; ok {
+ filter.Kind = kind.(string)
+ }
+ if phase, ok := m["phase"]; ok {
+ filter.Phase = phase.(string)
+ }
+ if id, ok := m["id"]; ok {
+ filter.ID = id.(string)
+ }
+ if version, ok := m["version"]; ok {
+ filter.Version = version.(string)
+ }
+
+ return filter, nil
+}
+
+type searchFilterRulesets struct {
+ Name *regexp.Regexp
+ Kind string
+ Phase string
+ ID string
+ Version string
+}
diff --git a/internal/sdkv2provider/data_source_rulesets_test.go b/internal/sdkv2provider/data_source_rulesets_test.go
new file mode 100644
index 0000000000..b5a0afce22
--- /dev/null
+++ b/internal/sdkv2provider/data_source_rulesets_test.go
@@ -0,0 +1,134 @@
+package sdkv2provider
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+)
+
+func TestAccCloudflareRulesetsProviderDataSource_PreventZoneIdAndAccountIdConflicts(t *testing.T) {
+ rnd := generateRandomResourceName()
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testCloudflareRulesetsProviderDataSourceConfigConflictingFields(rnd),
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("only one of `account_id,zone_id` can be specified")),
+ },
+ },
+ })
+}
+
+func testCloudflareRulesetsProviderDataSourceConfigConflictingFields(rnd string) string {
+ return fmt.Sprintf(`
+data "cloudflare_rulesets" "%[1]s" {
+ account_id = "123abc"
+ zone_id = "456def"
+}
+`, rnd)
+}
+
+func TestAccCloudflareRulesetsProviderDataSource_RequireOneOfZoneAccountID(t *testing.T) {
+ rnd := generateRandomResourceName()
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testCloudflareRulesetsProviderDataSourceRequireOneOfZoneAccountID(rnd),
+ ExpectError: regexp.MustCompile(regexp.QuoteMeta("one of `account_id,zone_id` must be specified")),
+ },
+ },
+ })
+}
+
+func testCloudflareRulesetsProviderDataSourceRequireOneOfZoneAccountID(rnd string) string {
+ return fmt.Sprintf(`
+data "cloudflare_rulesets" "%[1]s" {
+}
+`, rnd)
+}
+
+func TestAccCloudflareRulesetsProviderDataSource_FetchOWASPRulesetByName(t *testing.T) {
+ rnd := generateRandomResourceName()
+ zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
+ name := fmt.Sprintf("data.cloudflare_rulesets.%s", rnd)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testCloudflareRulesetsProviderDataSourceFetchOWASPRulesetByName(rnd, zoneID),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCloudflareRulesetsDataSourceID(name),
+ resource.TestCheckResourceAttr(name, "rulesets.0.name", "Cloudflare OWASP Core Ruleset"),
+ ),
+ },
+ },
+ })
+}
+
+func testCloudflareRulesetsProviderDataSourceFetchOWASPRulesetByName(rnd string, zoneID string) string {
+ return fmt.Sprintf(`
+data "cloudflare_rulesets" "%[1]s" {
+ zone_id = "%[2]s"
+
+ filter {
+ name = ".*OWASP.*"
+ }
+}
+`, rnd, zoneID)
+}
+
+func TestAccCloudflareRulesetsProviderDataSource_FetchOWASPRulesetByID(t *testing.T) {
+ rnd := generateRandomResourceName()
+ zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
+ name := fmt.Sprintf("data.cloudflare_rulesets.%s", rnd)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testCloudflareRulesetsProviderDataSourceFetchOWASPRulesetByID(rnd, zoneID),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCloudflareRulesetsDataSourceID(name),
+ resource.TestCheckResourceAttr(name, "rulesets.0.name", "Cloudflare OWASP Core Ruleset"),
+ ),
+ },
+ },
+ })
+}
+
+func testCloudflareRulesetsProviderDataSourceFetchOWASPRulesetByID(rnd string, zoneID string) string {
+ return fmt.Sprintf(`
+data "cloudflare_rulesets" "%[1]s" {
+ zone_id = "%[2]s"
+
+ filter {
+ id = "4814384a9e5d4991b9815dcfc25d2f1f"
+ }
+}
+`, rnd, zoneID)
+}
+
+func testAccCheckCloudflareRulesetsDataSourceID(n string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ all := s.RootModule().Resources
+ rs, ok := all[n]
+ if !ok {
+ return fmt.Errorf("can't find Rulesets data source: %s", n)
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("Snapshot Rulesets source ID not set")
+ }
+ return nil
+ }
+}
diff --git a/internal/sdkv2provider/provider.go b/internal/sdkv2provider/provider.go
index f027477c68..a6b970c973 100644
--- a/internal/sdkv2provider/provider.go
+++ b/internal/sdkv2provider/provider.go
@@ -171,6 +171,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_load_balancer_pools": dataSourceCloudflareLoadBalancerPools(),
"cloudflare_origin_ca_root_certificate": dataSourceCloudflareOriginCARootCertificate(),
"cloudflare_record": dataSourceCloudflareRecord(),
+ "cloudflare_rulesets": dataSourceCloudflareRulesets(),
"cloudflare_waf_groups": dataSourceCloudflareWAFGroups(),
"cloudflare_waf_packages": dataSourceCloudflareWAFPackages(),
"cloudflare_waf_rules": dataSourceCloudflareWAFRules(),
diff --git a/internal/sdkv2provider/schema_cloudflare_ruleset.go b/internal/sdkv2provider/schema_cloudflare_ruleset.go
index ff26cc8ad1..339f33150b 100644
--- a/internal/sdkv2provider/schema_cloudflare_ruleset.go
+++ b/internal/sdkv2provider/schema_cloudflare_ruleset.go
@@ -93,6 +93,11 @@ func resourceCloudflareRulesetSchema() map[string]*schema.Schema {
Optional: true,
Description: "Brief summary of the ruleset rule and its intended use.",
},
+ "last_updated": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "The most recent update to this rule.",
+ },
"action_parameters": {
Type: schema.TypeList,
MaxItems: 1,