From 45ba6864d58835e795ab999e1e6b3b2ebc3e9b8c Mon Sep 17 00:00:00 2001 From: Danniel Magno Date: Mon, 3 Jul 2017 21:16:05 -0300 Subject: [PATCH] Add support for aws_wafregional_web_acl_association --- aws/provider.go | 1 + ...rce_aws_wafregional_web_acl_association.go | 135 +++++++++++++++ ...ws_wafregional_web_acl_association_test.go | 161 ++++++++++++++++++ website/aws.erb | 4 + ...regional_web_acl_association.html.markdown | 89 ++++++++++ 5 files changed, 390 insertions(+) create mode 100644 aws/resource_aws_wafregional_web_acl_association.go create mode 100644 aws/resource_aws_wafregional_web_acl_association_test.go create mode 100644 website/docs/r/wafregional_web_acl_association.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 434652b171c..af3ae3fbe9c 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -475,6 +475,7 @@ func Provider() terraform.ResourceProvider { "aws_waf_sql_injection_match_set": resourceAwsWafSqlInjectionMatchSet(), "aws_wafregional_byte_match_set": resourceAwsWafRegionalByteMatchSet(), "aws_wafregional_ipset": resourceAwsWafRegionalIPSet(), + "aws_wafregional_web_acl_association": resourceAwsWafRegionalWebAclAssociation(), }, ConfigureFunc: providerConfigure, } diff --git a/aws/resource_aws_wafregional_web_acl_association.go b/aws/resource_aws_wafregional_web_acl_association.go new file mode 100644 index 00000000000..e0a736a3ade --- /dev/null +++ b/aws/resource_aws_wafregional_web_acl_association.go @@ -0,0 +1,135 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/wafregional" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsWafRegionalWebAclAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsWafRegionalWebAclAssociationCreate, + Read: resourceAwsWafRegionalWebAclAssociationRead, + Update: resourceAwsWafRegionalWebAclAssociationUpdate, + Delete: resourceAwsWafRegionalWebAclAssociationDelete, + + Schema: map[string]*schema.Schema{ + "web_acl_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "resource_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsWafRegionalWebAclAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + log.Printf( + "[INFO] Creating WAF Regional Web ACL association: %s => %s", + d.Get("web_acl_id").(string), + d.Get("resource_arn").(string)) + + params := &wafregional.AssociateWebACLInput{ + WebACLId: aws.String(d.Get("web_acl_id").(string)), + ResourceArn: aws.String(d.Get("resource_arn").(string)), + } + + // create association and wait on retryable error + // no response body + var err error + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + _, err = conn.AssociateWebACL(params) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "WAFUnavailableEntityException" { + return resource.RetryableError(awsErr) + } + } + return resource.NonRetryableError(err) + } + return nil + }) + if err != nil { + return err + } + + // Store association id + d.SetId(fmt.Sprintf("%s:%s", *params.WebACLId, *params.ResourceArn)) + + return nil +} + +func resourceAwsWafRegionalWebAclAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + web_acl_id, resource_arn := resourceAwsWafRegionalWebAclAssociationParseId(d.Id()) + + // List all resources for Web ACL and see if we get a match + params := &wafregional.ListResourcesForWebACLInput{ + WebACLId: aws.String(web_acl_id), + } + + resp, err := conn.ListResourcesForWebACL(params) + if err != nil { + return err + } + + // Find match + found := false + for _, list_resource_arn := range resp.ResourceArns { + if resource_arn == *list_resource_arn { + found = true + break + } + } + if !found { + // It seems it doesn't exist anymore, so clear the ID + d.SetId("") + } + + return nil +} + +func resourceAwsWafRegionalWebAclAssociationUpdate(d *schema.ResourceData, meta interface{}) error { + return resourceAwsWafRegionalWebAclAssociationRead(d, meta) +} + +func resourceAwsWafRegionalWebAclAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + _, resource_arn := resourceAwsWafRegionalWebAclAssociationParseId(d.Id()) + + log.Printf("[INFO] Deleting WAF Regional Web ACL association: %s", resource_arn) + + params := &wafregional.DisassociateWebACLInput{ + ResourceArn: aws.String(resource_arn), + } + + // If action sucessful HTTP 200 response with an empty body + _, err := conn.DisassociateWebACL(params) + if err != nil { + return err + } + + return nil +} + +func resourceAwsWafRegionalWebAclAssociationParseId(id string) (web_acl_id, resource_arn string) { + parts := strings.SplitN(id, ":", 2) + web_acl_id = parts[0] + resource_arn = parts[1] + return +} diff --git a/aws/resource_aws_wafregional_web_acl_association_test.go b/aws/resource_aws_wafregional_web_acl_association_test.go new file mode 100644 index 00000000000..4b5795bd4d9 --- /dev/null +++ b/aws/resource_aws_wafregional_web_acl_association_test.go @@ -0,0 +1,161 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/aws/aws-sdk-go/service/wafregional" +) + +func TestAccAWSWafRegionalWebAclAssociation_basic(t *testing.T) { + var webAcl waf.WebACL + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckWafRegionalWebAclAssociationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckWafRegionalWebAclAssociationConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckWafRegionalWebAclAssociationExists("aws_wafregional_web_acl_association.foo", &webAcl), + ), + }, + }, + }) +} + +func testAccCheckWafRegionalWebAclAssociationDestroy(s *terraform.State) error { + return testAccCheckWafRegionalWebAclAssociationDestroyWithProvider(s, testAccProvider) +} + +func testAccCheckWafRegionalWebAclAssociationDestroyWithProvider(s *terraform.State, provider *schema.Provider) error { + conn := provider.Meta().(*AWSClient).wafregionalconn + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_wafregional_web_acl_association" { + continue + } + + web_acl_id, resource_arn := resourceAwsWafRegionalWebAclAssociationParseId(rs.Primary.ID) + + resp, err := conn.ListResourcesForWebACL(&wafregional.ListResourcesForWebACLInput{WebACLId: aws.String(web_acl_id)}) + if err != nil { + found := false + for _, list_resource_arn := range resp.ResourceArns { + if resource_arn == *list_resource_arn { + found = true + break + } + } + if found { + return fmt.Errorf("WebACL: %v is still associated to resource: %v", web_acl_id, resource_arn) + } + } + } + return nil +} + +func testAccCheckWafRegionalWebAclAssociationExists(n string, webAcl *waf.WebACL) resource.TestCheckFunc { + return func(s *terraform.State) error { + return testAccCheckWafRegionalWebAclAssociationExistsWithProvider(s, n, webAcl, testAccProvider) + } +} + +func testAccCheckWafRegionalWebAclAssociationExistsWithProvider(s *terraform.State, n string, webAcl *waf.WebACL, provider *schema.Provider) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No WebACL association ID is set") + } + + web_acl_id, resource_arn := resourceAwsWafRegionalWebAclAssociationParseId(rs.Primary.ID) + + conn := provider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.ListResourcesForWebACL(&wafregional.ListResourcesForWebACLInput{WebACLId: aws.String(web_acl_id)}) + if err != nil { + return fmt.Errorf("List Web ACL err: %v", err) + } + + found := false + for _, list_resource_arn := range resp.ResourceArns { + if resource_arn == *list_resource_arn { + found = true + break + } + } + + if !found { + return fmt.Errorf("Web ACL association not found") + } + + return nil +} + +const testAccCheckWafRegionalWebAclAssociationConfig = ` +resource "aws_wafregional_ipset" "foo" { + name = "foo" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "foo" { + depends_on = ["aws_wafregional_ipset.foo"] + name = "foo" + metric_name = "foo" + predicates { + data_id = "${aws_wafregional_ipset.foo.id}" + negated = false + type = "IPMatch" + } +} + +resource "aws_wafregional_web_acl" "foo" { + name = "foo" + metric_name = "foo" + default_action { + type = "ALLOW" + } + rules { + action { + type = "COUNT" + } + priority = 100 + rule_id = "${aws_wafregional_rule.foo.id}" + } +} + +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "10.1.1.0/24" +} + +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "10.1.2.0/24" +} + +resource "aws_alb" "foo" { + subnets = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] +} + +resource "aws_wafregional_web_acl_association" "foo" { + depends_on = ["aws_alb.foo", "aws_wafregional_web_acl.foo"] + resource_arn = "${aws_alb.foo.arn}" + web_acl_id = "${aws_wafregional_web_acl.foo.id}" +} +` diff --git a/website/aws.erb b/website/aws.erb index 37c6dd950d2..2055c327e16 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1156,6 +1156,10 @@ aws_wafregional_ipset + > + aws_wafregional_web_acl_association + + diff --git a/website/docs/r/wafregional_web_acl_association.html.markdown b/website/docs/r/wafregional_web_acl_association.html.markdown new file mode 100644 index 00000000000..8bdfec19b32 --- /dev/null +++ b/website/docs/r/wafregional_web_acl_association.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "aws" +page_title: "AWS: aws_wafregional_web_acl_association" +sidebar_current: "docs-aws-resource-wafregional-web-acl-association" +description: |- + Provides a resource to create an association between a WAF Regional WebACL and Application Load Balancer. +--- + +# aws\_wafregional\_web\_acl\_association + +Provides a resource to create an association between a WAF Regional WebACL and Application Load Balancer. + +-> **Note:** An Application Load Balancer can only be associated with one WAF Regional WebACL. + +## Example Usage + +``` +resource "aws_wafregional_ipset" "ipset" { + name = "tfIPSet" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "wafrule" { + depends_on = ["aws_wafregional_ipset.ipset"] + name = "tfWAFRule" + metric_name = "tfWAFRule" + predicates { + data_id = "${aws_wafregional_ipset.ipset.id}" + negated = false + type = "IPMatch" + } +} + +resource "aws_wafregional_web_acl" "wafacl" { + depends_on = ["aws_wafregional_ipset.ipset", "aws_wafregional_rule.wafrule"] + name = "tfWebACL" + metric_name = "tfWebACL" + default_action { + type = "ALLOW" + } + rules { + action { + type = "BLOCK" + } + priority = 1 + rule_id = "${aws_wafregional_rule.wafrule.id}" + } +} + +resource "aws_vpc" "main" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.main.id}" + cidr_block = "10.1.1.0/24" +} + +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.main.id}" + cidr_block = "10.1.2.0/24" +} + +resource "aws_alb" "alb" { + subnets = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] +} + +resource "aws_wafregional_web_acl_association" "wafassociation" { + depends_on = ["aws_alb.alb", "aws_wafregional_web_acl.wafacl"] + web_acl_id = "${aws_wafregional_web_acl.wafacl.id}" + resource_arn = "${aws_alb.alb.arn}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `web_acl_id` - (Required) The ID of the WAF Regional WebACL to create an association. +* `resource_arn` - (Required) Application Load Balancer ARN to associate with. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the association