diff --git a/builtin/providers/aws/resource_aws_waf_xss_match_set.go b/builtin/providers/aws/resource_aws_waf_xss_match_set.go index 222940dd0..c6ea0d630 100644 --- a/builtin/providers/aws/resource_aws_waf_xss_match_set.go +++ b/builtin/providers/aws/resource_aws_waf_xss_match_set.go @@ -1,6 +1,7 @@ package aws import ( + "fmt" "log" "github.com/aws/aws-sdk-go/aws" @@ -25,7 +26,7 @@ func resourceAwsWafXssMatchSet() *schema.Resource { }, "xss_match_tuples": &schema.Schema{ Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "field_to_match": { @@ -99,30 +100,41 @@ func resourceAwsWafXssMatchSetRead(d *schema.ResourceData, meta interface{}) err } d.Set("name", resp.XssMatchSet.Name) + d.Set("xss_match_tuples", flattenWafXssMatchTuples(resp.XssMatchSet.XssMatchTuples)) return nil } func resourceAwsWafXssMatchSetUpdate(d *schema.ResourceData, meta interface{}) error { - log.Printf("[INFO] Updating XssMatchSet: %s", d.Get("name").(string)) - err := updateXssMatchSetResource(d, meta, waf.ChangeActionInsert) - if err != nil { - return errwrap.Wrapf("[ERROR] Error updating XssMatchSet: {{err}}", err) + conn := meta.(*AWSClient).wafconn + + if d.HasChange("xss_match_tuples") { + o, n := d.GetChange("xss_match_tuples") + oldT, newT := o.(*schema.Set).List(), n.(*schema.Set).List() + + err := updateXssMatchSetResource(d.Id(), oldT, newT, conn) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating XssMatchSet: {{err}}", err) + } } + return resourceAwsWafXssMatchSetRead(d, meta) } func resourceAwsWafXssMatchSetDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).wafconn - log.Printf("[INFO] Deleting XssMatchSet: %s", d.Get("name").(string)) - err := updateXssMatchSetResource(d, meta, waf.ChangeActionDelete) - if err != nil { - return errwrap.Wrapf("[ERROR] Error deleting XssMatchSet: {{err}}", err) + oldTuples := d.Get("xss_match_tuples").(*schema.Set).List() + if len(oldTuples) > 0 { + noTuples := []interface{}{} + err := updateXssMatchSetResource(d.Id(), oldTuples, noTuples, conn) + if err != nil { + return fmt.Errorf("Error updating IPSetDescriptors: %s", err) + } } wr := newWafRetryer(conn, "global") - _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.DeleteXssMatchSetInput{ ChangeToken: token, XssMatchSetId: aws.String(d.Id()), @@ -137,29 +149,16 @@ func resourceAwsWafXssMatchSetDelete(d *schema.ResourceData, meta interface{}) e return nil } -func updateXssMatchSetResource(d *schema.ResourceData, meta interface{}, ChangeAction string) error { - conn := meta.(*AWSClient).wafconn - +func updateXssMatchSetResource(id string, oldT, newT []interface{}, conn *waf.WAF) error { wr := newWafRetryer(conn, "global") _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.UpdateXssMatchSetInput{ ChangeToken: token, - XssMatchSetId: aws.String(d.Id()), - } - - xssMatchTuples := d.Get("xss_match_tuples").(*schema.Set) - for _, xssMatchTuple := range xssMatchTuples.List() { - xmt := xssMatchTuple.(map[string]interface{}) - xssMatchTupleUpdate := &waf.XssMatchSetUpdate{ - Action: aws.String(ChangeAction), - XssMatchTuple: &waf.XssMatchTuple{ - FieldToMatch: expandFieldToMatch(xmt["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), - TextTransformation: aws.String(xmt["text_transformation"].(string)), - }, - } - req.Updates = append(req.Updates, xssMatchTupleUpdate) + XssMatchSetId: aws.String(id), + Updates: diffWafXssMatchSetTuples(oldT, newT), } + log.Printf("[INFO] Updating XssMatchSet tuples: %s", req) return conn.UpdateXssMatchSet(req) }) if err != nil { @@ -168,3 +167,48 @@ func updateXssMatchSetResource(d *schema.ResourceData, meta interface{}, ChangeA return nil } + +func flattenWafXssMatchTuples(ts []*waf.XssMatchTuple) []interface{} { + out := make([]interface{}, len(ts), len(ts)) + for i, t := range ts { + m := make(map[string]interface{}) + m["field_to_match"] = flattenFieldToMatch(t.FieldToMatch) + m["text_transformation"] = *t.TextTransformation + out[i] = m + } + return out +} + +func diffWafXssMatchSetTuples(oldT, newT []interface{}) []*waf.XssMatchSetUpdate { + updates := make([]*waf.XssMatchSetUpdate, 0) + + for _, od := range oldT { + tuple := od.(map[string]interface{}) + + if idx, contains := sliceContainsMap(newT, tuple); contains { + newT = append(newT[:idx], newT[idx+1:]...) + continue + } + + updates = append(updates, &waf.XssMatchSetUpdate{ + Action: aws.String(waf.ChangeActionDelete), + XssMatchTuple: &waf.XssMatchTuple{ + FieldToMatch: expandFieldToMatch(tuple["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + + for _, nd := range newT { + tuple := nd.(map[string]interface{}) + + updates = append(updates, &waf.XssMatchSetUpdate{ + Action: aws.String(waf.ChangeActionInsert), + XssMatchTuple: &waf.XssMatchTuple{ + FieldToMatch: expandFieldToMatch(tuple["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + return updates +} diff --git a/builtin/providers/aws/resource_aws_waf_xss_match_set_test.go b/builtin/providers/aws/resource_aws_waf_xss_match_set_test.go index b2d223086..175e61946 100644 --- a/builtin/providers/aws/resource_aws_waf_xss_match_set_test.go +++ b/builtin/providers/aws/resource_aws_waf_xss_match_set_test.go @@ -31,6 +31,22 @@ func TestAccAWSWafXssMatchSet_basic(t *testing.T) { "aws_waf_xss_match_set.xss_match_set", "name", xssMatchSet), resource.TestCheckResourceAttr( "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.#", "2"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.2316364334.data", ""), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.2316364334.type", "QUERY_STRING"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.text_transformation", "NONE"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.3756326843.data", ""), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.3756326843.type", "URI"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.text_transformation", "NONE"), ), }, }, @@ -92,6 +108,94 @@ func TestAccAWSWafXssMatchSet_disappears(t *testing.T) { }) } +func TestAccAWSWafXssMatchSet_changeTuples(t *testing.T) { + var before, after waf.XssMatchSet + setName := fmt.Sprintf("xssMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafXssMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafXssMatchSetConfig(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafXssMatchSetExists("aws_waf_xss_match_set.xss_match_set", &before), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.#", "2"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.2316364334.data", ""), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.field_to_match.2316364334.type", "QUERY_STRING"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2018581549.text_transformation", "NONE"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.3756326843.data", ""), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.field_to_match.3756326843.type", "URI"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2786024938.text_transformation", "NONE"), + ), + }, + { + Config: testAccAWSWafXssMatchSetConfig_changeTuples(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafXssMatchSetExists("aws_waf_xss_match_set.xss_match_set", &after), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.#", "2"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2893682529.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2893682529.field_to_match.4253810390.data", "GET"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2893682529.field_to_match.4253810390.type", "METHOD"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.2893682529.text_transformation", "HTML_ENTITY_DECODE"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.4270311415.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.4270311415.field_to_match.281401076.data", ""), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.4270311415.field_to_match.281401076.type", "BODY"), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.4270311415.text_transformation", "CMD_LINE"), + ), + }, + }, + }) +} + +func TestAccAWSWafXssMatchSet_noTuples(t *testing.T) { + var ipset waf.XssMatchSet + setName := fmt.Sprintf("xssMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafXssMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafXssMatchSetConfig_noTuples(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafXssMatchSetExists("aws_waf_xss_match_set.xss_match_set", &ipset), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_waf_xss_match_set.xss_match_set", "xss_match_tuples.#", "0"), + ), + }, + }, + }) +} + func testAccCheckAWSWafXssMatchSetDisappears(v *waf.XssMatchSet) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).wafconn @@ -232,3 +336,31 @@ resource "aws_waf_xss_match_set" "xss_match_set" { } }`, name) } + +func testAccAWSWafXssMatchSetConfig_changeTuples(name string) string { + return fmt.Sprintf(` +resource "aws_waf_xss_match_set" "xss_match_set" { + name = "%s" + xss_match_tuples { + text_transformation = "CMD_LINE" + field_to_match { + type = "BODY" + } + } + + xss_match_tuples { + text_transformation = "HTML_ENTITY_DECODE" + field_to_match { + type = "METHOD" + data = "GET" + } + } +}`, name) +} + +func testAccAWSWafXssMatchSetConfig_noTuples(name string) string { + return fmt.Sprintf(` +resource "aws_waf_xss_match_set" "xss_match_set" { + name = "%s" +}`, name) +}