diff --git a/aws/resource_aws_waf_ipset.go b/aws/resource_aws_waf_ipset.go index e1e2746bf7e..68214e5f1cd 100644 --- a/aws/resource_aws_waf_ipset.go +++ b/aws/resource_aws_waf_ipset.go @@ -11,6 +11,9 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) +// WAF requires UpdateIPSet operations be split into batches of 1000 Updates +const wafUpdateIPSetUpdatesLimit = 1000 + func resourceAwsWafIPSet() *schema.Resource { return &schema.Resource{ Create: resourceAwsWafIPSetCreate, @@ -138,7 +141,7 @@ func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error { noDescriptors := []interface{}{} err := updateWafIpSetDescriptors(d.Id(), oldDescriptors, noDescriptors, conn) if err != nil { - return fmt.Errorf("Error updating IPSetDescriptors: %s", err) + return fmt.Errorf("Error Deleting IPSetDescriptors: %s", err) } } @@ -159,25 +162,28 @@ func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error { } func updateWafIpSetDescriptors(id string, oldD, newD []interface{}, conn *waf.WAF) error { - wr := newWafRetryer(conn, "global") - _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { - req := &waf.UpdateIPSetInput{ - ChangeToken: token, - IPSetId: aws.String(id), - Updates: diffWafIpSetDescriptors(oldD, newD), + for _, ipSetUpdates := range diffWafIpSetDescriptors(oldD, newD) { + wr := newWafRetryer(conn, "global") + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateIPSetInput{ + ChangeToken: token, + IPSetId: aws.String(id), + Updates: ipSetUpdates, + } + log.Printf("[INFO] Updating IPSet descriptors: %s", req) + return conn.UpdateIPSet(req) + }) + if err != nil { + return fmt.Errorf("Error Updating WAF IPSet: %s", err) } - log.Printf("[INFO] Updating IPSet descriptors: %s", req) - return conn.UpdateIPSet(req) - }) - if err != nil { - return fmt.Errorf("Error Updating WAF IPSet: %s", err) } return nil } -func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate { - updates := make([]*waf.IPSetUpdate, 0) +func diffWafIpSetDescriptors(oldD, newD []interface{}) [][]*waf.IPSetUpdate { + updates := make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit) + updatesBatches := make([][]*waf.IPSetUpdate, 0) for _, od := range oldD { descriptor := od.(map[string]interface{}) @@ -187,6 +193,11 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate { continue } + if len(updates) == wafUpdateIPSetUpdatesLimit { + updatesBatches = append(updatesBatches, updates) + updates = make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit) + } + updates = append(updates, &waf.IPSetUpdate{ Action: aws.String(waf.ChangeActionDelete), IPSetDescriptor: &waf.IPSetDescriptor{ @@ -199,6 +210,11 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate { for _, nd := range newD { descriptor := nd.(map[string]interface{}) + if len(updates) == wafUpdateIPSetUpdatesLimit { + updatesBatches = append(updatesBatches, updates) + updates = make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit) + } + updates = append(updates, &waf.IPSetUpdate{ Action: aws.String(waf.ChangeActionInsert), IPSetDescriptor: &waf.IPSetDescriptor{ @@ -207,5 +223,6 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate { }, }) } - return updates + updatesBatches = append(updatesBatches, updates) + return updatesBatches } diff --git a/aws/resource_aws_waf_ipset_test.go b/aws/resource_aws_waf_ipset_test.go index de7b56aaded..0a6bcad3e9a 100644 --- a/aws/resource_aws_waf_ipset_test.go +++ b/aws/resource_aws_waf_ipset_test.go @@ -2,8 +2,10 @@ package aws import ( "fmt" + "net" "reflect" "regexp" + "strings" "testing" "github.com/hashicorp/terraform/helper/resource" @@ -169,6 +171,46 @@ func TestAccAWSWafIPSet_noDescriptors(t *testing.T) { }) } +func TestAccAWSWafIPSet_IpSetDescriptors_1000UpdateLimit(t *testing.T) { + var ipset waf.IPSet + ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5)) + resourceName := "aws_waf_ipset.ipset" + + incrementIP := func(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } + } + + // Generate 2048 IPs + ip, ipnet, err := net.ParseCIDR("10.0.0.0/21") + if err != nil { + t.Fatal(err) + } + ipSetDescriptors := make([]string, 0, 2048) + for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); incrementIP(ip) { + ipSetDescriptors = append(ipSetDescriptors, fmt.Sprintf("ip_set_descriptors {\ntype=\"IPV4\"\nvalue=\"%s/32\"\n}", ip)) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafIPSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafIPSetConfig_IpSetDescriptors(ipsetName, strings.Join(ipSetDescriptors, "\n")), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafIPSetExists(resourceName, &ipset), + resource.TestCheckResourceAttr(resourceName, "ip_set_descriptors.#", "2048"), + ), + }, + }, + }) +} + func TestDiffWafIpSetDescriptors(t *testing.T) { testCases := []struct { Old []interface{} @@ -401,6 +443,13 @@ func testAccAWSWafIPSetConfigChangeIPSetDescriptors(name string) string { }`, name) } +func testAccAWSWafIPSetConfig_IpSetDescriptors(name, ipSetDescriptors string) string { + return fmt.Sprintf(`resource "aws_waf_ipset" "ipset" { + name = "%s" +%s +}`, name, ipSetDescriptors) +} + func testAccAWSWafIPSetConfig_noDescriptors(name string) string { return fmt.Sprintf(`resource "aws_waf_ipset" "ipset" { name = "%s" diff --git a/aws/resource_aws_wafregional_ipset.go b/aws/resource_aws_wafregional_ipset.go index a3ff2003c12..9989ab3605a 100644 --- a/aws/resource_aws_wafregional_ipset.go +++ b/aws/resource_aws_wafregional_ipset.go @@ -143,7 +143,7 @@ func resourceAwsWafRegionalIPSetDelete(d *schema.ResourceData, meta interface{}) err := updateIPSetResourceWR(d.Id(), oldD, noD, conn, region) if err != nil { - return fmt.Errorf("Error Removing IPSetDescriptors: %s", err) + return fmt.Errorf("Error Deleting IPSetDescriptors: %s", err) } } @@ -164,20 +164,21 @@ func resourceAwsWafRegionalIPSetDelete(d *schema.ResourceData, meta interface{}) } func updateIPSetResourceWR(id string, oldD, newD []interface{}, conn *wafregional.WAFRegional, region string) error { - - wr := newWafRegionalRetryer(conn, region) - _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { - req := &waf.UpdateIPSetInput{ - ChangeToken: token, - IPSetId: aws.String(id), - Updates: diffWafIpSetDescriptors(oldD, newD), + for _, ipSetUpdates := range diffWafIpSetDescriptors(oldD, newD) { + wr := newWafRegionalRetryer(conn, region) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateIPSetInput{ + ChangeToken: token, + IPSetId: aws.String(id), + Updates: ipSetUpdates, + } + log.Printf("[INFO] Updating IPSet descriptor: %s", req) + + return conn.UpdateIPSet(req) + }) + if err != nil { + return fmt.Errorf("Error Updating WAF IPSet: %s", err) } - log.Printf("[INFO] Updating IPSet descriptor: %s", req) - - return conn.UpdateIPSet(req) - }) - if err != nil { - return fmt.Errorf("Error Updating WAF IPSet: %s", err) } return nil diff --git a/aws/resource_aws_wafregional_ipset_test.go b/aws/resource_aws_wafregional_ipset_test.go index 6bf727cbde9..0e7035f4855 100644 --- a/aws/resource_aws_wafregional_ipset_test.go +++ b/aws/resource_aws_wafregional_ipset_test.go @@ -2,8 +2,10 @@ package aws import ( "fmt" + "net" "reflect" "regexp" + "strings" "testing" "github.com/hashicorp/terraform/helper/resource" @@ -142,6 +144,46 @@ func TestAccAWSWafRegionalIPSet_changeDescriptors(t *testing.T) { }) } +func TestAccAWSWafRegionalIPSet_IpSetDescriptors_1000UpdateLimit(t *testing.T) { + var ipset waf.IPSet + ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5)) + resourceName := "aws_wafregional_ipset.ipset" + + incrementIP := func(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } + } + + // Generate 2048 IPs + ip, ipnet, err := net.ParseCIDR("10.0.0.0/21") + if err != nil { + t.Fatal(err) + } + ipSetDescriptors := make([]string, 0, 2048) + for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); incrementIP(ip) { + ipSetDescriptors = append(ipSetDescriptors, fmt.Sprintf("ip_set_descriptor {\ntype=\"IPV4\"\nvalue=\"%s/32\"\n}", ip)) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalIPSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalIPSetConfig_IpSetDescriptors(ipsetName, strings.Join(ipSetDescriptors, "\n")), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegionalIPSetExists(resourceName, &ipset), + resource.TestCheckResourceAttr(resourceName, "ip_set_descriptor.#", "2048"), + ), + }, + }, + }) +} + func TestAccAWSWafRegionalIPSet_noDescriptors(t *testing.T) { var ipset waf.IPSet ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5)) @@ -398,6 +440,13 @@ func testAccAWSWafRegionalIPSetConfigChangeIPSetDescriptors(name string) string }`, name) } +func testAccAWSWafRegionalIPSetConfig_IpSetDescriptors(name, ipSetDescriptors string) string { + return fmt.Sprintf(`resource "aws_wafregional_ipset" "ipset" { + name = "%s" +%s +}`, name, ipSetDescriptors) +} + func testAccAWSWafRegionalIPSetConfig_noDescriptors(name string) string { return fmt.Sprintf(`resource "aws_wafregional_ipset" "ipset" { name = "%s"