diff --git a/builtin/providers/aws/resource_aws_waf_byte_match_set.go b/builtin/providers/aws/resource_aws_waf_byte_match_set.go index c28359351..53f3e93b8 100644 --- a/builtin/providers/aws/resource_aws_waf_byte_match_set.go +++ b/builtin/providers/aws/resource_aws_waf_byte_match_set.go @@ -1,6 +1,7 @@ package aws import ( + "fmt" "log" "github.com/aws/aws-sdk-go/aws" @@ -106,34 +107,47 @@ func resourceAwsWafByteMatchSetRead(d *schema.ResourceData, meta interface{}) er } d.Set("name", resp.ByteMatchSet.Name) + d.Set("byte_match_tuples", flattenWafByteMatchTuples(resp.ByteMatchSet.ByteMatchTuples)) return nil } func resourceAwsWafByteMatchSetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafconn + log.Printf("[INFO] Updating ByteMatchSet: %s", d.Get("name").(string)) - err := updateByteMatchSetResource(d, meta, waf.ChangeActionInsert) - if err != nil { - return errwrap.Wrapf("[ERROR] Error updating ByteMatchSet: {{err}}", err) + + if d.HasChange("byte_match_tuples") { + o, n := d.GetChange("byte_match_tuples") + oldT, newT := o.(*schema.Set).List(), n.(*schema.Set).List() + err := updateByteMatchSetResource(d.Id(), oldT, newT, conn) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating ByteMatchSet: {{err}}", err) + } } + return resourceAwsWafByteMatchSetRead(d, meta) } func resourceAwsWafByteMatchSetDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).wafconn - log.Printf("[INFO] Deleting ByteMatchSet: %s", d.Get("name").(string)) - err := updateByteMatchSetResource(d, meta, waf.ChangeActionDelete) - if err != nil { - return errwrap.Wrapf("[ERROR] Error deleting ByteMatchSet: {{err}}", err) + oldTuples := d.Get("byte_match_tuples").(*schema.Set).List() + if len(oldTuples) > 0 { + noTuples := []interface{}{} + err := updateByteMatchSetResource(d.Id(), oldTuples, noTuples, conn) + if err != nil { + return fmt.Errorf("Error updating ByteMatchSet: %s", err) + } } wr := newWafRetryer(conn, "global") - _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.DeleteByteMatchSetInput{ ChangeToken: token, ByteMatchSetId: aws.String(d.Id()), } + log.Printf("[INFO] Deleting WAF ByteMatchSet: %s", req) return conn.DeleteByteMatchSet(req) }) if err != nil { @@ -143,29 +157,13 @@ func resourceAwsWafByteMatchSetDelete(d *schema.ResourceData, meta interface{}) return nil } -func updateByteMatchSetResource(d *schema.ResourceData, meta interface{}, ChangeAction string) error { - conn := meta.(*AWSClient).wafconn - +func updateByteMatchSetResource(id string, oldT, newT []interface{}, conn *waf.WAF) error { wr := newWafRetryer(conn, "global") _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.UpdateByteMatchSetInput{ ChangeToken: token, - ByteMatchSetId: aws.String(d.Id()), - } - - ByteMatchTuples := d.Get("byte_match_tuples").(*schema.Set) - for _, ByteMatchTuple := range ByteMatchTuples.List() { - ByteMatch := ByteMatchTuple.(map[string]interface{}) - ByteMatchUpdate := &waf.ByteMatchSetUpdate{ - Action: aws.String(ChangeAction), - ByteMatchTuple: &waf.ByteMatchTuple{ - FieldToMatch: expandFieldToMatch(ByteMatch["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), - PositionalConstraint: aws.String(ByteMatch["positional_constraint"].(string)), - TargetString: []byte(ByteMatch["target_string"].(string)), - TextTransformation: aws.String(ByteMatch["text_transformation"].(string)), - }, - } - req.Updates = append(req.Updates, ByteMatchUpdate) + ByteMatchSetId: aws.String(id), + Updates: diffWafByteMatchSetTuples(oldT, newT), } return conn.UpdateByteMatchSet(req) @@ -177,6 +175,23 @@ func updateByteMatchSetResource(d *schema.ResourceData, meta interface{}, Change return nil } +func flattenWafByteMatchTuples(bmt []*waf.ByteMatchTuple) []interface{} { + out := make([]interface{}, len(bmt), len(bmt)) + for i, t := range bmt { + m := make(map[string]interface{}) + + if t.FieldToMatch != nil { + m["field_to_match"] = flattenFieldToMatch(t.FieldToMatch) + } + m["positional_constraint"] = *t.PositionalConstraint + m["target_string"] = string(t.TargetString) + m["text_transformation"] = *t.TextTransformation + + out[i] = m + } + return out +} + func expandFieldToMatch(d map[string]interface{}) *waf.FieldToMatch { return &waf.FieldToMatch{ Type: aws.String(d["type"].(string)), @@ -184,9 +199,51 @@ func expandFieldToMatch(d map[string]interface{}) *waf.FieldToMatch { } } -func flattenFieldToMatch(fm *waf.FieldToMatch) map[string]interface{} { +func flattenFieldToMatch(fm *waf.FieldToMatch) []interface{} { m := make(map[string]interface{}) - m["data"] = *fm.Data - m["type"] = *fm.Type - return m + if fm.Data != nil { + m["data"] = *fm.Data + } + if fm.Type != nil { + m["type"] = *fm.Type + } + return []interface{}{m} +} + +func diffWafByteMatchSetTuples(oldT, newT []interface{}) []*waf.ByteMatchSetUpdate { + updates := make([]*waf.ByteMatchSetUpdate, 0) + + for _, ot := range oldT { + tuple := ot.(map[string]interface{}) + + if idx, contains := sliceContainsMap(newT, tuple); contains { + newT = append(newT[:idx], newT[idx+1:]...) + continue + } + + updates = append(updates, &waf.ByteMatchSetUpdate{ + Action: aws.String(waf.ChangeActionDelete), + ByteMatchTuple: &waf.ByteMatchTuple{ + FieldToMatch: expandFieldToMatch(tuple["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + PositionalConstraint: aws.String(tuple["positional_constraint"].(string)), + TargetString: []byte(tuple["target_string"].(string)), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + + for _, nt := range newT { + tuple := nt.(map[string]interface{}) + + updates = append(updates, &waf.ByteMatchSetUpdate{ + Action: aws.String(waf.ChangeActionInsert), + ByteMatchTuple: &waf.ByteMatchTuple{ + FieldToMatch: expandFieldToMatch(tuple["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + PositionalConstraint: aws.String(tuple["positional_constraint"].(string)), + TargetString: []byte(tuple["target_string"].(string)), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + return updates } diff --git a/builtin/providers/aws/resource_aws_waf_byte_match_set_test.go b/builtin/providers/aws/resource_aws_waf_byte_match_set_test.go index c5e0ce213..2e432befb 100644 --- a/builtin/providers/aws/resource_aws_waf_byte_match_set_test.go +++ b/builtin/providers/aws/resource_aws_waf_byte_match_set_test.go @@ -27,10 +27,20 @@ func TestAccAWSWafByteMatchSet_basic(t *testing.T) { Config: testAccAWSWafByteMatchSetConfig(byteMatchSet), Check: resource.ComposeTestCheckFunc( testAccCheckAWSWafByteMatchSetExists("aws_waf_byte_match_set.byte_set", &v), - resource.TestCheckResourceAttr( - "aws_waf_byte_match_set.byte_set", "name", byteMatchSet), - resource.TestCheckResourceAttr( - "aws_waf_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "name", byteMatchSet), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.data", "referer"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.type", "HEADER"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.positional_constraint", "CONTAINS"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.target_string", "badrefer1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.text_transformation", "NONE"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.2991901334.data", "referer"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.2991901334.type", "HEADER"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.positional_constraint", "CONTAINS"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.target_string", "badrefer2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.text_transformation", "NONE"), ), }, }, @@ -71,6 +81,82 @@ func TestAccAWSWafByteMatchSet_changeNameForceNew(t *testing.T) { }) } +func TestAccAWSWafByteMatchSet_changeTuples(t *testing.T) { + var before, after waf.ByteMatchSet + byteMatchSetName := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafByteMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafByteMatchSetConfig(byteMatchSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafByteMatchSetExists("aws_waf_byte_match_set.byte_set", &before), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "name", byteMatchSetName), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.data", "referer"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.type", "HEADER"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.positional_constraint", "CONTAINS"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.target_string", "badrefer1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.text_transformation", "NONE"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.2991901334.data", "referer"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.field_to_match.2991901334.type", "HEADER"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.positional_constraint", "CONTAINS"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.target_string", "badrefer2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.839525137.text_transformation", "NONE"), + ), + }, + { + Config: testAccAWSWafByteMatchSetConfig_changeTuples(byteMatchSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafByteMatchSetExists("aws_waf_byte_match_set.byte_set", &after), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "name", byteMatchSetName), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.data", "referer"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.field_to_match.2991901334.type", "HEADER"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.positional_constraint", "CONTAINS"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.target_string", "badrefer1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.2174619346.text_transformation", "NONE"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.field_to_match.#", "1"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.field_to_match.4253810390.data", "GET"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.field_to_match.4253810390.type", "METHOD"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.positional_constraint", "CONTAINS_WORD"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.target_string", "blah"), + resource.TestCheckResourceAttr("aws_waf_byte_match_set.byte_set", "byte_match_tuples.4224486115.text_transformation", "URL_DECODE"), + ), + }, + }, + }) +} + +func TestAccAWSWafByteMatchSet_noTuples(t *testing.T) { + var byteSet waf.ByteMatchSet + byteMatchSetName := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafByteMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafByteMatchSetConfig_noTuples(byteMatchSetName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafByteMatchSetExists("aws_waf_byte_match_set.byte_set", &byteSet), + resource.TestCheckResourceAttr( + "aws_waf_byte_match_set.byte_set", "name", byteMatchSetName), + resource.TestCheckResourceAttr( + "aws_waf_byte_match_set.byte_set", "byte_match_tuples.#", "0"), + ), + }, + }, + }) +} + func TestAccAWSWafByteMatchSet_disappears(t *testing.T) { var v waf.ByteMatchSet byteMatchSet := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) @@ -248,3 +334,36 @@ resource "aws_waf_byte_match_set" "byte_set" { } }`, name) } + +func testAccAWSWafByteMatchSetConfig_changeTuples(name string) string { + return fmt.Sprintf(` +resource "aws_waf_byte_match_set" "byte_set" { + name = "%s" + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer1" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } + + byte_match_tuples { + text_transformation = "URL_DECODE" + target_string = "blah" + positional_constraint = "CONTAINS_WORD" + field_to_match { + type = "METHOD" + data = "GET" + } + } +}`, name) +} + +func testAccAWSWafByteMatchSetConfig_noTuples(name string) string { + return fmt.Sprintf(` +resource "aws_waf_byte_match_set" "byte_set" { + name = "%s" +}`, name) +}