diff --git a/.changelog/19164.txt b/.changelog/19164.txt new file mode 100644 index 00000000000..e0fa66fb0a6 --- /dev/null +++ b/.changelog/19164.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_route53_resolver_firewall_rule_group_association +``` \ No newline at end of file diff --git a/aws/internal/service/route53resolver/finder/finder.go b/aws/internal/service/route53resolver/finder/finder.go index 82a94981889..59286ec207d 100644 --- a/aws/internal/service/route53resolver/finder/finder.go +++ b/aws/internal/service/route53resolver/finder/finder.go @@ -156,3 +156,22 @@ func FirewallRuleByID(conn *route53resolver.Route53Resolver, firewallRuleId stri return rule, nil } + +// FirewallRuleGroupAssociationByID returns the DNS Firewall rule group association corresponding to the specified ID. +// Returns nil if no DNS Firewall rule group association is found. +func FirewallRuleGroupAssociationByID(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { + input := &route53resolver.GetFirewallRuleGroupAssociationInput{ + FirewallRuleGroupAssociationId: aws.String(firewallRuleGroupAssociationId), + } + + output, err := conn.GetFirewallRuleGroupAssociation(input) + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + return output.FirewallRuleGroupAssociation, nil +} diff --git a/aws/internal/service/route53resolver/waiter/status.go b/aws/internal/service/route53resolver/waiter/status.go index 4abeba22ed4..72d24206b4c 100644 --- a/aws/internal/service/route53resolver/waiter/status.go +++ b/aws/internal/service/route53resolver/waiter/status.go @@ -20,6 +20,9 @@ const ( firewallDomainListStatusNotFound = "NotFound" firewallDomainListStatusUnknown = "Unknown" + + resolverFirewallRuleGroupAssociationStatusNotFound = "NotFound" + resolverFirewallRuleGroupAssociationStatusUnknown = "Unknown" ) // QueryLogConfigAssociationStatus fetches the QueryLogConfigAssociation and its Status @@ -105,3 +108,24 @@ func FirewallDomainListStatus(conn *route53resolver.Route53Resolver, firewallDom return firewallDomainList, aws.StringValue(firewallDomainList.Status), nil } } + +// FirewallRuleGroupAssociationStatus fetches the FirewallRuleGroupAssociation and its Status +func FirewallRuleGroupAssociationStatus(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + firewallRuleGroupAssociation, err := finder.FirewallRuleGroupAssociationByID(conn, firewallRuleGroupAssociationId) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, resolverFirewallRuleGroupAssociationStatusNotFound, nil + } + + if err != nil { + return nil, resolverFirewallRuleGroupAssociationStatusUnknown, err + } + + if firewallRuleGroupAssociation == nil { + return nil, resolverFirewallRuleGroupAssociationStatusNotFound, nil + } + + return firewallRuleGroupAssociation, aws.StringValue(firewallRuleGroupAssociation.Status), nil + } +} diff --git a/aws/internal/service/route53resolver/waiter/waiter.go b/aws/internal/service/route53resolver/waiter/waiter.go index 08f12bdd7b4..ea18d0b32e6 100644 --- a/aws/internal/service/route53resolver/waiter/waiter.go +++ b/aws/internal/service/route53resolver/waiter/waiter.go @@ -33,6 +33,15 @@ const ( // Maximum amount of time to wait for a FirewallDomainList to be deleted FirewallDomainListDeletedTimeout = 5 * time.Minute + + // Maximum amount of time to wait for a FirewallRuleGroupAssociation to be created + FirewallRuleGroupAssociationCreatedTimeout = 5 * time.Minute + + // Maximum amount of time to wait for a FirewallRuleGroupAssociation to be updated + FirewallRuleGroupAssociationUpdatedTimeout = 5 * time.Minute + + // Maximum amount of time to wait for a FirewallRuleGroupAssociation to be deleted + FirewallRuleGroupAssociationDeletedTimeout = 5 * time.Minute ) // QueryLogConfigAssociationCreated waits for a QueryLogConfig to return ACTIVE @@ -187,3 +196,57 @@ func FirewallDomainListDeleted(conn *route53resolver.Route53Resolver, firewallDo return nil, err } + +// FirewallRuleGroupAssociationCreated waits for a FirewallRuleGroupAssociation to return COMPLETE +func FirewallRuleGroupAssociationCreated(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, + Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, + Refresh: FirewallRuleGroupAssociationStatus(conn, firewallRuleGroupAssociationId), + Timeout: FirewallRuleGroupAssociationCreatedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + return v, err + } + + return nil, err +} + +// FirewallRuleGroupAssociationUpdated waits for a FirewallRuleGroupAssociation to return COMPLETE +func FirewallRuleGroupAssociationUpdated(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, + Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, + Refresh: FirewallRuleGroupAssociationStatus(conn, firewallRuleGroupAssociationId), + Timeout: FirewallRuleGroupAssociationUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + return v, err + } + + return nil, err +} + +// FirewallRuleGroupAssociationDeleted waits for a FirewallRuleGroupAssociation to be deleted +func FirewallRuleGroupAssociationDeleted(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusDeleting}, + Target: []string{}, + Refresh: FirewallRuleGroupAssociationStatus(conn, firewallRuleGroupAssociationId), + Timeout: FirewallRuleGroupAssociationDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + return v, err + } + + return nil, err +} diff --git a/aws/provider.go b/aws/provider.go index 08b71b44c4b..366fc217882 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -931,6 +931,7 @@ func Provider() *schema.Provider { "aws_route53_resolver_firewall_domain_list": resourceAwsRoute53ResolverFirewallDomainList(), "aws_route53_resolver_firewall_rule": resourceAwsRoute53ResolverFirewallRule(), "aws_route53_resolver_firewall_rule_group": resourceAwsRoute53ResolverFirewallRuleGroup(), + "aws_route53_resolver_firewall_rule_group_association": resourceAwsRoute53ResolverFirewallRuleGroupAssociation(), "aws_route53_resolver_query_log_config": resourceAwsRoute53ResolverQueryLogConfig(), "aws_route53_resolver_query_log_config_association": resourceAwsRoute53ResolverQueryLogConfigAssociation(), "aws_route53_resolver_rule_association": resourceAwsRoute53ResolverRuleAssociation(), diff --git a/aws/resource_aws_route53_resolver_firewall_rule_group_association.go b/aws/resource_aws_route53_resolver_firewall_rule_group_association.go new file mode 100644 index 00000000000..f21c99e288f --- /dev/null +++ b/aws/resource_aws_route53_resolver_firewall_rule_group_association.go @@ -0,0 +1,219 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/route53resolver/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/route53resolver/waiter" +) + +func resourceAwsRoute53ResolverFirewallRuleGroupAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsRoute53ResolverFirewallRuleGroupAssociationCreate, + Read: resourceAwsRoute53ResolverFirewallRuleGroupAssociationRead, + Update: resourceAwsRoute53ResolverFirewallRuleGroupAssociationUpdate, + Delete: resourceAwsRoute53ResolverFirewallRuleGroupAssociationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateRoute53ResolverName, + }, + + "firewall_rule_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "mutation_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(route53resolver.MutationProtectionStatus_Values(), false), + }, + + "priority": { + Type: schema.TypeInt, + Required: true, + }, + + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + }, + + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsRoute53ResolverFirewallRuleGroupAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).route53resolverconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + input := &route53resolver.AssociateFirewallRuleGroupInput{ + CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-rslvr-frgassoc-")), + Name: aws.String(d.Get("name").(string)), + FirewallRuleGroupId: aws.String(d.Get("firewall_rule_group_id").(string)), + Priority: aws.Int64(int64(d.Get("priority").(int))), + VpcId: aws.String(d.Get("vpc_id").(string)), + Tags: tags.IgnoreAws().Route53resolverTags(), + } + + if v, ok := d.GetOk("mutation_protection"); ok { + input.MutationProtection = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Route 53 Resolver DNS Firewall rule group association: %#v", input) + output, err := conn.AssociateFirewallRuleGroup(input) + if err != nil { + return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule group association: %w", err) + } + + d.SetId(aws.StringValue(output.FirewallRuleGroupAssociation.Id)) + + _, err = waiter.FirewallRuleGroupAssociationCreated(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error waiting for Route53 Resolver DNS Firewall rule group association (%s) to become available: %w", d.Id(), err) + } + + return resourceAwsRoute53ResolverFirewallRuleGroupAssociationRead(d, meta) +} + +func resourceAwsRoute53ResolverFirewallRuleGroupAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).route53resolverconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + ruleGroupAssociation, err := finder.FirewallRuleGroupAssociationByID(conn, d.Id()) + + if !d.IsNewResource() && isAWSErr(err, route53resolver.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Route53 Resolver DNS Firewall rule group association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Route 53 Resolver DNS Firewall rule group association (%s): %w", d.Id(), err) + } + + if ruleGroupAssociation == nil { + if d.IsNewResource() { + return fmt.Errorf("error getting Route 53 Resolver DNS Firewall rule group association (%s): not found after creation", d.Id()) + } + + log.Printf("[WARN] Route 53 Resolver DNS Firewall rule group association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + arn := aws.StringValue(ruleGroupAssociation.Arn) + d.Set("arn", arn) + d.Set("name", ruleGroupAssociation.Name) + d.Set("firewall_rule_group_id", ruleGroupAssociation.FirewallRuleGroupId) + d.Set("mutation_protection", ruleGroupAssociation.MutationProtection) + d.Set("priority", ruleGroupAssociation.Priority) + d.Set("vpc_id", ruleGroupAssociation.VpcId) + + tags, err := keyvaluetags.Route53resolverListTags(conn, arn) + if err != nil { + return fmt.Errorf("error listing tags for Route53 Resolver DNS Firewall rule group association (%s): %w", arn, err) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceAwsRoute53ResolverFirewallRuleGroupAssociationUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).route53resolverconn + + if d.HasChanges("name", "mutation_protection", "priority") { + input := &route53resolver.UpdateFirewallRuleGroupAssociationInput{ + FirewallRuleGroupAssociationId: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + Priority: aws.Int64(int64(d.Get("priority").(int))), + } + + if v, ok := d.GetOk("mutation_protection"); ok { + input.MutationProtection = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Updating Route 53 Resolver DNS Firewall rule group association: %#v", input) + _, err := conn.UpdateFirewallRuleGroupAssociation(input) + if err != nil { + return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule group association: %w", err) + } + + _, err = waiter.FirewallRuleGroupAssociationUpdated(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error waiting for Route53 Resolver DNS Firewall rule group association (%s) to be updated: %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := keyvaluetags.Route53resolverUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating Route53 Resolver DNS Firewall rule group association (%s) tags: %w", d.Get("arn").(string), err) + } + } + + return resourceAwsRoute53ResolverFirewallRuleGroupAssociationRead(d, meta) +} + +func resourceAwsRoute53ResolverFirewallRuleGroupAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).route53resolverconn + + _, err := conn.DisassociateFirewallRuleGroup(&route53resolver.DisassociateFirewallRuleGroupInput{ + FirewallRuleGroupAssociationId: aws.String(d.Id()), + }) + + if isAWSErr(err, route53resolver.ErrCodeResourceNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall rule group association (%s): %w", d.Id(), err) + } + + _, err = waiter.FirewallRuleGroupAssociationDeleted(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error waiting for Route53 Resolver DNS Firewall rule group association (%s) to be deleted: %w", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_route53_resolver_firewall_rule_group_association_test.go b/aws/resource_aws_route53_resolver_firewall_rule_group_association_test.go new file mode 100644 index 00000000000..e6f1debc0a5 --- /dev/null +++ b/aws/resource_aws_route53_resolver_firewall_rule_group_association_test.go @@ -0,0 +1,401 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/route53resolver/finder" +) + +func init() { + resource.AddTestSweepers("aws_route53_resolver_firewall_rule_group_association", &resource.Sweeper{ + Name: "aws_route53_resolver_firewall_rule_group_association", + F: testSweepRoute53ResolverFirewallRuleGroupAssociations, + }) +} + +func testSweepRoute53ResolverFirewallRuleGroupAssociations(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).route53resolverconn + var sweeperErrs *multierror.Error + + err = conn.ListFirewallRuleGroupAssociationsPages(&route53resolver.ListFirewallRuleGroupAssociationsInput{}, func(page *route53resolver.ListFirewallRuleGroupAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, firewallRuleGroupAssociation := range page.FirewallRuleGroupAssociations { + id := aws.StringValue(firewallRuleGroupAssociation.Id) + + log.Printf("[INFO] Deleting Route53 Resolver DNS Firewall rule group association: %s", id) + r := resourceAwsRoute53ResolverFirewallRuleGroupAssociation() + d := r.Data(nil) + d.SetId(id) + err := r.Delete(d, client) + + if err != nil { + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) + continue + } + } + + return !lastPage + }) + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall rule group associations sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall rule group associations: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_basic(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "firewall_rule_group_id", "aws_route53_resolver_firewall_rule_group.test", "id"), + resource.TestCheckResourceAttr(resourceName, "mutation_protection", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "priority", "101"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_name(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + rNewName := acctest.RandomWithPrefix("tf-acc-test2") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig(rNewName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", rNewName), + ), + }, + }, + }) +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_mutationProtection(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig_mutationProtection(rName, "ENABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mutation_protection", "ENABLED"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig_mutationProtection(rName, "DISABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mutation_protection", "DISABLED"), + ), + }, + }, + }) +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_priority(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig_priority(rName, 101), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "priority", "101"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig_priority(rName, 200), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "priority", "200"), + ), + }, + }, + }) +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_disappears(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsRoute53ResolverFirewallRuleGroupAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSRoute53ResolverFirewallRuleGroupAssociation_tags(t *testing.T) { + var v route53resolver.FirewallRuleGroupAssociation + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_route53_resolver_firewall_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSRoute53Resolver(t) }, + ErrorCheck: testAccErrorCheck(t, route53resolver.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccRoute53ResolverFirewallRuleGroupAssociationConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckRoute53ResolverFirewallRuleGroupAssociationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).route53resolverconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_route53_resolver_firewall_rule_group_association" { + continue + } + + // Try to find the resource + _, err := finder.FirewallRuleGroupAssociationByID(conn, rs.Primary.ID) + // Verify the error is what we want + if isAWSErr(err, route53resolver.ErrCodeResourceNotFoundException, "") { + continue + } + if err != nil { + return err + } + return fmt.Errorf("Route 53 Resolver DNS Firewall rule group association still exists: %s", rs.Primary.ID) + } + + return nil +} + +func testAccCheckRoute53ResolverFirewallRuleGroupAssociationExists(n string, v *route53resolver.FirewallRuleGroupAssociation) 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 Route 53 Resolver DNS Firewall rule group association ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).route53resolverconn + out, err := finder.FirewallRuleGroupAssociationByID(conn, rs.Primary.ID) + if err != nil { + return err + } + + *v = *out + + return nil + } +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_route53_resolver_firewall_rule_group" "test" { + name = %[1]q +} +`, rName) +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfig(rName string) string { + return fmt.Sprintf(` +%[1]s + +resource "aws_route53_resolver_firewall_rule_group_association" "test" { + name = %[2]q + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id + mutation_protection = "DISABLED" + priority = 101 + vpc_id = aws_vpc.test.id +} +`, testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName), rName) +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfig_mutationProtection(rName, mutationProtection string) string { + return fmt.Sprintf(` +%[1]s + +resource "aws_route53_resolver_firewall_rule_group_association" "test" { + name = %[2]q + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id + mutation_protection = %[3]q + priority = 101 + vpc_id = aws_vpc.test.id +} +`, testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName), rName, mutationProtection) +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfig_priority(rName string, priority int) string { + return fmt.Sprintf(` +%[1]s + +resource "aws_route53_resolver_firewall_rule_group_association" "test" { + name = %[2]q + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id + priority = %[3]d + vpc_id = aws_vpc.test.id +} +`, testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName), rName, priority) +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +%[1]s + +resource "aws_route53_resolver_firewall_rule_group_association" "test" { + name = %[2]q + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id + priority = 101 + vpc_id = aws_vpc.test.id + + tags = { + %[3]q = %[4]q + } +} +`, testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName), rName, tagKey1, tagValue1) +} + +func testAccRoute53ResolverFirewallRuleGroupAssociationConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +%[1]s + +resource "aws_route53_resolver_firewall_rule_group_association" "test" { + name = %[2]q + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id + priority = 101 + vpc_id = aws_vpc.test.id + + tags = { + %[3]q = %[4]q + %[5]q = %[6]q + } +} +`, testAccRoute53ResolverFirewallRuleGroupAssociationConfig_base(rName), rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/website/docs/r/route53_resolver_firewall_rule_group_association.markdown b/website/docs/r/route53_resolver_firewall_rule_group_association.markdown new file mode 100644 index 00000000000..5afddd9b84b --- /dev/null +++ b/website/docs/r/route53_resolver_firewall_rule_group_association.markdown @@ -0,0 +1,53 @@ +--- +subcategory: "Route53 Resolver" +layout: "aws" +page_title: "AWS: aws_route53_resolver_firewall_rule_group_association" +description: |- + Provides a Route 53 Resolver DNS Firewall rule group association resource. +--- + +# Resource: aws_route53_resolver_firewall_rule_group_association + +Provides a Route 53 Resolver DNS Firewall rule group association resource. + +## Example Usage + +```terraform +resource "aws_route53_resolver_firewall_rule_group" "example" { + name = "example" +} + +resource "aws_route53_resolver_firewall_rule_group_association" "example" { + name = "example" + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.example.id + priority = 100 + vpc_id = aws_vpc.example.id +} +``` + +## Argument Reference + +The following argument is supported: + +* `name` - (Required) A name that lets you identify the rule group association, to manage and use it. +* `firewall_rule_group_id` - (Required) The unique identifier of the firewall rule group. +* `mutation_protection` - (Optional) If enabled, this setting disallows modification or removal of the association, to help prevent against accidentally altering DNS firewall protections. Valid values: `ENABLED`, `DISABLED`. +* `priority` - (Required) The setting that determines the processing order of the rule group among the rule groups that you associate with the specified VPC. DNS Firewall filters VPC traffic starting from the rule group with the lowest numeric priority setting. +* `vpc_id` - (Required) The unique identifier of the VPC that you want to associate with the rule group. +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN (Amazon Resource Name) of the firewall rule group association. +* `id` - The identifier for the association. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +Route 53 Resolver DNS Firewall rule group associations can be imported using the Route 53 Resolver DNS Firewall rule group association ID, e.g. + +``` +$ terraform import aws_route53_resolver_firewall_rule_group_association.example rslvr-frgassoc-0123456789abcdef +```