From 4200a88282f5eaa827c0215f24beb18ff8e8c6e9 Mon Sep 17 00:00:00 2001 From: Denis Davydov Date: Mon, 18 Jul 2022 09:40:02 +0100 Subject: [PATCH] Extend redirect action with from_value parameter --- .changelog/1781.txt | 3 + .../resources/cloudflare_ruleset/resource.tf | 25 ++++++++ .../provider/resource_cloudflare_ruleset.go | 36 +++++++++++- .../resource_cloudflare_ruleset_test.go | 58 +++++++++++++++++++ .../provider/schema_cloudflare_ruleset.go | 40 +++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 .changelog/1781.txt diff --git a/.changelog/1781.txt b/.changelog/1781.txt new file mode 100644 index 0000000000..844899c22f --- /dev/null +++ b/.changelog/1781.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_ruleset: add support for `from_value` action parameter when using redirect action +``` diff --git a/examples/resources/cloudflare_ruleset/resource.tf b/examples/resources/cloudflare_ruleset/resource.tf index e41aea3cbc..3de1d4c92a 100644 --- a/examples/resources/cloudflare_ruleset/resource.tf +++ b/examples/resources/cloudflare_ruleset/resource.tf @@ -318,3 +318,28 @@ resource "cloudflare_ruleset" "redirect_from_list_example" { enabled = true } } + +# Dynamic Redirects from value resource +resource "cloudflare_ruleset" "redirect_from_value_example" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "redirects" + description = "Redirect ruleset" + kind = "root" + phase = "http_request_dynamic_redirect" + + rules { + action = "redirect" + action_parameters { + from_value { + status_code = 301 + target_url { + value = "some_host.com" + } + preserve_query_string = true + } + } + expression = "true" + description = "Apply redirect from value" + enabled = true + } +} diff --git a/internal/provider/resource_cloudflare_ruleset.go b/internal/provider/resource_cloudflare_ruleset.go index 4cf4b5b961..eb37279c12 100644 --- a/internal/provider/resource_cloudflare_ruleset.go +++ b/internal/provider/resource_cloudflare_ruleset.go @@ -255,6 +255,7 @@ func buildStateFromRulesetRules(rules []cloudflare.RulesetRule) interface{} { serveStaleFields []map[string]interface{} cacheKeyFields []map[string]interface{} fromListFields []map[string]interface{} + fromValueFields []map[string]interface{} ) actionParameterRules := make(map[string]string) @@ -491,12 +492,23 @@ func buildStateFromRulesetRules(rules []cloudflare.RulesetRule) interface{} { } if !reflect.ValueOf(r.ActionParameters.FromList).IsNil() { - fromListFields = append(origin, map[string]interface{}{ + fromListFields = append(fromListFields, map[string]interface{}{ "name": r.ActionParameters.FromList.Name, "key": r.ActionParameters.FromList.Key, }) } + if !reflect.ValueOf(r.ActionParameters.FromValue).IsNil() { + fromValueFields = append(fromValueFields, map[string]interface{}{ + "status_code": r.ActionParameters.FromValue.StatusCode, + "target_url": []interface{}{map[string]interface{}{ + "value": r.ActionParameters.FromValue.TargetURL.Value, + "expression": r.ActionParameters.FromValue.TargetURL.Expression, + }}, + "preserve_query_string": r.ActionParameters.FromValue.PreserveQueryString, + }) + } + actionParameters = append(actionParameters, map[string]interface{}{ "id": r.ActionParameters.ID, "increment": r.ActionParameters.Increment, @@ -524,6 +536,7 @@ func buildStateFromRulesetRules(rules []cloudflare.RulesetRule) interface{} { "cache_key": cacheKeyFields, "origin_error_page_passthru": r.ActionParameters.OriginErrorPagePassthru, "from_list": fromListFields, + "from_value": fromValueFields, }) rule["action_parameters"] = actionParameters @@ -1055,6 +1068,27 @@ func buildRulesetRulesFromResource(d *schema.ResourceData) ([]cloudflare.Ruleset } } + case "from_value": + for i := range pValue.([]interface{}) { + var targetURL cloudflare.RulesetRuleActionParametersTargetURL + for _, pValue := range pValue.([]interface{})[i].(map[string]interface{})["target_url"].([]interface{}) { + for pKey, pValue := range pValue.(map[string]interface{}) { + switch pKey { + case "value": + targetURL.Value = pValue.(string) + case "expression": + targetURL.Expression = pValue.(string) + } + } + } + + rule.ActionParameters.FromValue = &cloudflare.RulesetRuleActionParametersFromValue{ + StatusCode: uint16(pValue.([]interface{})[i].(map[string]interface{})["status_code"].(int)), + TargetURL: targetURL, + PreserveQueryString: pValue.([]interface{})[i].(map[string]interface{})["preserve_query_string"].(bool), + } + } + default: log.Printf("[DEBUG] unknown key encountered in buildRulesetRulesFromResource for action parameters: %s", pKey) } diff --git a/internal/provider/resource_cloudflare_ruleset_test.go b/internal/provider/resource_cloudflare_ruleset_test.go index f20497d4a0..d8560b4519 100644 --- a/internal/provider/resource_cloudflare_ruleset_test.go +++ b/internal/provider/resource_cloudflare_ruleset_test.go @@ -1711,6 +1711,37 @@ func TestAccCloudflareRuleset_Redirect(t *testing.T) { }) } +func TestAccCloudflareRuleset_DynamicRedirect(t *testing.T) { + t.Parallel() + rnd := generateRandomResourceName() + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + resourceName := "cloudflare_ruleset." + rnd + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareRulesetRedirectFromValue(rnd, zoneID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttr(resourceName, "kind", "zone"), + resource.TestCheckResourceAttr(resourceName, "phase", "http_request_dynamic_redirect"), + + resource.TestCheckResourceAttr(resourceName, "rules.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rules.0.action", "redirect"), + + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.from_value.0.status_code", "301"), + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.from_value.0.target_url.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.from_value.0.target_url.0.expression", ""), + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.from_value.0.target_url.0.value", "some_host.com"), + resource.TestCheckResourceAttr(resourceName, "rules.0.action_parameters.0.from_value.0.preserve_query_string", "true"), + ), + }, + }, + }) +} + func testAccCheckCloudflareRulesetMagicTransitSingle(rnd, name, accountID string) string { return fmt.Sprintf(` resource "cloudflare_ruleset" "%[1]s" { @@ -2991,3 +3022,30 @@ func testAccCloudflareRulesetRedirectFromList(rnd, accountID string) string { } }`, rnd, accountID) } + +func testAccCloudflareRulesetRedirectFromValue(rnd, zoneID string) string { + return fmt.Sprintf(` + resource "cloudflare_ruleset" "%[1]s" { + zone_id = "%[2]s" + name = "%[1]s" + description = "%[1]s ruleset description" + kind = "zone" + phase = "http_request_dynamic_redirect" + + rules { + action = "redirect" + action_parameters { + from_value { + status_code = 301 + target_url { + value = "some_host.com" + } + preserve_query_string = true + } + } + expression = "true" + description = "Apply redirect from value" + enabled = true + } + }`, rnd, zoneID) +} diff --git a/internal/provider/schema_cloudflare_ruleset.go b/internal/provider/schema_cloudflare_ruleset.go index 5c28e931fa..7d71d67926 100644 --- a/internal/provider/schema_cloudflare_ruleset.go +++ b/internal/provider/schema_cloudflare_ruleset.go @@ -713,6 +713,46 @@ func resourceCloudflareRulesetSchema() map[string]*schema.Schema { }, }, }, + "from_value": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Use a value to lookup information for the action.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status_code": { + Type: schema.TypeInt, + Description: "Status code for redirect.", + Optional: true, + }, + "target_url": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Target URL for redirect.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Optional: true, + Description: "Static value to provide as the HTTP request header value. Conflicts with `\"expression\"`.", + }, + "expression": { + Description: "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. Conflicts with `\"value\"`.", + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "preserve_query_string": { + Type: schema.TypeBool, + Description: "Preserve query string for redirect URL.", + Optional: true, + }, + }, + }, + }, }, }, },