Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend redirect action with from_value parameter #1781

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1781.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/cloudflare_ruleset: add support for `from_value` action parameter when using redirect action
```
45 changes: 45 additions & 0 deletions docs/resources/ruleset.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,31 @@ 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
}
}
```

<!-- schema generated by tfplugindocs -->
Expand Down Expand Up @@ -410,6 +435,7 @@ Optional:
- `cookie_fields` (Set of String) List of cookie values to include as part of custom fields logging.
- `edge_ttl` (Block List, Max: 1) List of edge TTL parameters to apply to the request. (see [below for nested schema](#nestedblock--rules--action_parameters--edge_ttl))
- `from_list` (Block List, Max: 1) Use a list to lookup information for the action. (see [below for nested schema](#nestedblock--rules--action_parameters--from_list))
- `from_value` (Block List, Max: 1) Use a value to lookup information for the action. (see [below for nested schema](#nestedblock--rules--action_parameters--from_value))
- `headers` (Block List) List of HTTP header modifications to perform in the ruleset rule. (see [below for nested schema](#nestedblock--rules--action_parameters--headers))
- `host_header` (String) Host Header that request origin receives.
- `id` (String) Identifier of the action parameter to modify.
Expand Down Expand Up @@ -556,6 +582,25 @@ Required:
- `name` (String) Name of the list.


<a id="nestedblock--rules--action_parameters--from_value"></a>
### Nested Schema for `rules.action_parameters.from_value`

Optional:

- `preserve_query_string` (Boolean) Preserve query string for redirect URL.
- `status_code` (Number) Status code for redirect.
- `target_url` (Block List, Max: 1) Target URL for redirect. (see [below for nested schema](#nestedblock--rules--action_parameters--from_value--target_url))

<a id="nestedblock--rules--action_parameters--from_value--target_url"></a>
### Nested Schema for `rules.action_parameters.from_value.target_url`

Optional:

- `expression` (String) 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"`.
- `value` (String) Static value to provide as the HTTP request header value. Conflicts with `"expression"`.



<a id="nestedblock--rules--action_parameters--headers"></a>
### Nested Schema for `rules.action_parameters.headers`

Expand Down
25 changes: 25 additions & 0 deletions examples/resources/cloudflare_ruleset/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
36 changes: 35 additions & 1 deletion internal/provider/resource_cloudflare_ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
58 changes: 58 additions & 0 deletions internal/provider/resource_cloudflare_ruleset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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)
}
40 changes: 40 additions & 0 deletions internal/provider/schema_cloudflare_ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
},
},
},
},
Expand Down