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

Add challenge and js_challenge rate-limit modes #172

Merged
merged 9 commits into from
Jan 7, 2019

Conversation

jdarley
Copy link
Contributor

@jdarley jdarley commented Nov 30, 2018

Minor update to add challenge and js_challenge support to cloudflare_rate_limit resource.

This has been manually tested and I can confirm it will create a rate limit with a challenge or js_challenge action as expected.

This PR related to issue 171

},

"timeout": {
Type: schema.TypeInt,
Required: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is now optional based on the mode, we probably want to add a check in the Create/Update that if the mode is simulate or ban, we check the value is defined to prevent people scratching their heads with unintended behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree. The Cloudflare API does give reasonable error messages around this though it should really be caught by the provider before the API is called.

I'm just working on it at the moment, though this is the first time I've written any Go so you may have to forgive my non-idiomatic code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After grappling with attempting to insert validation into expandRateLimitAction, I've realised that this won't behave quite as expected. I've attempted to implement a ValidateFunc into the action section of the schema itself (where I'll have access to both mode and timeout), but it appears that this is not supported on lists or sets at this point in time.

I'd appreciate any pointers as to how to proceed with this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... but it appears that this is not supported on lists or sets at this point in time.

Yep, the ValidateFunc is only supported on primitive types which throws it out a bit.

I'd appreciate any pointers as to how to proceed with this.

Sure, I think this might do the job. You were on the right track with using expandRateLimitAction as it's used in both the Create and Update. I haven't thoroughly vetted this though.

