From 98dae1760d68c612419a207d92f6d8910f7d88b4 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 23 Mar 2018 17:22:02 +0000 Subject: [PATCH] New Resource: aws_waf_regex_pattern_set --- aws/provider.go | 1 + aws/resource_aws_waf_regex_pattern_set.go | 144 +++++++++++ ...resource_aws_waf_regex_pattern_set_test.go | 231 ++++++++++++++++++ aws/waf_helpers.go | 34 +++ website/aws.erb | 4 + .../r/waf_regex_pattern_set.html.markdown | 33 +++ 6 files changed, 447 insertions(+) create mode 100644 aws/resource_aws_waf_regex_pattern_set.go create mode 100644 aws/resource_aws_waf_regex_pattern_set_test.go create mode 100644 website/docs/r/waf_regex_pattern_set.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 927eac1dc8a2..44abf261cc70 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -549,6 +549,7 @@ func Provider() terraform.ResourceProvider { "aws_waf_ipset": resourceAwsWafIPSet(), "aws_waf_rule": resourceAwsWafRule(), "aws_waf_rate_based_rule": resourceAwsWafRateBasedRule(), + "aws_waf_regex_pattern_set": resourceAwsWafRegexPatternSet(), "aws_waf_size_constraint_set": resourceAwsWafSizeConstraintSet(), "aws_waf_web_acl": resourceAwsWafWebAcl(), "aws_waf_xss_match_set": resourceAwsWafXssMatchSet(), diff --git a/aws/resource_aws_waf_regex_pattern_set.go b/aws/resource_aws_waf_regex_pattern_set.go new file mode 100644 index 000000000000..1ebbc58f69a0 --- /dev/null +++ b/aws/resource_aws_waf_regex_pattern_set.go @@ -0,0 +1,144 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsWafRegexPatternSet() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsWafRegexPatternSetCreate, + Read: resourceAwsWafRegexPatternSetRead, + Update: resourceAwsWafRegexPatternSetUpdate, + Delete: resourceAwsWafRegexPatternSetDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "regex_pattern_strings": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceAwsWafRegexPatternSetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafconn + + log.Printf("[INFO] Creating WAF Regex Pattern Set: %s", d.Get("name").(string)) + + wr := newWafRetryer(conn, "global") + out, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + params := &waf.CreateRegexPatternSetInput{ + ChangeToken: token, + Name: aws.String(d.Get("name").(string)), + } + return conn.CreateRegexPatternSet(params) + }) + if err != nil { + return fmt.Errorf("Failed creating WAF Regex Pattern Set: %s", err) + } + resp := out.(*waf.CreateRegexPatternSetOutput) + + d.SetId(*resp.RegexPatternSet.RegexPatternSetId) + + return resourceAwsWafRegexPatternSetUpdate(d, meta) +} + +func resourceAwsWafRegexPatternSetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafconn + log.Printf("[INFO] Reading WAF Regex Pattern Set: %s", d.Get("name").(string)) + params := &waf.GetRegexPatternSetInput{ + RegexPatternSetId: aws.String(d.Id()), + } + + resp, err := conn.GetRegexPatternSet(params) + if err != nil { + // TODO: Replace with a constant once available + // See https://github.com/aws/aws-sdk-go/issues/1856 + if isAWSErr(err, "WAFNonexistentItemException", "") { + log.Printf("[WARN] WAF Regex Pattern Set (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + return err + } + + d.Set("name", resp.RegexPatternSet.Name) + d.Set("regex_pattern_strings", aws.StringValueSlice(resp.RegexPatternSet.RegexPatternStrings)) + + return nil +} + +func resourceAwsWafRegexPatternSetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafconn + + log.Printf("[INFO] Updating WAF Regex Pattern Set: %s", d.Get("name").(string)) + + if d.HasChange("regex_pattern_strings") { + o, n := d.GetChange("regex_pattern_strings") + oldPatterns, newPatterns := o.(*schema.Set).List(), n.(*schema.Set).List() + err := updateWafRegexPatternSetPatternStrings(d.Id(), oldPatterns, newPatterns, conn) + if err != nil { + return fmt.Errorf("Failed updating WAF Regex Pattern Set: %s", err) + } + } + + return resourceAwsWafRegexPatternSetRead(d, meta) +} + +func resourceAwsWafRegexPatternSetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafconn + + oldPatterns := d.Get("regex_pattern_strings").(*schema.Set).List() + if len(oldPatterns) > 0 { + noPatterns := []interface{}{} + err := updateWafRegexPatternSetPatternStrings(d.Id(), oldPatterns, noPatterns, conn) + if err != nil { + return fmt.Errorf("Error updating WAF Regex Pattern Set: %s", err) + } + } + + wr := newWafRetryer(conn, "global") + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.DeleteRegexPatternSetInput{ + ChangeToken: token, + RegexPatternSetId: aws.String(d.Id()), + } + log.Printf("[INFO] Deleting WAF Regex Pattern Set: %s", req) + return conn.DeleteRegexPatternSet(req) + }) + if err != nil { + return fmt.Errorf("Failed deleting WAF Regex Pattern Set: %s", err) + } + + return nil +} + +func updateWafRegexPatternSetPatternStrings(id string, oldPatterns, newPatterns []interface{}, conn *waf.WAF) error { + wr := newWafRetryer(conn, "global") + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateRegexPatternSetInput{ + ChangeToken: token, + RegexPatternSetId: aws.String(id), + Updates: diffWafRegexPatternSetPatternStrings(oldPatterns, newPatterns), + } + + return conn.UpdateRegexPatternSet(req) + }) + if err != nil { + return fmt.Errorf("Failed updating WAF Regex Pattern Set: %s", err) + } + + return nil +} diff --git a/aws/resource_aws_waf_regex_pattern_set_test.go b/aws/resource_aws_waf_regex_pattern_set_test.go new file mode 100644 index 000000000000..ff8c7b550b96 --- /dev/null +++ b/aws/resource_aws_waf_regex_pattern_set_test.go @@ -0,0 +1,231 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSWafRegexPatternSet_basic(t *testing.T) { + var v waf.RegexPatternSet + patternSetName := fmt.Sprintf("tfacc-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegexPatternSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSWafRegexPatternSetConfig(patternSetName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegexPatternSetExists("aws_waf_regex_pattern_set.test", &v), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "name", patternSetName), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.#", "2"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.2848565413", "one"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.3351840846", "two"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegexPatternSet_changePatterns(t *testing.T) { + var before, after waf.RegexPatternSet + patternSetName := fmt.Sprintf("tfacc-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegexPatternSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegexPatternSetConfig(patternSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegexPatternSetExists("aws_waf_regex_pattern_set.test", &before), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "name", patternSetName), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.#", "2"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.2848565413", "one"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.3351840846", "two"), + ), + }, + { + Config: testAccAWSWafRegexPatternSetConfig_changePatterns(patternSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegexPatternSetExists("aws_waf_regex_pattern_set.test", &after), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "name", patternSetName), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.#", "3"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.3351840846", "two"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.2929247714", "three"), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.1294846542", "four"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegexPatternSet_noPatterns(t *testing.T) { + var patternSet waf.RegexPatternSet + patternSetName := fmt.Sprintf("tfacc-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegexPatternSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegexPatternSetConfig_noPatterns(patternSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegexPatternSetExists("aws_waf_regex_pattern_set.test", &patternSet), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "name", patternSetName), + resource.TestCheckResourceAttr("aws_waf_regex_pattern_set.test", "regex_pattern_strings.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegexPatternSet_disappears(t *testing.T) { + var v waf.RegexPatternSet + patternSetName := fmt.Sprintf("tfacc-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegexPatternSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegexPatternSetConfig(patternSetName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegexPatternSetExists("aws_waf_regex_pattern_set.test", &v), + testAccCheckAWSWafRegexPatternSetDisappears(&v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSWafRegexPatternSetDisappears(set *waf.RegexPatternSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).wafconn + + wr := newWafRetryer(conn, "global") + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateRegexPatternSetInput{ + ChangeToken: token, + RegexPatternSetId: set.RegexPatternSetId, + } + + for _, pattern := range set.RegexPatternStrings { + update := &waf.RegexPatternSetUpdate{ + Action: aws.String("DELETE"), + RegexPatternString: pattern, + } + req.Updates = append(req.Updates, update) + } + + return conn.UpdateRegexPatternSet(req) + }) + if err != nil { + return fmt.Errorf("Failed updating WAF Regex Pattern Set: %s", err) + } + + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + opts := &waf.DeleteRegexPatternSetInput{ + ChangeToken: token, + RegexPatternSetId: set.RegexPatternSetId, + } + return conn.DeleteRegexPatternSet(opts) + }) + if err != nil { + return fmt.Errorf("Failed deleting WAF Regex Pattern Set: %s", err) + } + + return nil + } +} + +func testAccCheckAWSWafRegexPatternSetExists(n string, v *waf.RegexPatternSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No WAF Regex Pattern Set ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).wafconn + resp, err := conn.GetRegexPatternSet(&waf.GetRegexPatternSetInput{ + RegexPatternSetId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if *resp.RegexPatternSet.RegexPatternSetId == rs.Primary.ID { + *v = *resp.RegexPatternSet + return nil + } + + return fmt.Errorf("WAF Regex Pattern Set (%s) not found", rs.Primary.ID) + } +} + +func testAccCheckAWSWafRegexPatternSetDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_waf_regex_pattern_set" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).wafconn + resp, err := conn.GetRegexPatternSet(&waf.GetRegexPatternSetInput{ + RegexPatternSetId: aws.String(rs.Primary.ID), + }) + + if err == nil { + if *resp.RegexPatternSet.RegexPatternSetId == rs.Primary.ID { + return fmt.Errorf("WAF Regex Pattern Set %s still exists", rs.Primary.ID) + } + } + + // Return nil if the Regex Pattern Set is already destroyed + if isAWSErr(err, "WAFNonexistentItemException", "") { + return nil + } + + return err + } + + return nil +} + +func testAccAWSWafRegexPatternSetConfig(name string) string { + return fmt.Sprintf(` +resource "aws_waf_regex_pattern_set" "test" { + name = "%s" + regex_pattern_strings = ["one", "two"] +}`, name) +} + +func testAccAWSWafRegexPatternSetConfig_changePatterns(name string) string { + return fmt.Sprintf(` +resource "aws_waf_regex_pattern_set" "test" { + name = "%s" + regex_pattern_strings = ["two", "three", "four"] +}`, name) +} + +func testAccAWSWafRegexPatternSetConfig_noPatterns(name string) string { + return fmt.Sprintf(` +resource "aws_waf_regex_pattern_set" "test" { + name = "%s" +}`, name) +} diff --git a/aws/waf_helpers.go b/aws/waf_helpers.go index bf0e6c6a8b1f..315bedd1f91a 100644 --- a/aws/waf_helpers.go +++ b/aws/waf_helpers.go @@ -151,3 +151,37 @@ func diffWafGeoMatchSetConstraints(oldT, newT []interface{}) []*waf.GeoMatchSetU } return updates } + +func diffWafRegexPatternSetPatternStrings(oldPatterns, newPatterns []interface{}) []*waf.RegexPatternSetUpdate { + updates := make([]*waf.RegexPatternSetUpdate, 0) + + for _, op := range oldPatterns { + if idx, contains := sliceContainsString(newPatterns, op.(string)); contains { + newPatterns = append(newPatterns[:idx], newPatterns[idx+1:]...) + continue + } + + updates = append(updates, &waf.RegexPatternSetUpdate{ + Action: aws.String(waf.ChangeActionDelete), + RegexPatternString: aws.String(op.(string)), + }) + } + + for _, np := range newPatterns { + updates = append(updates, &waf.RegexPatternSetUpdate{ + Action: aws.String(waf.ChangeActionInsert), + RegexPatternString: aws.String(np.(string)), + }) + } + return updates +} + +func sliceContainsString(slice []interface{}, s string) (int, bool) { + for idx, value := range slice { + v := value.(string) + if v == s { + return idx, true + } + } + return -1, false +} diff --git a/website/aws.erb b/website/aws.erb index df9502b5be55..b8ed67d1791f 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1480,6 +1480,10 @@ aws_waf_rate_based_rule + > + aws_waf_regex_pattern_set + + > aws_waf_rule diff --git a/website/docs/r/waf_regex_pattern_set.html.markdown b/website/docs/r/waf_regex_pattern_set.html.markdown new file mode 100644 index 000000000000..ca9bcb9588e6 --- /dev/null +++ b/website/docs/r/waf_regex_pattern_set.html.markdown @@ -0,0 +1,33 @@ +--- +layout: "aws" +page_title: "AWS: waf_regex_pattern_set" +sidebar_current: "docs-aws-resource-waf-regex-pattern-set" +description: |- + Provides a AWS WAF Regex Pattern Set resource. +--- + +# aws_waf_regex_pattern_set + +Provides a WAF Regex Pattern Set Resource + +## Example Usage + +```hcl +resource "aws_waf_regex_pattern_set" "example" { + name = "tf_waf_regex_pattern_set" + regex_pattern_strings = ["one", "two"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name or description of the Regex Pattern Set. +* `regex_pattern_strings` - (Optional) A list of regular expression (regex) patterns that you want AWS WAF to search for, such as `B[a@]dB[o0]t`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the WAF Regex Pattern Set.