diff --git a/cloudflare/resource_cloudflare_rate_limit.go b/cloudflare/resource_cloudflare_rate_limit.go
index e67f1a4..dd0f170 100644
--- a/cloudflare/resource_cloudflare_rate_limit.go
+++ b/cloudflare/resource_cloudflare_rate_limit.go
@@ -197,11 +197,15 @@ func resourceCloudflareRateLimit() *schema.Resource {
 
 func resourceCloudflareRateLimitCreate(d *schema.ResourceData, meta interface{}) error {
 	client := meta.(*cloudflare.API)
+	rateLimitAction, err := expandRateLimitAction(d)
+	if err != nil {
+		return errors.Wrap(err, "error expanding rate limit action")
+	}
 
 	newRateLimit := cloudflare.RateLimit{
 		Threshold: d.Get("threshold").(int),
 		Period:    d.Get("period").(int),
-		Action:    expandRateLimitAction(d),
+		Action:    rateLimitAction,
 	}
 
 	newRateLimitMatch, err := expandRateLimitTrafficMatcher(d)
@@ -255,11 +259,15 @@ func resourceCloudflareRateLimitUpdate(d *schema.ResourceData, meta interface{})
 	client := meta.(*cloudflare.API)
 	zoneId := d.Get("zone_id").(string)
 	rateLimitId := d.Id()
+	rateLimitAction, err := expandRateLimitAction(d)
+	if err != nil {
+		return errors.Wrap(err, "error expanding rate limit action")
+	}
 
 	updatedRateLimit := cloudflare.RateLimit{
 		Threshold: d.Get("threshold").(int),
 		Period:    d.Get("period").(int),
-		Action:    expandRateLimitAction(d),
+		Action:    rateLimitAction,
 	}
 
 	newRateLimitMatch, err := expandRateLimitTrafficMatcher(d)
@@ -341,13 +349,21 @@ func expandRateLimitTrafficMatcher(d *schema.ResourceData) (matcher cloudflare.R
 	return
 }
 
-func expandRateLimitAction(d *schema.ResourceData) cloudflare.RateLimitAction {
+func expandRateLimitAction(d *schema.ResourceData) (cloudflare.RateLimitAction, error) {
 	// dont need to guard for array length because MinItems is set **and** action is required
 	tfAction := d.Get("action").([]interface{})[0].(map[string]interface{})
+	mode := tfAction["mode"].(string)
+	timeout := tfAction["timeout"].(int)
+
+	if mode == "simulate" || mode == "ban" {
+		if timeout == 0 {
+			return cloudflare.RateLimitAction{}, fmt.Errorf("rate limit timeout must be set if the 'mode' is simulate or ban")
+		}
+	}
 
 	action := cloudflare.RateLimitAction{
-		Mode:    tfAction["mode"].(string),
-		Timeout: tfAction["timeout"].(int),
+		Mode:    mode,
+		Timeout: timeout,
 	}
 
 	if _, ok := tfAction["response"]; ok && len(tfAction["response"].([]interface{})) > 0 {
@@ -359,7 +375,7 @@ func expandRateLimitAction(d *schema.ResourceData) cloudflare.RateLimitAction {
 			Body:        tfActionResponse["body"].(string),
 		}
 	}
-	return action
+	return action, nil
 }
 
 func expandRateLimitCorrelate(d *schema.ResourceData) (correlate cloudflare.RateLimitCorrelate, err error) {

Do you want to try this and let me know how you go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's absolutely brilliant, thanks for your help. I'd used fmt.Errorf to raise errors, but hadn't wrapped them with errors.Wrap. This now raises errors correctly.

I expanded the logic to throw an error when timeout values are provided for challenge and js_challenge modes as the Cloudflare API won't accept them.

@ghost ghost added kind/documentation Categorizes issue or PR as related to documentation. size/M and removed size/S labels Dec 7, 2018
@jdarley
Copy link
Contributor Author

jdarley commented Dec 7, 2018

The previous commit was just my current work in progress, unfortunately it didn't error during the plan or the apply phases and let the underlying error form the API bubble out.

I'm just in the process of applying the diff above and testing it.

@ghost ghost added size/L and removed size/M labels Dec 7, 2018
@jacobbednarz
Copy link
Member

@jdarley is this ready for another review?

@jdarley
Copy link
Contributor Author

jdarley commented Dec 21, 2018

I think it is @jacobbednarz. I've tested it manually by attempting to create challenge responses with a timeout and block/simulate without a timeout.

I've introduced a couple of acceptance tests which I think will test this behaviour, though I've not yet sorted an environment to run the full suite of acceptance tests against.

period = 1
action {
mode = "challenge"
timeoute = 60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
timeoute = 60
timeout = 60

@jacobbednarz
Copy link
Member

I know they weren't all your changes however the period attribute is too low for a standard enterprise account and throws an entitlement error in the tests. Let's set them to 10 as that gets all the tests passing. Here is a patch to make this faster for you.

diff --git a/cloudflare/resource_cloudflare_rate_limit_test.go b/cloudflare/resource_cloudflare_rate_limit_test.go
index 1a1ecbcad21e86c058420a5c9fdfb355064838b9..203142bb41028dc934a9501e265b88a1dd8a5d40 100644
--- a/cloudflare/resource_cloudflare_rate_limit_test.go
+++ b/cloudflare/resource_cloudflare_rate_limit_test.go
@@ -326,7 +326,7 @@ func testAccCheckCloudflareRateLimitConfigBasic(zone, id string) string {
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 1000
-  period = 1
+  period = 10
   action {
     mode = "simulate"
     timeout = 86400
@@ -339,7 +339,7 @@ func testAccCheckCloudflareRateLimitConfigMatchingUrl(zone, id string) string {
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 1000
-  period = 1
+  period = 10
   match {
     request {
       url_pattern = "%[2]s/tfacc-url-%[1]s"
@@ -357,7 +357,7 @@ func testAccCheckCloudflareRateLimitConfigFullySpecified(zone, id string) string
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 2000
-  period = 2
+  period = 10
   match {
     request {
       url_pattern = "%[2]s/tfacc-full-%[1]s"
@@ -391,7 +391,7 @@ func testAccCheckCloudflareRateLimitChallengeConfigBasic(zone, id string) string
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 1000
-  period = 1
+  period = 10
   action {
     mode = "challenge"
   }
@@ -403,7 +403,7 @@ func testAccCheckCloudflareRateLimitConfigWithoutTimeout(zone, id string) string
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 1000
-  period = 1
+  period = 10
   action {
     mode = "simulate"
   }
@@ -415,7 +415,7 @@ func testAccCheckCloudflareRateLimitChallengeConfigWithTimeout(zone, id string)
 resource "cloudflare_rate_limit" "%[1]s" {
   zone = "%[2]s"
   threshold = 1000
-  period = 1
+  period = 10
   action {
     mode = "challenge"
     timeoute = 60

@jacobbednarz
Copy link
Member

Very close @jdarley! Once we address the timeoute typo and the period changes, we're good to go! Here are the integration tests all passing with those two changes.

$ CLOUDFLARE_DOMAIN="$CF_DOMAIN" \
  CLOUDFLARE_EMAIL="$CF_EMAIL" \
  CLOUDFLARE_TOKEN="$CF_API_TOKEN" \
  TF_ACC=1 \
  go test -v github.com/terraform-providers/terraform-provider-cloudflare/cloudflare -run "TestAccCloudflareRateLimit_*" -timeout 120m

=== RUN   TestAccCloudflareRateLimit_Import
=== PAUSE TestAccCloudflareRateLimit_Import
=== RUN   TestAccCloudflareRateLimit_Basic
=== PAUSE TestAccCloudflareRateLimit_Basic
=== RUN   TestAccCloudflareRateLimitChallenge_Basic
=== PAUSE TestAccCloudflareRateLimitChallenge_Basic
=== RUN   TestAccCloudflareRateLimit_FullySpecified
=== PAUSE TestAccCloudflareRateLimit_FullySpecified
=== RUN   TestAccCloudflareRateLimit_Update
=== PAUSE TestAccCloudflareRateLimit_Update
=== RUN   TestAccCloudflareRateLimit_CreateAfterManualDestroy
=== PAUSE TestAccCloudflareRateLimit_CreateAfterManualDestroy
=== RUN   TestAccCloudflareRateLimit_WithoutTimeout
=== PAUSE TestAccCloudflareRateLimit_WithoutTimeout
=== RUN   TestAccCloudflareRateLimit_ChallengeWithTimeout
=== PAUSE TestAccCloudflareRateLimit_ChallengeWithTimeout
=== CONT  TestAccCloudflareRateLimit_Import
=== CONT  TestAccCloudflareRateLimit_WithoutTimeout
=== CONT  TestAccCloudflareRateLimit_CreateAfterManualDestroy
=== CONT  TestAccCloudflareRateLimit_FullySpecified
--- PASS: TestAccCloudflareRateLimit_WithoutTimeout (0.02s)
=== CONT  TestAccCloudflareRateLimit_Update
--- PASS: TestAccCloudflareRateLimit_FullySpecified (3.30s)
=== CONT  TestAccCloudflareRateLimitChallenge_Basic
--- PASS: TestAccCloudflareRateLimit_Import (3.58s)
=== CONT  TestAccCloudflareRateLimit_Basic
--- PASS: TestAccCloudflareRateLimit_Update (4.78s)
=== CONT  TestAccCloudflareRateLimit_ChallengeWithTimeout
--- PASS: TestAccCloudflareRateLimit_ChallengeWithTimeout (0.01s)
--- PASS: TestAccCloudflareRateLimitChallenge_Basic (2.93s)
--- PASS: TestAccCloudflareRateLimit_Basic (2.92s)
--- PASS: TestAccCloudflareRateLimit_CreateAfterManualDestroy (6.84s)
PASS
ok  	github.com/terraform-providers/terraform-provider-cloudflare/cloudflare	6.865s

@jdarley
Copy link
Contributor Author

jdarley commented Jan 6, 2019

Apologies for not getting this sorted sooner @jacobbednarz - I've had a couple of weeks completely away from the screen. Hopefully this last commit will get it over the line!

@jacobbednarz
Copy link
Member

No sweat @jdarley. Glad to hear you're taking time away from the screens 😄

Copy link
Member

@jacobbednarz jacobbednarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚢

@patryk patryk merged commit 340950a into cloudflare:master Jan 7, 2019
@patryk
Copy link
Contributor

patryk commented Jan 7, 2019

Thanks @jdarley!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/documentation Categorizes issue or PR as related to documentation. size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants