diff --git a/.changelog/27487.txt b/.changelog/27487.txt new file mode 100644 index 00000000000..fa9e25b03c1 --- /dev/null +++ b/.changelog/27487.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_route53_resolver_config +``` \ No newline at end of file diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index d5ea308b206..25008e4683b 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -691,17 +691,29 @@ func PreCheckMultipleRegion(t *testing.T, regions int) { } } -// PreCheckRegion checks that the test region is the specified region. -func PreCheckRegion(t *testing.T, region string) { - if curr := Region(); curr != region { - t.Skipf("skipping tests; %s (%s) does not equal %s", envvar.DefaultRegion, curr, region) +// PreCheckRegion checks that the test region is one of the specified regions. +func PreCheckRegion(t *testing.T, regions ...string) { + curr := Region() + var regionOK bool + + for _, region := range regions { + if curr == region { + regionOK = true + break + } + } + + if !regionOK { + t.Skipf("skipping tests; %s (%s) not supported", envvar.DefaultRegion, curr) } } // PreCheckRegionNot checks that the test region is not one of the specified regions. func PreCheckRegionNot(t *testing.T, regions ...string) { + curr := Region() + for _, region := range regions { - if curr := Region(); curr == region { + if curr == region { t.Skipf("skipping tests; %s (%s) not supported", envvar.DefaultRegion, curr) } } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c7f90cac240..b1baf693dad 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1921,6 +1921,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_route53recoveryreadiness_recovery_group": route53recoveryreadiness.ResourceRecoveryGroup(), "aws_route53recoveryreadiness_resource_set": route53recoveryreadiness.ResourceResourceSet(), + "aws_route53_resolver_config": route53resolver.ResourceConfig(), "aws_route53_resolver_dnssec_config": route53resolver.ResourceDNSSECConfig(), "aws_route53_resolver_endpoint": route53resolver.ResourceEndpoint(), "aws_route53_resolver_firewall_config": route53resolver.ResourceFirewallConfig(), diff --git a/internal/service/apigatewayv2/domain_name_test.go b/internal/service/apigatewayv2/domain_name_test.go index 7af22fddfc3..8aaa2464fa2 100644 --- a/internal/service/apigatewayv2/domain_name_test.go +++ b/internal/service/apigatewayv2/domain_name_test.go @@ -216,7 +216,7 @@ func TestAccAPIGatewayV2DomainName_updateCertificate(t *testing.T) { func TestAccAPIGatewayV2DomainName_MutualTLSAuthentication_basic(t *testing.T) { rootDomain := acctest.ACMCertificateDomainFromEnv(t) - domain := fmt.Sprintf("%s.%s", acctest.RandomSubdomain(), rootDomain) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) var v apigatewayv2.GetDomainNameOutput resourceName := "aws_apigatewayv2_domain_name.test" @@ -294,7 +294,7 @@ func TestAccAPIGatewayV2DomainName_MutualTLSAuthentication_basic(t *testing.T) { func TestAccAPIGatewayV2DomainName_MutualTLSAuthentication_noVersion(t *testing.T) { rootDomain := acctest.ACMCertificateDomainFromEnv(t) - domain := fmt.Sprintf("%s.%s", acctest.RandomSubdomain(), rootDomain) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) var v apigatewayv2.GetDomainNameOutput resourceName := "aws_apigatewayv2_domain_name.test" @@ -331,7 +331,7 @@ func TestAccAPIGatewayV2DomainName_MutualTLSAuthentication_noVersion(t *testing. func TestAccAPIGatewayV2DomainName_MutualTLSAuthentication_ownership(t *testing.T) { rootDomain := acctest.ACMCertificateDomainFromEnv(t) - domain := fmt.Sprintf("%s.%s", acctest.RandomSubdomain(), rootDomain) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) key := acctest.TLSRSAPrivateKeyPEM(2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(key, domain) diff --git a/internal/service/route53/find.go b/internal/service/route53/find.go index f28b89aa716..ad84133cf3f 100644 --- a/internal/service/route53/find.go +++ b/internal/service/route53/find.go @@ -108,6 +108,27 @@ func FindKeySigningKeyByResourceID(conn *route53.Route53, resourceID string) (*r return FindKeySigningKey(conn, hostedZoneID, name) } +func FindQueryLoggingConfigByID(ctx context.Context, conn *route53.Route53, id string) (*route53.QueryLoggingConfig, error) { + input := &route53.GetQueryLoggingConfigInput{ + Id: aws.String(id), + } + + output, err := conn.GetQueryLoggingConfigWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53.ErrCodeNoSuchQueryLoggingConfig, route53.ErrCodeNoSuchHostedZone) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if output == nil || output.QueryLoggingConfig == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.QueryLoggingConfig, nil +} + func FindTrafficPolicyByID(ctx context.Context, conn *route53.Route53, id string) (*route53.TrafficPolicy, error) { var latestVersion int64 diff --git a/internal/service/route53/query_log.go b/internal/service/route53/query_log.go index a9591590a88..5cc6931059d 100644 --- a/internal/service/route53/query_log.go +++ b/internal/service/route53/query_log.go @@ -1,6 +1,7 @@ package route53 import ( + "context" "fmt" "log" @@ -8,16 +9,19 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/route53" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceQueryLog() *schema.Resource { return &schema.Resource{ - Create: resourceQueryLogCreate, - Read: resourceQueryLogRead, - Delete: resourceQueryLogDelete, + CreateWithoutTimeout: resourceQueryLogCreate, + ReadWithoutTimeout: resourceQueryLogRead, + DeleteWithoutTimeout: resourceQueryLogDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -33,7 +37,6 @@ func ResourceQueryLog() *schema.Resource { ForceNew: true, ValidateFunc: verify.ValidARN, }, - "zone_id": { Type: schema.TypeString, Required: true, @@ -43,46 +46,39 @@ func ResourceQueryLog() *schema.Resource { } } -func resourceQueryLogCreate(d *schema.ResourceData, meta interface{}) error { - r53 := meta.(*conns.AWSClient).Route53Conn +func resourceQueryLogCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53Conn input := &route53.CreateQueryLoggingConfigInput{ CloudWatchLogsLogGroupArn: aws.String(d.Get("cloudwatch_log_group_arn").(string)), HostedZoneId: aws.String(d.Get("zone_id").(string)), } - log.Printf("[DEBUG] Creating Route53 query logging configuration: %#v", input) - out, err := r53.CreateQueryLoggingConfig(input) + output, err := conn.CreateQueryLoggingConfigWithContext(ctx, input) + if err != nil { - return fmt.Errorf("Error creating Route53 query logging configuration: %s", err) + return diag.Errorf("creating Route53 Query Logging Config: %s", err) } - log.Printf("[DEBUG] Route53 query logging configuration created: %#v", out) - d.SetId(aws.StringValue(out.QueryLoggingConfig.Id)) + d.SetId(aws.StringValue(output.QueryLoggingConfig.Id)) - return resourceQueryLogRead(d, meta) + return resourceQueryLogRead(ctx, d, meta) } -func resourceQueryLogRead(d *schema.ResourceData, meta interface{}) error { - r53 := meta.(*conns.AWSClient).Route53Conn +func resourceQueryLogRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53Conn - input := &route53.GetQueryLoggingConfigInput{ - Id: aws.String(d.Id()), + output, err := FindQueryLoggingConfigByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Query Logging Config %s not found, removing from state", d.Id()) + d.SetId("") + return nil } - log.Printf("[DEBUG] Reading Route53 query logging configuration: %#v", input) - out, err := r53.GetQueryLoggingConfig(input) + if err != nil { - if tfawserr.ErrCodeEquals(err, route53.ErrCodeNoSuchQueryLoggingConfig) || tfawserr.ErrCodeEquals(err, route53.ErrCodeNoSuchHostedZone) { - log.Printf("[WARN] Route53 Query Logging Config (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return fmt.Errorf("Error reading Route53 query logging configuration: %s", err) + return diag.Errorf("reading Route53 Query Logging Config (%s): %s", d.Id(), err) } - log.Printf("[DEBUG] Route53 query logging configuration received: %#v", out) - - d.Set("cloudwatch_log_group_arn", out.QueryLoggingConfig.CloudWatchLogsLogGroupArn) - d.Set("zone_id", out.QueryLoggingConfig.HostedZoneId) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -90,24 +86,26 @@ func resourceQueryLogRead(d *schema.ResourceData, meta interface{}) error { Resource: fmt.Sprintf("queryloggingconfig/%s", d.Id()), }.String() d.Set("arn", arn) + d.Set("cloudwatch_log_group_arn", output.CloudWatchLogsLogGroupArn) + d.Set("zone_id", output.HostedZoneId) return nil } -func resourceQueryLogDelete(d *schema.ResourceData, meta interface{}) error { - r53 := meta.(*conns.AWSClient).Route53Conn +func resourceQueryLogDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53Conn - input := &route53.DeleteQueryLoggingConfigInput{ + log.Printf("[DEBUG] Deleting Route53 Query Logging Config: %s", d.Id()) + _, err := conn.DeleteQueryLoggingConfigWithContext(ctx, &route53.DeleteQueryLoggingConfigInput{ Id: aws.String(d.Id()), - } - log.Printf("[DEBUG] Deleting Route53 query logging configuration: %#v", input) - _, err := r53.DeleteQueryLoggingConfig(input) + }) + if tfawserr.ErrCodeEquals(err, route53.ErrCodeNoSuchQueryLoggingConfig) { return nil } if err != nil { - return fmt.Errorf("deleting Route53 query logging configuration (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Query Logging Config (%s): %s", d.Id(), err) } return nil diff --git a/internal/service/route53/query_log_test.go b/internal/service/route53/query_log_test.go index c65294b07dd..3e7cc9f71d1 100644 --- a/internal/service/route53/query_log_test.go +++ b/internal/service/route53/query_log_test.go @@ -4,43 +4,43 @@ import ( "context" "fmt" "regexp" - "sync" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/service/route53" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" sdkacctest "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/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/provider" tfroute53 "github.com/hashicorp/terraform-provider-aws/internal/service/route53" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53QueryLog_basic(t *testing.T) { cloudwatchLogGroupResourceName := "aws_cloudwatch_log_group.test" resourceName := "aws_route53_query_log.test" route53ZoneResourceName := "aws_route53_zone.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domainName := acctest.RandomDomainName() + var v route53.QueryLoggingConfig - var queryLoggingConfig route53.QueryLoggingConfig resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckQueryLog(t) }, + PreCheck: func() { + acctest.PreCheck(t) + // AWS Commercial: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/query-logs.html + // AWS GovCloud (US) - only private DNS: https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-r53.html + // AWS China - not available yet: https://docs.amazonaws.cn/en_us/aws/latest/userguide/route53.html + acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) + }, ErrorCheck: acctest.ErrorCheck(t, route53.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckQueryLogDestroy, Steps: []resource.TestStep{ { - Config: testAccQueryLogConfig_resourceBasic1(rName, domainName), + Config: testAccQueryLogConfig_basic(rName, domainName), Check: resource.ComposeTestCheckFunc( - testAccCheckQueryLogExists(resourceName, &queryLoggingConfig), + testAccCheckQueryLogExists(resourceName, &v), acctest.MatchResourceAttrGlobalARNNoAccount(resourceName, "arn", "route53", regexp.MustCompile("queryloggingconfig/.+")), resource.TestCheckResourceAttrPair(resourceName, "cloudwatch_log_group_arn", cloudwatchLogGroupResourceName, "arn"), resource.TestCheckResourceAttrPair(resourceName, "zone_id", route53ZoneResourceName, "zone_id"), @@ -57,21 +57,20 @@ func TestAccRoute53QueryLog_basic(t *testing.T) { func TestAccRoute53QueryLog_disappears(t *testing.T) { resourceName := "aws_route53_query_log.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domainName := acctest.RandomDomainName() + var v route53.QueryLoggingConfig - var queryLoggingConfig route53.QueryLoggingConfig resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckQueryLog(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) }, ErrorCheck: acctest.ErrorCheck(t, route53.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckQueryLogDestroy, Steps: []resource.TestStep{ { - Config: testAccQueryLogConfig_resourceBasic1(rName, domainName), + Config: testAccQueryLogConfig_basic(rName, domainName), Check: resource.ComposeTestCheckFunc( - testAccCheckQueryLogExists(resourceName, &queryLoggingConfig), + testAccCheckQueryLogExists(resourceName, &v), acctest.CheckResourceDisappears(acctest.Provider, tfroute53.ResourceQueryLog(), resourceName), ), ExpectNonEmptyPlan: true, @@ -83,21 +82,20 @@ func TestAccRoute53QueryLog_disappears(t *testing.T) { func TestAccRoute53QueryLog_Disappears_hostedZone(t *testing.T) { resourceName := "aws_route53_query_log.test" route53ZoneResourceName := "aws_route53_zone.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domainName := acctest.RandomDomainName() + var v route53.QueryLoggingConfig - var queryLoggingConfig route53.QueryLoggingConfig resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckQueryLog(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) }, ErrorCheck: acctest.ErrorCheck(t, route53.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckQueryLogDestroy, Steps: []resource.TestStep{ { - Config: testAccQueryLogConfig_resourceBasic1(rName, domainName), + Config: testAccQueryLogConfig_basic(rName, domainName), Check: resource.ComposeTestCheckFunc( - testAccCheckQueryLogExists(resourceName, &queryLoggingConfig), + testAccCheckQueryLogExists(resourceName, &v), acctest.CheckResourceDisappears(acctest.Provider, tfroute53.ResourceZone(), route53ZoneResourceName), ), ExpectNonEmptyPlan: true, @@ -106,66 +104,57 @@ func TestAccRoute53QueryLog_Disappears_hostedZone(t *testing.T) { }) } -func testAccCheckQueryLogExists(pr string, queryLoggingConfig *route53.QueryLoggingConfig) resource.TestCheckFunc { +func testAccCheckQueryLogExists(n string, v *route53.QueryLoggingConfig) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := testAccProviderRoute53QueryLog.Meta().(*conns.AWSClient).Route53Conn - rs, ok := s.RootModule().Resources[pr] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", pr) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No Route53 Query Logging Config ID is set") } - out, err := conn.GetQueryLoggingConfig(&route53.GetQueryLoggingConfigInput{ - Id: aws.String(rs.Primary.ID), - }) + conn := acctest.Provider.Meta().(*conns.AWSClient).Route53Conn + + output, err := tfroute53.FindQueryLoggingConfigByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - if out.QueryLoggingConfig == nil { - return fmt.Errorf("Route53 query logging configuration does not exist: %q", rs.Primary.ID) - } - *queryLoggingConfig = *out.QueryLoggingConfig + *v = *output return nil } } func testAccCheckQueryLogDestroy(s *terraform.State) error { - conn := testAccProviderRoute53QueryLog.Meta().(*conns.AWSClient).Route53Conn + conn := acctest.Provider.Meta().(*conns.AWSClient).Route53Conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_route53_query_log" { continue } - out, err := conn.GetQueryLoggingConfig(&route53.GetQueryLoggingConfigInput{ - Id: aws.String(rs.Primary.ID), - }) + _, err := tfroute53.FindQueryLoggingConfigByID(context.Background(), conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, route53.ErrCodeNoSuchQueryLoggingConfig) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("reading Route 53 Query Logging Configuration (%s): %w", rs.Primary.ID, err) + return err } - if out.QueryLoggingConfig != nil { - return fmt.Errorf("Route53 query logging configuration exists: %q", rs.Primary.ID) - } + return fmt.Errorf("Route53 Query Logging Config %s still exists", rs.Primary.ID) } return nil } -func testAccQueryLogConfig_resourceBasic1(rName, domainName string) string { - return acctest.ConfigCompose( - testAccQueryLogRegionProviderConfig(), - fmt.Sprintf(` +func testAccQueryLogConfig_basic(rName, domainName string) string { + return fmt.Sprintf(` resource "aws_cloudwatch_log_group" "test" { name = "/aws/route53/${aws_route53_zone.test.name}" retention_in_days = 1 @@ -204,85 +193,5 @@ resource "aws_route53_query_log" "test" { cloudwatch_log_group_arn = aws_cloudwatch_log_group.test.arn zone_id = aws_route53_zone.test.zone_id } -`, rName, domainName)) -} - -// Route 53 Query Logging can only be enabled with CloudWatch Log Groups in specific regions, - -// testAccRoute53QueryLogRegion is the chosen Route 53 Query Logging testing region -// -// Cached to prevent issues should multiple regions become available. -var testAccRoute53QueryLogRegion string - -// testAccProviderRoute53QueryLog is the Route 53 Query Logging provider instance -// -// This Provider can be used in testing code for API calls without requiring -// the use of saving and referencing specific ProviderFactories instances. -// -// testAccPreCheckQueryLog(t) must be called before using this provider instance. -var testAccProviderRoute53QueryLog *schema.Provider - -// testAccProviderRoute53QueryLogConfigure ensures the provider is only configured once -var testAccProviderRoute53QueryLogConfigure sync.Once - -// testAccPreCheckQueryLog verifies AWS credentials and that Route 53 Query Logging is supported -func testAccPreCheckQueryLog(t *testing.T) { - acctest.PreCheckPartitionHasService(route53.EndpointsID, t) - - region := testAccGetQueryLogRegion() - - if region == "" { - t.Skip("Route 53 Query Log not available in this AWS Partition") - } - - // Since we are outside the scope of the Terraform configuration we must - // call Configure() to properly initialize the provider configuration. - testAccProviderRoute53QueryLogConfigure.Do(func() { - ctx := context.Background() - var err error - testAccProviderRoute53QueryLog, err = provider.New(ctx) - - if err != nil { - t.Fatal(err) - } - - testAccRecordConfig_config := map[string]interface{}{ - "region": region, - } - - diags := testAccProviderRoute53QueryLog.Configure(ctx, terraform.NewResourceConfigRaw(testAccRecordConfig_config)) - - if diags != nil && diags.HasError() { - for _, d := range diags { - if d.Severity == diag.Error { - t.Fatalf("error configuring Route 53 Query Logging provider: %s", d.Summary) - } - } - } - }) -} - -// testAccQueryLogRegionProviderConfig is the Terraform provider configuration for Route 53 Query Logging region testing -// -// Testing Route 53 Query Logging assumes no other provider configurations -// are necessary and overwrites the "aws" provider configuration. -func testAccQueryLogRegionProviderConfig() string { - return acctest.ConfigRegionalProvider(testAccGetQueryLogRegion()) -} - -// testAccGetQueryLogRegion returns the Route 53 Query Logging region for testing -func testAccGetQueryLogRegion() string { - if testAccRoute53QueryLogRegion != "" { - return testAccRoute53QueryLogRegion - } - - // AWS Commercial: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/query-logs.html - // AWS GovCloud (US) - only private DNS: https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-r53.html - // AWS China - not available yet: https://docs.amazonaws.cn/en_us/aws/latest/userguide/route53.html - switch acctest.Partition() { - case endpoints.AwsPartitionID: - testAccRoute53QueryLogRegion = endpoints.UsEast1RegionID - } - - return testAccRoute53QueryLogRegion +`, rName, domainName) } diff --git a/internal/service/route53resolver/config.go b/internal/service/route53resolver/config.go new file mode 100644 index 00000000000..a4ac835020e --- /dev/null +++ b/internal/service/route53resolver/config.go @@ -0,0 +1,227 @@ +package route53resolver + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceConfig() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigCreate, + ReadWithoutTimeout: resourceConfigRead, + UpdateWithoutTimeout: resourceConfigUpdate, + DeleteWithoutTimeout: schema.NoopContext, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "autodefined_reverse_flag": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(autodefinedReverseFlag_Values(), false), + }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53ResolverConn + + autodefinedReverseFlag := d.Get("autodefined_reverse_flag").(string) + input := &route53resolver.UpdateResolverConfigInput{ + AutodefinedReverseFlag: aws.String(autodefinedReverseFlag), + ResourceId: aws.String(d.Get("resource_id").(string)), + } + + output, err := conn.UpdateResolverConfigWithContext(ctx, input) + + if err != nil { + return diag.Errorf("creating Route53 Resolver Config: %s", err) + } + + d.SetId(aws.StringValue(output.ResolverConfig.Id)) + + if _, err = waitAutodefinedReverseUpdated(ctx, conn, d.Id(), autodefinedReverseFlag); err != nil { + return diag.Errorf("waiting for Route53 Resolver Config (%s) create: %s", d.Id(), err) + } + + return resourceConfigRead(ctx, d, meta) +} + +func resourceConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53ResolverConn + + resolverConfig, err := FindResolverConfigByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Config (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading Route53 Resolver Config (%s): %s", d.Id(), err) + } + + var autodefinedReverseFlag string + if aws.StringValue(resolverConfig.AutodefinedReverse) == route53resolver.ResolverAutodefinedReverseStatusEnabled { + autodefinedReverseFlag = autodefinedReverseFlagEnable + } else { + autodefinedReverseFlag = autodefinedReverseFlagDisable + } + d.Set("autodefined_reverse_flag", autodefinedReverseFlag) + d.Set("owner_id", resolverConfig.OwnerId) + d.Set("resource_id", resolverConfig.ResourceId) + + return nil +} + +func resourceConfigUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).Route53ResolverConn + + autodefinedReverseFlag := d.Get("autodefined_reverse_flag").(string) + input := &route53resolver.UpdateResolverConfigInput{ + AutodefinedReverseFlag: aws.String(autodefinedReverseFlag), + ResourceId: aws.String(d.Get("resource_id").(string)), + } + + _, err := conn.UpdateResolverConfigWithContext(ctx, input) + + if err != nil { + return diag.Errorf("updating Route53 Resolver Config: %s", err) + } + + if _, err = waitAutodefinedReverseUpdated(ctx, conn, d.Id(), autodefinedReverseFlag); err != nil { + return diag.Errorf("waiting for Route53 Resolver Config (%s) update: %s", d.Id(), err) + } + + return resourceConfigRead(ctx, d, meta) +} + +func FindResolverConfigByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverConfig, error) { + input := &route53resolver.ListResolverConfigsInput{} + var output *route53resolver.ResolverConfig + + // GetResolverConfig does not support query by ID. + err := conn.ListResolverConfigsPagesWithContext(ctx, input, func(page *route53resolver.ListResolverConfigsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ResolverConfigs { + if aws.StringValue(v.Id) == id { + output = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, &resource.NotFoundError{LastRequest: input} + } + + return output, nil +} + +const ( + // https://docs.aws.amazon.com/Route53/latest/APIReference/API_route53resolver_UpdateResolverConfig.html#API_route53resolver_UpdateResolverConfig_RequestSyntax + autodefinedReverseFlagDisable = "DISABLE" + autodefinedReverseFlagEnable = "ENABLE" +) + +func autodefinedReverseFlag_Values() []string { + return []string{ + autodefinedReverseFlagDisable, + autodefinedReverseFlagEnable, + } +} + +func statusAutodefinedReverse(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindResolverConfigByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.AutodefinedReverse), nil + } +} + +const ( + autodefinedReverseUpdatedTimeout = 10 * time.Minute +) + +func waitAutodefinedReverseUpdated(ctx context.Context, conn *route53resolver.Route53Resolver, id, autodefinedReverseFlag string) (*route53resolver.ResolverConfig, error) { + if autodefinedReverseFlag == autodefinedReverseFlagDisable { + return waitAutodefinedReverseDisabled(ctx, conn, id) + } else { + return waitAutodefinedReverseEnabled(ctx, conn, id) + } +} + +func waitAutodefinedReverseEnabled(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverAutodefinedReverseStatusEnabling}, + Target: []string{route53resolver.ResolverAutodefinedReverseStatusEnabled}, + Refresh: statusAutodefinedReverse(ctx, conn, id), + Timeout: autodefinedReverseUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverConfig); ok { + return output, err + } + + return nil, err +} + +func waitAutodefinedReverseDisabled(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverAutodefinedReverseStatusDisabling}, + Target: []string{route53resolver.ResolverAutodefinedReverseStatusDisabled}, + Refresh: statusAutodefinedReverse(ctx, conn, id), + Timeout: autodefinedReverseUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverConfig); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/route53resolver/config_test.go b/internal/service/route53resolver/config_test.go new file mode 100644 index 00000000000..e237a1aa143 --- /dev/null +++ b/internal/service/route53resolver/config_test.go @@ -0,0 +1,113 @@ +package route53resolver_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/route53resolver" + sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" +) + +func TestAccRoute53ResolverConfig_basic(t *testing.T) { + var v route53resolver.ResolverConfig + resourceName := "aws_route53_resolver_config.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigConfig_basic(rName, "DISABLE"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "autodefined_reverse_flag", "DISABLE"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrPair(resourceName, "resource_id", vpcResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigConfig_basic(rName, "ENABLE"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "autodefined_reverse_flag", "ENABLE"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttrPair(resourceName, "resource_id", vpcResourceName, "id"), + ), + }, + }, + }) +} + +func TestAccRoute53ResolverConfig_Disappears_vpc(t *testing.T) { + var v route53resolver.ResolverConfig + resourceName := "aws_route53_resolver_config.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigConfig_basic(rName, "ENABLE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPC(), vpcResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckConfigExists(n string, v *route53resolver.ResolverConfig) 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 Route53 Resolver Config ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn + + output, err := tfroute53resolver.FindResolverConfigByID(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccConfigConfig_basic(rName, autodefinedReverseFlag string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` +resource "aws_route53_resolver_config" "test" { + autodefined_reverse_flag = %[1]q + resource_id = aws_vpc.test.id +} +`, autodefinedReverseFlag)) +} diff --git a/internal/service/route53resolver/dnssec_config.go b/internal/service/route53resolver/dnssec_config.go index 952f1a0230d..13b2376fca5 100644 --- a/internal/service/route53resolver/dnssec_config.go +++ b/internal/service/route53resolver/dnssec_config.go @@ -1,22 +1,28 @@ package route53resolver import ( + "context" "fmt" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceDNSSECConfig() *schema.Resource { return &schema.Resource{ - Create: resourceDNSSECConfigCreate, - Read: resourceDNSSECConfigRead, - Delete: resourceDNSSECConfigDelete, + CreateWithoutTimeout: resourceDNSSECConfigCreate, + ReadWithoutTimeout: resourceDNSSECConfigRead, + DeleteWithoutTimeout: resourceDNSSECConfigDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -26,18 +32,15 @@ func ResourceDNSSECConfig() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "owner_id": { Type: schema.TypeString, Computed: true, }, - "resource_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "validation_status": { Type: schema.TypeString, Computed: true, @@ -46,138 +49,176 @@ func ResourceDNSSECConfig() *schema.Resource { } } -func resourceDNSSECConfigCreate(d *schema.ResourceData, meta interface{}) error { +func resourceDNSSECConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - req := &route53resolver.UpdateResolverDnssecConfigInput{ + input := &route53resolver.UpdateResolverDnssecConfigInput{ ResourceId: aws.String(d.Get("resource_id").(string)), Validation: aws.String(route53resolver.ValidationEnable), } - log.Printf("[DEBUG] Creating Route 53 Resolver DNSSEC config: %#v", req) - resp, err := conn.UpdateResolverDnssecConfig(req) + output, err := conn.UpdateResolverDnssecConfigWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNSSEC config: %w", err) + return diag.Errorf("creating Route53 Resolver DNSSEC Config: %s", err) } - d.SetId(aws.StringValue(resp.ResolverDNSSECConfig.Id)) + d.SetId(aws.StringValue(output.ResolverDNSSECConfig.Id)) - _, err = WaitDNSSECConfigCreated(conn, d.Id()) - if err != nil { - return err + if _, err := waitDNSSECConfigCreated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver DNSSEC Config (%s) create: %s", d.Id(), err) } - return resourceDNSSECConfigRead(d, meta) + return resourceDNSSECConfigRead(ctx, d, meta) } -func resourceDNSSECConfigRead(d *schema.ResourceData, meta interface{}) error { +func resourceDNSSECConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - config, err := FindResolverDNSSECConfigByID(conn, d.Id()) - - if err != nil { - return fmt.Errorf("error getting Route 53 Resolver DNSSEC config (%s): %w", d.Id(), err) - } + dnssecConfig, err := FindResolverDNSSECConfigByID(ctx, conn, d.Id()) - if config == nil || aws.StringValue(config.ValidationStatus) == route53resolver.ResolverDNSSECValidationStatusDisabled { - log.Printf("[WARN] Route 53 Resolver DNSSEC config (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver DNSSEC Config (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - d.Set("owner_id", config.OwnerId) - d.Set("resource_id", config.ResourceId) - d.Set("validation_status", config.ValidationStatus) + if err != nil { + return diag.Errorf("reading Route53 Resolver DNSSEC Config (%s): %s", d.Id(), err) + } - configArn := arn.ARN{ + ownerID := aws.StringValue(dnssecConfig.OwnerId) + resourceID := aws.StringValue(dnssecConfig.ResourceId) + arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: "route53resolver", Region: meta.(*conns.AWSClient).Region, - AccountID: aws.StringValue(config.OwnerId), - Resource: fmt.Sprintf("resolver-dnssec-config/%s", aws.StringValue(config.ResourceId)), + AccountID: ownerID, + Resource: fmt.Sprintf("resolver-dnssec-config/%s", resourceID), }.String() - d.Set("arn", configArn) + d.Set("arn", arn) + d.Set("owner_id", ownerID) + d.Set("resource_id", resourceID) + d.Set("validation_status", dnssecConfig.ValidationStatus) return nil } -func resourceDNSSECConfigDelete(d *schema.ResourceData, meta interface{}) error { +func resourceDNSSECConfigDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - // To delete a Route 53 ResolverDnssecConfig, it must be: - // (1) updated to a "DISABLED" state - // (2) updated again to be permanently removed - // - // To determine how many Updates are required, - // we first find the config by ID and proceed as follows: - - config, err := FindResolverDNSSECConfigByID(conn, d.Id()) + log.Printf("[DEBUG] Deleting Route53 Resolver DNSSEC Config: %s", d.Id()) + _, err := conn.UpdateResolverDnssecConfigWithContext(ctx, &route53resolver.UpdateResolverDnssecConfigInput{ + ResourceId: aws.String(d.Get("resource_id").(string)), + Validation: aws.String(route53resolver.ValidationDisable), + }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeAccessDeniedException) { + // VPC doesn't exist. return nil } if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNSSEC config (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver DNSSEC Config (%s): %s", d.Id(), err) } - if config == nil { - return nil + if _, err = waitDNSSECConfigDeleted(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver DNSSEC Config (%s) delete: %s", d.Id(), err) } - // (1) Update Route 53 ResolverDnssecConfig to "DISABLED" state, if necessary - if aws.StringValue(config.ValidationStatus) == route53resolver.ResolverDNSSECValidationStatusEnabled { - config, err = updateResolverDNSSECConfigValidation(conn, aws.StringValue(config.ResourceId), route53resolver.ValidationDisable) - if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNSSEC config (%s): %w", d.Id(), err) + return nil +} + +func FindResolverDNSSECConfigByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverDnssecConfig, error) { + input := &route53resolver.ListResolverDnssecConfigsInput{} + var output *route53resolver.ResolverDnssecConfig + + // GetResolverDnssecConfig does not support query by ID. + err := conn.ListResolverDnssecConfigsPagesWithContext(ctx, input, func(page *route53resolver.ListResolverDnssecConfigsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - if config == nil { - return nil + + for _, v := range page.ResolverDnssecConfigs { + if aws.StringValue(v.Id) == id { + output = v + + return false + } } + + return !lastPage + }) + + if err != nil { + return nil, err } - // (1.a) Wait for Route 53 ResolverDnssecConfig to reach "DISABLED" state, if necessary - if aws.StringValue(config.ValidationStatus) != route53resolver.ResolverDNSSECValidationStatusDisabled { - if _, err = WaitDNSSECConfigDisabled(conn, d.Id()); err != nil { - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil - } + if output == nil { + return nil, &resource.NotFoundError{LastRequest: input} + } - return fmt.Errorf("error waiting for Route 53 Resolver DNSSEC config (%s) to be disabled: %w", d.Id(), err) + if validationStatus := aws.StringValue(output.ValidationStatus); validationStatus == route53resolver.ResolverDNSSECValidationStatusDisabled { + return nil, &resource.NotFoundError{ + Message: validationStatus, + LastRequest: input, } } - // (2) Update Route 53 ResolverDnssecConfig again, effectively deleting the resource - _, err = updateResolverDNSSECConfigValidation(conn, aws.StringValue(config.ResourceId), route53resolver.ValidationDisable) + return output, nil +} - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil - } +func statusDNSSECConfig(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindResolverDNSSECConfigByID(ctx, conn, id) - if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNSSEC config (%s): %w", d.Id(), err) - } + if tfresource.NotFound(err) { + return nil, "", nil + } - return nil + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.ValidationStatus), nil + } } -func updateResolverDNSSECConfigValidation(conn *route53resolver.Route53Resolver, resourceId, validation string) (*route53resolver.ResolverDnssecConfig, error) { - output, err := conn.UpdateResolverDnssecConfig(&route53resolver.UpdateResolverDnssecConfigInput{ - ResourceId: aws.String(resourceId), - Validation: aws.String(validation), - }) +const ( + dnssecConfigCreatedTimeout = 10 * time.Minute + dnssecConfigDeletedTimeout = 10 * time.Minute +) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil, nil +func waitDNSSECConfigCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverDnssecConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverDNSSECValidationStatusEnabling}, + Target: []string{route53resolver.ResolverDNSSECValidationStatusEnabled}, + Refresh: statusDNSSECConfig(ctx, conn, id), + Timeout: dnssecConfigCreatedTimeout, } - if err != nil { - return nil, err + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverDnssecConfig); ok { + return output, err } - if output == nil { - return nil, nil + return nil, err +} + +func waitDNSSECConfigDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverDnssecConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverDNSSECValidationStatusDisabling}, + Target: []string{}, + Refresh: statusDNSSECConfig(ctx, conn, id), + Timeout: dnssecConfigDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverDnssecConfig); ok { + return output, err } - return output.ResolverDNSSECConfig, nil + return nil, err } diff --git a/internal/service/route53resolver/dnssec_config_test.go b/internal/service/route53resolver/dnssec_config_test.go index d11ee20837b..726e6c631b1 100644 --- a/internal/service/route53resolver/dnssec_config_test.go +++ b/internal/service/route53resolver/dnssec_config_test.go @@ -1,19 +1,19 @@ package route53resolver_test import ( + "context" "fmt" "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverDNSSECConfig_basic(t *testing.T) { @@ -76,9 +76,9 @@ func testAccCheckDNSSECConfigDestroy(s *terraform.State) error { continue } - config, err := tfroute53resolver.FindResolverDNSSECConfigByID(conn, rs.Primary.ID) + _, err := tfroute53resolver.FindResolverDNSSECConfigByID(context.Background(), conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + if tfresource.NotFound(err) { continue } @@ -86,11 +86,7 @@ func testAccCheckDNSSECConfigDestroy(s *terraform.State) error { return err } - if config == nil { - continue - } - - return fmt.Errorf("Route 53 Resolver Dnssec config still exists: %s", rs.Primary.ID) + return fmt.Errorf("Route53 Resolver DNSSEC Config still exists: %s", rs.Primary.ID) } return nil @@ -104,48 +100,21 @@ func testAccCheckDNSSECConfigExists(n string) resource.TestCheckFunc { } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver Dnssec config ID is set") + return fmt.Errorf("No Route53 Resolver DNSSEC Config ID is set") } - id := rs.Primary.ID conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - config, err := tfroute53resolver.FindResolverDNSSECConfigByID(conn, id) - - if err != nil { - return err - } + _, err := tfroute53resolver.FindResolverDNSSECConfigByID(context.Background(), conn, rs.Primary.ID) - if config == nil { - return fmt.Errorf("Route 53 Resolver Dnssec config (%s) not found", id) - } - - if aws.StringValue(config.ValidationStatus) != route53resolver.ResolverDNSSECValidationStatusEnabled { - return fmt.Errorf("Route 53 Resolver Dnssec config (%s) is not enabled", aws.StringValue(config.Id)) - } - - return nil + return err } } -func testAccDNSSECConfigBase(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - enable_dns_support = true - enable_dns_hostnames = true - - tags = { - Name = %q - } -} -`, rName) -} - func testAccDNSSECConfigConfig_basic(rName string) string { - return testAccDNSSECConfigBase(rName) + ` + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), ` resource "aws_route53_resolver_dnssec_config" "test" { resource_id = aws_vpc.test.id } -` +`) } diff --git a/internal/service/route53resolver/endpoint.go b/internal/service/route53resolver/endpoint.go index 663bff3f148..2aad416e928 100644 --- a/internal/service/route53resolver/endpoint.go +++ b/internal/service/route53resolver/endpoint.go @@ -2,6 +2,8 @@ package route53resolver import ( "bytes" + "context" + "errors" "fmt" "log" "time" @@ -9,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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" @@ -16,40 +19,36 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -const ( - EndpointStatusDeleted = "DELETED" -) - -const ( - endpointCreatedDefaultTimeout = 10 * time.Minute - endpointUpdatedDefaultTimeout = 10 * time.Minute - endpointDeletedDefaultTimeout = 10 * time.Minute -) - func ResourceEndpoint() *schema.Resource { return &schema.Resource{ - Create: resourceEndpointCreate, - Read: resourceEndpointRead, - Update: resourceEndpointUpdate, - Delete: resourceEndpointDelete, + CreateWithoutTimeout: resourceEndpointCreate, + ReadWithoutTimeout: resourceEndpointRead, + UpdateWithoutTimeout: resourceEndpointUpdate, + DeleteWithoutTimeout: resourceEndpointDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "direction": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(route53resolver.ResolverEndpointDirection_Values(), false), + }, + "host_vpc_id": { Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - route53resolver.ResolverEndpointDirectionInbound, - route53resolver.ResolverEndpointDirectionOutbound, - }, false), + Computed: true, }, - "ip_address": { Type: schema.TypeSet, Required: true, @@ -57,10 +56,6 @@ func ResourceEndpoint() *schema.Resource { MaxItems: 10, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "subnet_id": { - Type: schema.TypeString, - Required: true, - }, "ip": { Type: schema.TypeString, Optional: true, @@ -71,11 +66,19 @@ func ResourceEndpoint() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + }, }, }, Set: endpointHashIPAddress, }, - + "name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validResolverName, + }, "security_group_ids": { Type: schema.TypeSet, Required: true, @@ -83,160 +86,125 @@ func ResourceEndpoint() *schema.Resource { MinItems: 1, MaxItems: 64, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, - - "name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validResolverName, - }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - - "arn": { - Type: schema.TypeString, - Computed: true, - }, - - "host_vpc_id": { - Type: schema.TypeString, - Computed: true, - }, }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(endpointCreatedDefaultTimeout), - Update: schema.DefaultTimeout(endpointUpdatedDefaultTimeout), - Delete: schema.DefaultTimeout(endpointDeletedDefaultTimeout), + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), }, CustomizeDiff: verify.SetTagsDiff, } } -func resourceEndpointCreate(d *schema.ResourceData, meta interface{}) error { +func resourceEndpointCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - req := &route53resolver.CreateResolverEndpointInput{ + input := &route53resolver.CreateResolverEndpointInput{ CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-endpoint-")), Direction: aws.String(d.Get("direction").(string)), IpAddresses: expandEndpointIPAddresses(d.Get("ip_address").(*schema.Set)), SecurityGroupIds: flex.ExpandStringSet(d.Get("security_group_ids").(*schema.Set)), } + if v, ok := d.GetOk("name"); ok { - req.Name = aws.String(v.(string)) + input.Name = aws.String(v.(string)) } - if v, ok := d.GetOk("tags"); ok && len(v.(map[string]interface{})) > 0 { - req.Tags = Tags(tags.IgnoreAWS()) + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Route53 Resolver endpoint: %#v", req) - resp, err := conn.CreateResolverEndpoint(req) + output, err := conn.CreateResolverEndpointWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route53 Resolver endpoint: %s", err) + return diag.Errorf("creating Route53 Resolver Endpoint: %s", err) } - d.SetId(aws.StringValue(resp.ResolverEndpoint.Id)) + d.SetId(aws.StringValue(output.ResolverEndpoint.Id)) - err = EndpointWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutCreate), - []string{route53resolver.ResolverEndpointStatusCreating}, - []string{route53resolver.ResolverEndpointStatusOperational}) - if err != nil { - return err + if _, err := waitEndpointCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Endpoint (%s) create: %s", d.Id(), err) } - return resourceEndpointRead(d, meta) + return resourceEndpointRead(ctx, d, meta) } -func resourceEndpointRead(d *schema.ResourceData, meta interface{}) error { +func resourceEndpointRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - epRaw, state, err := endpointRefresh(conn, d.Id())() - if err != nil { - return fmt.Errorf("error getting Route53 Resolver endpoint (%s): %s", d.Id(), err) - } - if state == EndpointStatusDeleted { - log.Printf("[WARN] Route53 Resolver endpoint (%s) not found, removing from state", d.Id()) + ep, err := FindResolverEndpointByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Endpoint (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - ep := epRaw.(*route53resolver.ResolverEndpoint) - d.Set("arn", ep.Arn) + if err != nil { + return diag.Errorf("reading Route53 Resolver Endpoint (%s): %s", d.Id(), err) + } + + arn := aws.StringValue(ep.Arn) + d.Set("arn", arn) d.Set("direction", ep.Direction) d.Set("host_vpc_id", ep.HostVPCId) d.Set("name", ep.Name) - if err := d.Set("security_group_ids", flex.FlattenStringSet(ep.SecurityGroupIds)); err != nil { - return err - } - - ipAddresses := []interface{}{} - req := &route53resolver.ListResolverEndpointIpAddressesInput{ - ResolverEndpointId: aws.String(d.Id()), - } - for { - resp, err := conn.ListResolverEndpointIpAddresses(req) - if err != nil { - return fmt.Errorf("error getting Route53 Resolver endpoint (%s) IP addresses: %s", d.Id(), err) - } + d.Set("security_group_ids", aws.StringValueSlice(ep.SecurityGroupIds)) - ipAddresses = append(ipAddresses, flattenEndpointIPAddresses(resp.IpAddresses)...) + ipAddresses, err := findResolverEndpointIPAddressesByID(ctx, conn, d.Id()) - if resp.NextToken == nil { - break - } - req.NextToken = resp.NextToken + if err != nil { + return diag.Errorf("listing Route53 Resolver Endpoint (%s) IP addresses: %s", d.Id(), err) } - if err := d.Set("ip_address", schema.NewSet(endpointHashIPAddress, ipAddresses)); err != nil { - return err + + if err := d.Set("ip_address", schema.NewSet(endpointHashIPAddress, flattenEndpointIPAddresses(ipAddresses))); err != nil { + return diag.Errorf("setting ip_address: %s", err) } - tags, err := ListTags(conn, d.Get("arn").(string)) + tags, err := ListTagsWithContext(ctx, conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver endpoint (%s): %s", d.Get("arn").(string), err) + return diag.Errorf("listing tags for Route53 Resolver Endpoint (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceEndpointUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChange("name") { - req := &route53resolver.UpdateResolverEndpointInput{ - ResolverEndpointId: aws.String(d.Id()), + _, err := conn.UpdateResolverEndpointWithContext(ctx, &route53resolver.UpdateResolverEndpointInput{ Name: aws.String(d.Get("name").(string)), - } + ResolverEndpointId: aws.String(d.Id()), + }) - log.Printf("[DEBUG] Updating Route53 Resolver endpoint: %#v", req) - _, err := conn.UpdateResolverEndpoint(req) if err != nil { - return fmt.Errorf("error updating Route53 Resolver endpoint (%s): %s", d.Id(), err) + return diag.Errorf("updating Route53 Resolver Endpoint (%s): %s", d.Id(), err) } - err = EndpointWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutUpdate), - []string{route53resolver.ResolverEndpointStatusUpdating}, - []string{route53resolver.ResolverEndpointStatusOperational}) - if err != nil { - return err + if _, err := waitEndpointUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Endpoint (%s) update: %s", d.Id(), err) } } @@ -249,103 +217,195 @@ func resourceEndpointUpdate(d *schema.ResourceData, meta interface{}) error { // Add new before deleting old so number of IP addresses doesn't drop below 2. for _, v := range add { - _, err := conn.AssociateResolverEndpointIpAddress(&route53resolver.AssociateResolverEndpointIpAddressInput{ - ResolverEndpointId: aws.String(d.Id()), + _, err := conn.AssociateResolverEndpointIpAddressWithContext(ctx, &route53resolver.AssociateResolverEndpointIpAddressInput{ IpAddress: expandEndpointIPAddressUpdate(v), + ResolverEndpointId: aws.String(d.Id()), }) + if err != nil { - return fmt.Errorf("error associating Route53 Resolver endpoint (%s) IP address: %s", d.Id(), err) + return diag.Errorf("associating Route53 Resolver Endpoint (%s) IP address: %s", d.Id(), err) } - err = EndpointWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutUpdate), - []string{route53resolver.ResolverEndpointStatusUpdating}, - []string{route53resolver.ResolverEndpointStatusOperational}) - if err != nil { - return err + if _, err := waitEndpointUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Endpoint (%s) update: %s", d.Id(), err) } } for _, v := range del { - _, err := conn.DisassociateResolverEndpointIpAddress(&route53resolver.DisassociateResolverEndpointIpAddressInput{ - ResolverEndpointId: aws.String(d.Id()), + _, err := conn.DisassociateResolverEndpointIpAddressWithContext(ctx, &route53resolver.DisassociateResolverEndpointIpAddressInput{ IpAddress: expandEndpointIPAddressUpdate(v), + ResolverEndpointId: aws.String(d.Id()), }) + if err != nil { - return fmt.Errorf("error disassociating Route53 Resolver endpoint (%s) IP address: %s", d.Id(), err) + return diag.Errorf("disassociating Route53 Resolver Endpoint (%s) IP address: %s", d.Id(), err) } - err = EndpointWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutUpdate), - []string{route53resolver.ResolverEndpointStatusUpdating}, - []string{route53resolver.ResolverEndpointStatusOperational}) - if err != nil { - return err + if _, err := waitEndpointUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Endpoint (%s) update: %s", d.Id(), err) } } } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Route53 Resolver endpoint (%s) tags: %s", d.Get("arn").(string), err) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating Route53 Resolver Endpoint (%s) tags: %s", d.Id(), err) } } - return resourceEndpointRead(d, meta) + return resourceEndpointRead(ctx, d, meta) } -func resourceEndpointDelete(d *schema.ResourceData, meta interface{}) error { +func resourceEndpointDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route53 Resolver endpoint: %s", d.Id()) - _, err := conn.DeleteResolverEndpoint(&route53resolver.DeleteResolverEndpointInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Endpoint: %s", d.Id()) + _, err := conn.DeleteResolverEndpointWithContext(ctx, &route53resolver.DeleteResolverEndpointInput{ ResolverEndpointId: aws.String(d.Id()), }) + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { return nil } + if err != nil { - return fmt.Errorf("error deleting Route53 Resolver endpoint (%s): %s", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Endpoint (%s): %s", d.Id(), err) } - return EndpointWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutDelete), - []string{route53resolver.ResolverEndpointStatusDeleting}, - []string{EndpointStatusDeleted}) + if _, err := waitEndpointDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Endpoint (%s) delete: %s", d.Id(), err) + } + + return nil } -func endpointRefresh(conn *route53resolver.Route53Resolver, epId string) resource.StateRefreshFunc { +func FindResolverEndpointByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverEndpoint, error) { + input := &route53resolver.GetResolverEndpointInput{ + ResolverEndpointId: aws.String(id), + } + + output, err := conn.GetResolverEndpointWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.ResolverEndpoint == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ResolverEndpoint, nil +} + +func findResolverEndpointIPAddressesByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) ([]*route53resolver.IpAddressResponse, error) { + input := &route53resolver.ListResolverEndpointIpAddressesInput{ + ResolverEndpointId: aws.String(id), + } + var output []*route53resolver.IpAddressResponse + + err := conn.ListResolverEndpointIpAddressesPagesWithContext(ctx, input, func(page *route53resolver.ListResolverEndpointIpAddressesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + output = append(output, page.IpAddresses...) + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func statusEndpoint(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.GetResolverEndpoint(&route53resolver.GetResolverEndpointInput{ - ResolverEndpointId: aws.String(epId), - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return &route53resolver.ResolverEndpoint{}, EndpointStatusDeleted, nil + output, err := FindResolverEndpointByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil } + if err != nil { return nil, "", err } - if statusMessage := aws.StringValue(resp.ResolverEndpoint.StatusMessage); statusMessage != "" { - log.Printf("[INFO] Route 53 Resolver endpoint (%s) status message: %s", epId, statusMessage) - } + return output, aws.StringValue(output.Status), nil + } +} + +func waitEndpointCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverEndpoint, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverEndpointStatusCreating}, + Target: []string{route53resolver.ResolverEndpointStatusOperational}, + Refresh: statusEndpoint(ctx, conn, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverEndpoint); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err + } - return resp.ResolverEndpoint, aws.StringValue(resp.ResolverEndpoint.Status), nil + return nil, err +} + +func waitEndpointUpdated(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverEndpoint, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverEndpointStatusUpdating}, + Target: []string{route53resolver.ResolverEndpointStatusOperational}, + Refresh: statusEndpoint(ctx, conn, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverEndpoint); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err + } + + return nil, err } -func EndpointWaitUntilTargetState(conn *route53resolver.Route53Resolver, epId string, timeout time.Duration, pending, target []string) error { +func waitEndpointDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverEndpoint, error) { stateConf := &resource.StateChangeConf{ - Pending: pending, - Target: target, - Refresh: endpointRefresh(conn, epId), + Pending: []string{route53resolver.ResolverEndpointStatusDeleting}, + Target: []string{}, + Refresh: statusEndpoint(ctx, conn, id), Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 5 * time.Second, } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for Route53 Resolver endpoint (%s) to reach target state: %s", epId, err) + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverEndpoint); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err } - return nil + return nil, err } func endpointHashIPAddress(v interface{}) int { @@ -354,3 +414,62 @@ func endpointHashIPAddress(v interface{}) int { buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string))) return create.StringHashcode(buf.String()) } + +func expandEndpointIPAddressUpdate(vIpAddress interface{}) *route53resolver.IpAddressUpdate { + ipAddressUpdate := &route53resolver.IpAddressUpdate{} + + mIpAddress := vIpAddress.(map[string]interface{}) + + if vSubnetId, ok := mIpAddress["subnet_id"].(string); ok && vSubnetId != "" { + ipAddressUpdate.SubnetId = aws.String(vSubnetId) + } + if vIp, ok := mIpAddress["ip"].(string); ok && vIp != "" { + ipAddressUpdate.Ip = aws.String(vIp) + } + if vIpId, ok := mIpAddress["ip_id"].(string); ok && vIpId != "" { + ipAddressUpdate.IpId = aws.String(vIpId) + } + + return ipAddressUpdate +} + +func expandEndpointIPAddresses(vIpAddresses *schema.Set) []*route53resolver.IpAddressRequest { + ipAddressRequests := []*route53resolver.IpAddressRequest{} + + for _, vIpAddress := range vIpAddresses.List() { + ipAddressRequest := &route53resolver.IpAddressRequest{} + + mIpAddress := vIpAddress.(map[string]interface{}) + + if vSubnetId, ok := mIpAddress["subnet_id"].(string); ok && vSubnetId != "" { + ipAddressRequest.SubnetId = aws.String(vSubnetId) + } + if vIp, ok := mIpAddress["ip"].(string); ok && vIp != "" { + ipAddressRequest.Ip = aws.String(vIp) + } + + ipAddressRequests = append(ipAddressRequests, ipAddressRequest) + } + + return ipAddressRequests +} + +func flattenEndpointIPAddresses(ipAddresses []*route53resolver.IpAddressResponse) []interface{} { + if ipAddresses == nil { + return []interface{}{} + } + + vIpAddresses := []interface{}{} + + for _, ipAddress := range ipAddresses { + mIpAddress := map[string]interface{}{ + "subnet_id": aws.StringValue(ipAddress.SubnetId), + "ip": aws.StringValue(ipAddress.Ip), + "ip_id": aws.StringValue(ipAddress.IpId), + } + + vIpAddresses = append(vIpAddresses, mIpAddress) + } + + return vIpAddresses +} diff --git a/internal/service/route53resolver/endpoint_data_source.go b/internal/service/route53resolver/endpoint_data_source.go index 8959ff8f0d1..e236583f35e 100644 --- a/internal/service/route53resolver/endpoint_data_source.go +++ b/internal/service/route53resolver/endpoint_data_source.go @@ -1,19 +1,28 @@ package route53resolver import ( - "fmt" + "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" ) func DataSourceEndpoint() *schema.Resource { return &schema.Resource{ - Read: dataSourceEndpointRead, + ReadWithoutTimeout: dataSourceEndpointRead, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "direction": { + Type: schema.TypeString, + Computed: true, + }, "filter": { Type: schema.TypeSet, Optional: true, @@ -32,27 +41,19 @@ func DataSourceEndpoint() *schema.Resource { }, }, }, - - "direction": { - Type: schema.TypeString, + "ip_addresses": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, }, - "name": { Type: schema.TypeString, Computed: true, }, - - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "resolver_endpoint_id": { Type: schema.TypeString, Optional: true, }, - "status": { Type: schema.TypeString, Computed: true, @@ -61,102 +62,73 @@ func DataSourceEndpoint() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "ip_addresses": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Computed: true, - Set: schema.HashString, - }, }, } } -func dataSourceEndpointRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceEndpointRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - req := &route53resolver.ListResolverEndpointsInput{} - - resolvers := make([]*route53resolver.ResolverEndpoint, 0) - rID, rIDOk := d.GetOk("resolver_endpoint_id") - filters, filtersOk := d.GetOk("filter") + endpointID := d.Get("resolver_endpoint_id").(string) + input := &route53resolver.ListResolverEndpointsInput{} - if filtersOk { - req.Filters = buildR53ResolverTagFilters(filters.(*schema.Set)) + if v, ok := d.GetOk("filter"); ok && v.(*schema.Set).Len() > 0 { + input.Filters = buildR53ResolverTagFilters(v.(*schema.Set)) } - for { - resp, err := conn.ListResolverEndpoints(req) + var endpoints []*route53resolver.ResolverEndpoint - if err != nil { - return fmt.Errorf("Error Reading Route53 Resolver Endpoints: %s", req) + err := conn.ListResolverEndpointsPagesWithContext(ctx, input, func(page *route53resolver.ListResolverEndpointsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - if len(resp.ResolverEndpoints) == 0 && filtersOk { - return fmt.Errorf("Your query returned no results. Please change your search criteria and try again") - } - - if len(resp.ResolverEndpoints) > 1 && !rIDOk { - return fmt.Errorf("Your query returned more than one resolver. Please change your search criteria and try again") - } - - if rIDOk { - for _, r := range resp.ResolverEndpoints { - if aws.StringValue(r.Id) == rID { - resolvers = append(resolvers, r) - break + for _, v := range page.ResolverEndpoints { + if endpointID != "" { + if aws.StringValue(v.Id) == endpointID { + endpoints = append(endpoints, v) } + } else { + endpoints = append(endpoints, v) } - } else { - resolvers = append(resolvers, resp.ResolverEndpoints[0]) - } - - if len(resolvers) == 0 { - return fmt.Errorf("The ID provided could not be found") } - resolver := resolvers[0] - - d.SetId(aws.StringValue(resolver.Id)) - d.Set("resolver_endpoint_id", resolver.Id) - d.Set("arn", resolver.Arn) - d.Set("status", resolver.Status) - d.Set("name", resolver.Name) - d.Set("vpc_id", resolver.HostVPCId) - d.Set("direction", resolver.Direction) - - if resp.NextToken == nil { - break - } + return !lastPage + }) - req.NextToken = resp.NextToken + if err != nil { + return diag.Errorf("listing Route53 Resolver Endpoints: %s", err) } - params := &route53resolver.ListResolverEndpointIpAddressesInput{ - ResolverEndpointId: aws.String(d.Id()), + if n := len(endpoints); n == 0 { + return diag.Errorf("no Route53 Resolver Endpoint matched") + } else if n > 1 { + return diag.Errorf("%d Route53 Resolver Endpoints matched; use additional constraints to reduce matches to a single Endpoint", n) } - ipAddresses := []interface{}{} - - for { - ip, err := conn.ListResolverEndpointIpAddresses(params) + ep := endpoints[0] + d.SetId(aws.StringValue(ep.Id)) + d.Set("arn", ep.Arn) + d.Set("direction", ep.Direction) + d.Set("name", ep.Name) + d.Set("resolver_endpoint_id", ep.Id) + d.Set("status", ep.Status) + d.Set("vpc_id", ep.HostVPCId) - if err != nil { - return fmt.Errorf("error getting Route53 Resolver endpoint (%s) IP Addresses: %w", d.Id(), err) - } + ipAddresses, err := findResolverEndpointIPAddressesByID(ctx, conn, d.Id()) - for _, vIPAddresses := range ip.IpAddresses { - ipAddresses = append(ipAddresses, aws.StringValue(vIPAddresses.Ip)) - } + if err != nil { + return diag.Errorf("listing Route53 Resolver Endpoint (%s) IP addresses: %s", d.Id(), err) + } - d.Set("ip_addresses", ipAddresses) + var ips []*string - if ip.NextToken == nil { - break - } - - params.NextToken = ip.NextToken + for _, v := range ipAddresses { + ips = append(ips, v.Ip) } + d.Set("ip_addresses", aws.StringValueSlice(ips)) + return nil } diff --git a/internal/service/route53resolver/endpoint_data_source_test.go b/internal/service/route53resolver/endpoint_data_source_test.go index 673a99768e4..8398eca8eca 100644 --- a/internal/service/route53resolver/endpoint_data_source_test.go +++ b/internal/service/route53resolver/endpoint_data_source_test.go @@ -1,8 +1,6 @@ package route53resolver_test import ( - "fmt" - "regexp" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" @@ -12,11 +10,9 @@ import ( ) func TestAccRoute53ResolverEndpointDataSource_basic(t *testing.T) { - name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rInt := sdkacctest.RandInt() - direction := "INBOUND" - resourceName := "aws_route53_resolver_endpoint.foo" - datasourceName := "data.aws_route53_resolver_endpoint.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_route53_resolver_endpoint.test" + datasourceName := "data.aws_route53_resolver_endpoint.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -24,16 +20,15 @@ func TestAccRoute53ResolverEndpointDataSource_basic(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEndpointDataSourceConfig_nonExistent, - ExpectError: regexp.MustCompile("The ID provided could not be found"), - }, - { - Config: testAccEndpointDataSourceConfig_initial(rInt, direction, name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + Config: testAccEndpointDataSourceConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "direction", resourceName, "direction"), resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), + resource.TestCheckResourceAttrPair(datasourceName, "ip_addresses.#", resourceName, "ip_address.#"), + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(datasourceName, "resolver_endpoint_id", resourceName, "id"), - resource.TestCheckResourceAttr(datasourceName, "ip_addresses.#", "2"), + resource.TestCheckResourceAttrPair(datasourceName, "vpc_id", resourceName, "host_vpc_id"), ), }, }, @@ -41,179 +36,52 @@ func TestAccRoute53ResolverEndpointDataSource_basic(t *testing.T) { } func TestAccRoute53ResolverEndpointDataSource_filter(t *testing.T) { - name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rInt := sdkacctest.RandInt() - direction := "OUTBOUND" - resourceName := "aws_route53_resolver_endpoint.foo" - datasourceName := "data.aws_route53_resolver_endpoint.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_route53_resolver_endpoint.test" + datasourceName := "data.aws_route53_resolver_endpoint.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ + { - Config: testAccEndpointDataSourceConfig_nonExistentFilter, - ExpectError: regexp.MustCompile("Your query returned no results. Please change your search criteria and try again"), - }, - { - Config: testAccEndpointDataSourceConfig_filter(rInt, direction, name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + Config: testAccEndpointDataSourceConfig_filter(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "direction", resourceName, "direction"), resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), + resource.TestCheckResourceAttrPair(datasourceName, "ip_addresses.#", resourceName, "ip_address.#"), + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(datasourceName, "resolver_endpoint_id", resourceName, "id"), - resource.TestCheckResourceAttr(datasourceName, "ip_addresses.#", "2"), + resource.TestCheckResourceAttrPair(datasourceName, "vpc_id", resourceName, "host_vpc_id"), ), }, }, }) } -func testAccDataSourceEndpointConfig_base(rInt int) string { - return acctest.ConfigAvailableAZsNoOptIn() + fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.0.0.0/16" - enable_dns_support = true - enable_dns_hostnames = true - - tags = { - Name = "terraform-testacc-r53-resolver-vpc-%[1]d" - } -} - -resource "aws_subnet" "sn1" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 0) - availability_zone = data.aws_availability_zones.available.names[0] - - tags = { - Name = "tf-acc-r53-resolver-sn1-%[1]d" - } -} - -resource "aws_subnet" "sn2" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1) - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = "tf-acc-r53-resolver-sn2-%[1]d" - } +func testAccEndpointDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccEndpointConfig_basic(rName), ` +data "aws_route53_resolver_endpoint" "test" { + resolver_endpoint_id = aws_route53_resolver_endpoint.test.id } - -resource "aws_subnet" "sn3" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 2) - availability_zone = data.aws_availability_zones.available.names[2] - - tags = { - Name = "tf-acc-r53-resolver-sn3-%[1]d" - } +`) } -resource "aws_security_group" "sg1" { - vpc_id = aws_vpc.foo.id - name = "tf-acc-r53-resolver-sg1-%[1]d" - - tags = { - Name = "tf-acc-r53-resolver-sg1-%[1]d" - } -} - -resource "aws_security_group" "sg2" { - vpc_id = aws_vpc.foo.id - name = "tf-acc-r53-resolver-sg2-%[1]d" - - tags = { - Name = "tf-acc-r53-resolver-sg2-%[1]d" - } -} -`, rInt) -} - -func testAccEndpointDataSourceConfig_initial(rInt int, direction, name string) string { - return acctest.ConfigCompose(testAccDataSourceEndpointConfig_base(rInt), fmt.Sprintf(` -resource "aws_route53_resolver_endpoint" "foo" { - direction = "%s" - name = "%s" - - security_group_ids = [ - aws_security_group.sg1.id, - aws_security_group.sg2.id, - ] - - ip_address { - subnet_id = aws_subnet.sn1.id - } - - ip_address { - subnet_id = aws_subnet.sn2.id - ip = cidrhost(aws_subnet.sn2.cidr_block, 8) - } - - tags = { - Environment = "production" - Usage = "original" - } -} - -data "aws_route53_resolver_endpoint" "foo" { - resolver_endpoint_id = aws_route53_resolver_endpoint.foo.id -} -`, direction, name)) -} - -func testAccEndpointDataSourceConfig_filter(rInt int, direction, name string) string { - return acctest.ConfigCompose(testAccDataSourceEndpointConfig_base(rInt), fmt.Sprintf(` -resource "aws_route53_resolver_endpoint" "foo" { - direction = "%s" - name = "%s" - - security_group_ids = [ - aws_security_group.sg1.id, - aws_security_group.sg2.id, - ] - - ip_address { - subnet_id = aws_subnet.sn1.id - } - - ip_address { - subnet_id = aws_subnet.sn2.id - ip = cidrhost(aws_subnet.sn2.cidr_block, 8) - } - - tags = { - Environment = "production" - Usage = "original" - } -} - -data "aws_route53_resolver_endpoint" "foo" { +func testAccEndpointDataSourceConfig_filter(rName string) string { + return acctest.ConfigCompose(testAccEndpointConfig_outbound(rName, rName), ` +data "aws_route53_resolver_endpoint" "test" { filter { name = "Name" - values = [aws_route53_resolver_endpoint.foo.name] + values = [aws_route53_resolver_endpoint.test.name] } filter { name = "SecurityGroupIds" - values = [aws_security_group.sg1.id, aws_security_group.sg2.id] + values = aws_security_group.test[*].id } } -`, direction, name)) -} - -const testAccEndpointDataSourceConfig_nonExistent = ` -data "aws_route53_resolver_endpoint" "foo" { - resolver_endpoint_id = "rslvr-in-8g85830108dd4c82b" -} -` - -const testAccEndpointDataSourceConfig_nonExistentFilter = ` -data "aws_route53_resolver_endpoint" "foo" { - filter { - name = "Name" - values = ["None-Existent-Resource"] - } +`) } -` diff --git a/internal/service/route53resolver/endpoint_test.go b/internal/service/route53resolver/endpoint_test.go index cc4efe753cd..b7a3434f444 100644 --- a/internal/service/route53resolver/endpoint_test.go +++ b/internal/service/route53resolver/endpoint_test.go @@ -1,24 +1,25 @@ package route53resolver_test import ( + "context" "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func TestAccRoute53ResolverEndpoint_basicInbound(t *testing.T) { +func TestAccRoute53ResolverEndpoint_basic(t *testing.T) { var ep route53resolver.ResolverEndpoint - resourceName := "aws_route53_resolver_endpoint.foo" - rInt := sdkacctest.RandInt() - name := fmt.Sprintf("terraform-testacc-r53-resolver-%d", rInt) + resourceName := "aws_route53_resolver_endpoint.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -27,14 +28,16 @@ func TestAccRoute53ResolverEndpoint_basicInbound(t *testing.T) { CheckDestroy: testAccCheckEndpointDestroy, Steps: []resource.TestStep{ { - Config: testAccEndpointConfig_initial(rInt, "INBOUND", name), - Check: resource.ComposeTestCheckFunc( + Config: testAccEndpointConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEndpointExists(resourceName, &ep), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "ip_address.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "direction", "INBOUND"), + resource.TestCheckResourceAttrPair(resourceName, "host_vpc_id", vpcResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "ip_address.#", "3"), + resource.TestCheckResourceAttr(resourceName, "name", ""), resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.Usage", "original"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -46,12 +49,80 @@ func TestAccRoute53ResolverEndpoint_basicInbound(t *testing.T) { }) } +func TestAccRoute53ResolverEndpoint_disappears(t *testing.T) { + var ep route53resolver.ResolverEndpoint + resourceName := "aws_route53_resolver_endpoint.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEndpointConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckEndpointExists(resourceName, &ep), + acctest.CheckResourceDisappears(acctest.Provider, tfroute53resolver.ResourceEndpoint(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRoute53ResolverEndpoint_tags(t *testing.T) { + var ep route53resolver.ResolverEndpoint + resourceName := "aws_route53_resolver_endpoint.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEndpointConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckEndpointExists(resourceName, &ep), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEndpointConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckEndpointExists(resourceName, &ep), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccEndpointConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckEndpointExists(resourceName, &ep), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func TestAccRoute53ResolverEndpoint_updateOutbound(t *testing.T) { var ep route53resolver.ResolverEndpoint - resourceName := "aws_route53_resolver_endpoint.foo" - rInt := sdkacctest.RandInt() - initialName := fmt.Sprintf("terraform-testacc-r53-resolver-%d", rInt) - updatedName := fmt.Sprintf("terraform-testacc-r53-rupdated-%d", rInt) + resourceName := "aws_route53_resolver_endpoint.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + initialName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + updatedName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -60,25 +131,21 @@ func TestAccRoute53ResolverEndpoint_updateOutbound(t *testing.T) { CheckDestroy: testAccCheckEndpointDestroy, Steps: []resource.TestStep{ { - Config: testAccEndpointConfig_initial(rInt, "OUTBOUND", initialName), + Config: testAccEndpointConfig_outbound(rName, initialName), Check: resource.ComposeTestCheckFunc( testAccCheckEndpointExists(resourceName, &ep), - resource.TestCheckResourceAttr(resourceName, "name", initialName), + resource.TestCheckResourceAttr(resourceName, "direction", "OUTBOUND"), resource.TestCheckResourceAttr(resourceName, "ip_address.#", "2"), - resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.Usage", "original"), + resource.TestCheckResourceAttr(resourceName, "name", initialName), ), }, { - Config: testAccEndpointConfig_updated(rInt, "OUTBOUND", updatedName), + Config: testAccEndpointConfig_updatedOutbound(rName, updatedName), Check: resource.ComposeTestCheckFunc( testAccCheckEndpointExists(resourceName, &ep), + resource.TestCheckResourceAttr(resourceName, "direction", "OUTBOUND"), + resource.TestCheckResourceAttr(resourceName, "ip_address.#", "3"), resource.TestCheckResourceAttr(resourceName, "name", updatedName), - resource.TestCheckResourceAttr(resourceName, "ip_address.#", "2"), - resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Usage", "changed"), ), }, }, @@ -93,24 +160,23 @@ func testAccCheckEndpointDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := conn.GetResolverEndpoint(&route53resolver.GetResolverEndpointInput{ - ResolverEndpointId: aws.String(rs.Primary.ID), - }) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindResolverEndpointByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver endpoint still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Endpoint still exists: %s", rs.Primary.ID) } return nil } -func testAccCheckEndpointExists(n string, ep *route53resolver.ResolverEndpoint) resource.TestCheckFunc { +func testAccCheckEndpointExists(n string, v *route53resolver.ResolverEndpoint) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -118,18 +184,18 @@ func testAccCheckEndpointExists(n string, ep *route53resolver.ResolverEndpoint) } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver endpoint ID is set") + return fmt.Errorf("No Route53 Resolver Endpoint ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - resp, err := conn.GetResolverEndpoint(&route53resolver.GetResolverEndpointInput{ - ResolverEndpointId: aws.String(rs.Primary.ID), - }) + + output, err := tfroute53resolver.FindResolverEndpointByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *ep = *resp.ResolverEndpoint + *v = *output return nil } @@ -151,131 +217,157 @@ func testAccPreCheck(t *testing.T) { } } -func testAccEndpointConfig_base(rInt int) string { - return fmt.Sprintf(` -resource "aws_vpc" "foo" { +func testAccEndpointConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { - Name = "terraform-testacc-r53-resolver-vpc-%[1]d" + Name = %[1]q } } -data "aws_availability_zones" "available" { - state = "available" +resource "aws_subnet" "test" { + count = 3 + + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -resource "aws_subnet" "sn1" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 0) - availability_zone = data.aws_availability_zones.available.names[0] +resource "aws_security_group" "test" { + count = 2 + + vpc_id = aws_vpc.test.id + name = "%[1]s-${count.index}" tags = { - Name = "tf-acc-r53-resolver-sn1-%[1]d" + Name = %[1]q } } +`, rName)) +} -resource "aws_subnet" "sn2" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1) - availability_zone = data.aws_availability_zones.available.names[1] +func testAccEndpointConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccEndpointConfig_base(rName), ` +resource "aws_route53_resolver_endpoint" "test" { + direction = "INBOUND" - tags = { - Name = "tf-acc-r53-resolver-sn2-%[1]d" + security_group_ids = aws_security_group.test[*].id + + ip_address { + subnet_id = aws_subnet.test[0].id } -} -resource "aws_subnet" "sn3" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 2) - availability_zone = data.aws_availability_zones.available.names[2] + ip_address { + subnet_id = aws_subnet.test[1].id + } - tags = { - Name = "tf-acc-r53-resolver-sn3-%[1]d" + ip_address { + subnet_id = aws_subnet.test[2].id } } +`) +} -resource "aws_security_group" "sg1" { - vpc_id = aws_vpc.foo.id - name = "tf-acc-r53-resolver-sg1-%[1]d" +func testAccEndpointConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccEndpointConfig_base(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + direction = "INBOUND" - tags = { - Name = "tf-acc-r53-resolver-sg1-%[1]d" + security_group_ids = aws_security_group.test[*].id + + ip_address { + subnet_id = aws_subnet.test[0].id } -} -resource "aws_security_group" "sg2" { - vpc_id = aws_vpc.foo.id - name = "tf-acc-r53-resolver-sg2-%[1]d" + ip_address { + subnet_id = aws_subnet.test[1].id + } + + ip_address { + subnet_id = aws_subnet.test[2].id + } tags = { - Name = "tf-acc-r53-resolver-sg2-%[1]d" + %[1]q = %[2]q } } -`, rInt) +`, tagKey1, tagValue1)) } -func testAccEndpointConfig_initial(rInt int, direction, name string) string { - return fmt.Sprintf(` -%s +func testAccEndpointConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccEndpointConfig_base(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + direction = "INBOUND" -resource "aws_route53_resolver_endpoint" "foo" { - direction = "%s" - name = "%s" + security_group_ids = aws_security_group.test[*].id - security_group_ids = [ - aws_security_group.sg1.id, - aws_security_group.sg2.id, - ] + ip_address { + subnet_id = aws_subnet.test[0].id + } ip_address { - subnet_id = aws_subnet.sn1.id + subnet_id = aws_subnet.test[1].id } ip_address { - subnet_id = aws_subnet.sn2.id - ip = cidrhost(aws_subnet.sn2.cidr_block, 8) + subnet_id = aws_subnet.test[2].id } tags = { - Environment = "production" - Usage = "original" + %[1]q = %[2]q + %[3]q = %[4]q } } -`, testAccEndpointConfig_base(rInt), direction, name) +`, tagKey1, tagValue1, tagKey2, tagValue2)) } -func testAccEndpointConfig_updated(rInt int, direction, name string) string { - return fmt.Sprintf(` -%s +func testAccEndpointConfig_outbound(rName, name string) string { + return acctest.ConfigCompose(testAccEndpointConfig_base(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + direction = "OUTBOUND" + name = %[1]q + + security_group_ids = aws_security_group.test[*].id + + ip_address { + subnet_id = aws_subnet.test[0].id + } + + ip_address { + subnet_id = aws_subnet.test[1].id + ip = cidrhost(aws_subnet.test[1].cidr_block, 8) + } +} +`, name)) +} -resource "aws_route53_resolver_endpoint" "foo" { - direction = "%s" - name = "%s" +func testAccEndpointConfig_updatedOutbound(rName, name string) string { + return acctest.ConfigCompose(testAccEndpointConfig_base(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + direction = "OUTBOUND" + name = %[1]q - security_group_ids = [ - aws_security_group.sg1.id, - aws_security_group.sg2.id, - ] + security_group_ids = aws_security_group.test[*].id ip_address { - subnet_id = aws_subnet.sn1.id + subnet_id = aws_subnet.test[2].id } ip_address { - subnet_id = aws_subnet.sn3.id + subnet_id = aws_subnet.test[1].id } - tags = { - Usage = "changed" + ip_address { + subnet_id = aws_subnet.test[0].id } } -`, testAccEndpointConfig_base(rInt), direction, name) +`, name)) } diff --git a/internal/service/route53resolver/find.go b/internal/service/route53resolver/find.go deleted file mode 100644 index 84fa3ddaa7e..00000000000 --- a/internal/service/route53resolver/find.go +++ /dev/null @@ -1,210 +0,0 @@ -package route53resolver - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -// FindResolverQueryLogConfigAssociationByID returns the query logging configuration association corresponding to the specified ID. -// Returns nil if no configuration is found. -func FindResolverQueryLogConfigAssociationByID(conn *route53resolver.Route53Resolver, queryLogConfigAssociationID string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { - input := &route53resolver.GetResolverQueryLogConfigAssociationInput{ - ResolverQueryLogConfigAssociationId: aws.String(queryLogConfigAssociationID), - } - - output, err := conn.GetResolverQueryLogConfigAssociation(input) - if err != nil { - return nil, err - } - - if output == nil { - return nil, nil - } - - return output.ResolverQueryLogConfigAssociation, nil -} - -// FindResolverQueryLogConfigByID returns the query logging configuration corresponding to the specified ID. -// Returns nil if no configuration is found. -func FindResolverQueryLogConfigByID(conn *route53resolver.Route53Resolver, queryLogConfigID string) (*route53resolver.ResolverQueryLogConfig, error) { - input := &route53resolver.GetResolverQueryLogConfigInput{ - ResolverQueryLogConfigId: aws.String(queryLogConfigID), - } - - output, err := conn.GetResolverQueryLogConfig(input) - if err != nil { - return nil, err - } - - if output == nil { - return nil, nil - } - - return output.ResolverQueryLogConfig, nil -} - -// FindResolverDNSSECConfigByID returns the dnssec configuration corresponding to the specified ID. -// Returns nil if no configuration is found. -func FindResolverDNSSECConfigByID(conn *route53resolver.Route53Resolver, dnssecConfigID string) (*route53resolver.ResolverDnssecConfig, error) { - input := &route53resolver.ListResolverDnssecConfigsInput{} - - var config *route53resolver.ResolverDnssecConfig - // GetResolverDnssecConfigs does not support query with id - err := conn.ListResolverDnssecConfigsPages(input, func(page *route53resolver.ListResolverDnssecConfigsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, c := range page.ResolverDnssecConfigs { - if aws.StringValue(c.Id) == dnssecConfigID { - config = c - return false - } - } - - return !lastPage - }) - - if err != nil { - return nil, err - } - - if config == nil { - return nil, nil - } - - return config, nil -} - -// FindFirewallRuleGroupByID returns the DNS Firewall rule group corresponding to the specified ID. -// Returns nil if no DNS Firewall rule group is found. -func FindFirewallRuleGroupByID(conn *route53resolver.Route53Resolver, firewallGroupId string) (*route53resolver.FirewallRuleGroup, error) { - input := &route53resolver.GetFirewallRuleGroupInput{ - FirewallRuleGroupId: aws.String(firewallGroupId), - } - - output, err := conn.GetFirewallRuleGroup(input) - if err != nil { - return nil, err - } - - if output == nil { - return nil, nil - } - - return output.FirewallRuleGroup, nil -} - -// FindFirewallDomainListByID returns the DNS Firewall rule group corresponding to the specified ID. -// Returns nil if no DNS Firewall rule group is found. -func FindFirewallDomainListByID(conn *route53resolver.Route53Resolver, firewallDomainListId string) (*route53resolver.FirewallDomainList, error) { - input := &route53resolver.GetFirewallDomainListInput{ - FirewallDomainListId: aws.String(firewallDomainListId), - } - - output, err := conn.GetFirewallDomainList(input) - - if err != nil { - return nil, err - } - - if output == nil { - return nil, nil - } - - return output.FirewallDomainList, nil -} - -// FindFirewallConfigByID returns the dnssec configuration corresponding to the specified ID. -// Returns NotFoundError if no configuration is found. -func FindFirewallConfigByID(conn *route53resolver.Route53Resolver, firewallConfigID string) (*route53resolver.FirewallConfig, error) { - input := &route53resolver.ListFirewallConfigsInput{} - - var config *route53resolver.FirewallConfig - // GetFirewallConfigs does not support query with id - err := conn.ListFirewallConfigsPages(input, func(page *route53resolver.ListFirewallConfigsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, c := range page.FirewallConfigs { - if aws.StringValue(c.Id) == firewallConfigID { - config = c - return false - } - } - - return !lastPage - }) - - if err != nil { - return nil, err - } - - if config == nil { - return nil, &resource.NotFoundError{} - } - - return config, nil -} - -// FindFirewallRuleByID returns the DNS Firewall rule corresponding to the specified rule group and domain list IDs. -// Returns nil if no DNS Firewall rule is found. -func FindFirewallRuleByID(conn *route53resolver.Route53Resolver, firewallRuleId string) (*route53resolver.FirewallRule, error) { - firewallRuleGroupId, firewallDomainListId, err := FirewallRuleParseID(firewallRuleId) - - if err != nil { - return nil, err - } - - var rule *route53resolver.FirewallRule - - input := &route53resolver.ListFirewallRulesInput{ - FirewallRuleGroupId: aws.String(firewallRuleGroupId), - } - - err = conn.ListFirewallRulesPages(input, func(page *route53resolver.ListFirewallRulesOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, r := range page.FirewallRules { - if aws.StringValue(r.FirewallDomainListId) == firewallDomainListId { - rule = r - return false - } - } - - return !lastPage - }) - - if err != nil { - return nil, err - } - - if rule == nil { - return nil, nil - } - - return rule, nil -} - -// FindFirewallRuleGroupAssociationByID returns the DNS Firewall rule group association corresponding to the specified ID. -// Returns nil if no DNS Firewall rule group association is found. -func FindFirewallRuleGroupAssociationByID(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/internal/service/route53resolver/firewall_config.go b/internal/service/route53resolver/firewall_config.go index 2b6c7657938..8fff0614b76 100644 --- a/internal/service/route53resolver/firewall_config.go +++ b/internal/service/route53resolver/firewall_config.go @@ -1,11 +1,13 @@ package route53resolver import ( - "fmt" + "context" "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/hashicorp/terraform-provider-aws/internal/conns" @@ -14,37 +16,36 @@ import ( func ResourceFirewallConfig() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallConfigCreate, - Read: resourceFirewallConfigRead, - Update: resourceFirewallConfigUpdate, - Delete: resourceFirewallConfigDelete, + CreateWithoutTimeout: resourceFirewallConfigCreate, + ReadWithoutTimeout: resourceFirewallConfigRead, + UpdateWithoutTimeout: resourceFirewallConfigUpdate, + DeleteWithoutTimeout: resourceFirewallConfigDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ + "firewall_fail_open": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(route53resolver.FirewallFailOpenStatus_Values(), false), + }, "owner_id": { Type: schema.TypeString, Computed: true, }, - "resource_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "firewall_fail_open": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice(route53resolver.FirewallFailOpenStatus_Values(), false), - }, }, } } -func resourceFirewallConfigCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn input := &route53resolver.UpdateFirewallConfigInput{ @@ -55,40 +56,40 @@ func resourceFirewallConfigCreate(d *schema.ResourceData, meta interface{}) erro input.FirewallFailOpen = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating Route 53 Resolver DNS Firewall config: %#v", input) - output, err := conn.UpdateFirewallConfig(input) + output, err := conn.UpdateFirewallConfigWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall config: %w", err) + return diag.Errorf("creating Route53 Resolver Firewall Config: %s", err) } d.SetId(aws.StringValue(output.FirewallConfig.Id)) - return resourceFirewallConfigRead(d, meta) + return resourceFirewallConfigRead(ctx, d, meta) } -func resourceFirewallConfigRead(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - config, err := FindFirewallConfigByID(conn, d.Id()) + firewallConfig, err := FindFirewallConfigByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Route 53 Resolver DNS Firewall config (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Route53 Resolver Firewall Config (%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 config (%s): %w", d.Id(), err) + return diag.Errorf("reading Route53 Resolver Firewall Config (%s): %s", d.Id(), err) } - d.Set("owner_id", config.OwnerId) - d.Set("resource_id", config.ResourceId) - d.Set("firewall_fail_open", config.FirewallFailOpen) + d.Set("firewall_fail_open", firewallConfig.FirewallFailOpen) + d.Set("owner_id", firewallConfig.OwnerId) + d.Set("resource_id", firewallConfig.ResourceId) return nil } -func resourceFirewallConfigUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallConfigUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn input := &route53resolver.UpdateFirewallConfigInput{ @@ -99,27 +100,59 @@ func resourceFirewallConfigUpdate(d *schema.ResourceData, meta interface{}) erro input.FirewallFailOpen = aws.String(v.(string)) } - log.Printf("[DEBUG] Updating Route 53 Resolver DNS Firewall config: %#v", input) - _, err := conn.UpdateFirewallConfig(input) + _, err := conn.UpdateFirewallConfigWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall config: %w", err) + return diag.Errorf("updating Route53 Resolver Firewall Config: %s", err) } - return resourceFirewallConfigRead(d, meta) + return resourceFirewallConfigRead(ctx, d, meta) } -func resourceFirewallConfigDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallConfigDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route 53 Resolver DNS Firewall config") - _, err := conn.UpdateFirewallConfig(&route53resolver.UpdateFirewallConfigInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Firewall Config: %s", d.Id()) + _, err := conn.UpdateFirewallConfigWithContext(ctx, &route53resolver.UpdateFirewallConfigInput{ ResourceId: aws.String(d.Get("resource_id").(string)), FirewallFailOpen: aws.String(route53resolver.FirewallFailOpenStatusDisabled), }) if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall config (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Firewall Config (%s): %s", d.Id(), err) } return nil } + +func FindFirewallConfigByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallConfig, error) { + input := &route53resolver.ListFirewallConfigsInput{} + var output *route53resolver.FirewallConfig + + // GetFirewallConfig does not support query by ID. + err := conn.ListFirewallConfigsPagesWithContext(ctx, input, func(page *route53resolver.ListFirewallConfigsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.FirewallConfigs { + if aws.StringValue(v.Id) == id { + output = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, &resource.NotFoundError{LastRequest: input} + } + + return output, nil +} diff --git a/internal/service/route53resolver/firewall_config_test.go b/internal/service/route53resolver/firewall_config_test.go index 1c4142ddd1d..3b41e71b665 100644 --- a/internal/service/route53resolver/firewall_config_test.go +++ b/internal/service/route53resolver/firewall_config_test.go @@ -1,6 +1,7 @@ package route53resolver_test import ( + "context" "fmt" "testing" @@ -74,7 +75,7 @@ func testAccCheckFirewallConfigDestroy(s *terraform.State) error { continue } - config, err := tfroute53resolver.FindFirewallConfigByID(conn, rs.Primary.ID) + config, err := tfroute53resolver.FindFirewallConfigByID(context.Background(), conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -88,7 +89,7 @@ func testAccCheckFirewallConfigDestroy(s *terraform.State) error { return nil } - return fmt.Errorf("Route 53 Resolver DNS Firewall config still exists: %s", rs.Primary.ID) + return fmt.Errorf("Route53 Resolver Firewall Config still exists: %s", rs.Primary.ID) } return nil @@ -102,36 +103,28 @@ func testAccCheckFirewallConfigExists(n string, v *route53resolver.FirewallConfi } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver DNS Firewall config ID is set") + return fmt.Errorf("No Route53 Resolver Firewall Config ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindFirewallConfigByID(conn, rs.Primary.ID) + output, err := tfroute53resolver.FindFirewallConfigByID(context.Background(), conn, rs.Primary.ID) if err != nil { return err } - *v = *out + *v = *output return nil } } func testAccFirewallConfigConfig_basic(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), ` resource "aws_route53_resolver_firewall_config" "test" { resource_id = aws_vpc.test.id firewall_fail_open = "ENABLED" } -`, rName) +`) } diff --git a/internal/service/route53resolver/firewall_domain_list.go b/internal/service/route53resolver/firewall_domain_list.go index ce8f8802a71..4d2da0b3420 100644 --- a/internal/service/route53resolver/firewall_domain_list.go +++ b/internal/service/route53resolver/firewall_domain_list.go @@ -1,26 +1,31 @@ package route53resolver import ( - "fmt" + "context" + "errors" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceFirewallDomainList() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallDomainListCreate, - Read: resourceFirewallDomainListRead, - Update: resourceFirewallDomainListUpdate, - Delete: resourceFirewallDomainListDelete, + CreateWithoutTimeout: resourceFirewallDomainListCreate, + ReadWithoutTimeout: resourceFirewallDomainListRead, + UpdateWithoutTimeout: resourceFirewallDomainListUpdate, + DeleteWithoutTimeout: resourceFirewallDomainListDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -30,23 +35,18 @@ func ResourceFirewallDomainList() *schema.Resource { Type: schema.TypeString, Computed: true, }, - + "domains": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "name": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validResolverName, }, - - "domains": { - Type: schema.TypeSet, - Optional: true, - MinItems: 0, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "tags": tftags.TagsSchema(), - + "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -54,53 +54,63 @@ func ResourceFirewallDomainList() *schema.Resource { } } -func resourceFirewallDomainListCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallDomainListCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) input := &route53resolver.CreateFirewallDomainListInput{ CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-firewall-domain-list-")), - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), } if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Route 53 Resolver DNS Firewall domain list: %#v", input) - output, err := conn.CreateFirewallDomainList(input) + output, err := conn.CreateFirewallDomainListWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall domain list: %w", err) + return diag.Errorf("creating Route53 Resolver Firewall Domain List (%s): %s", name, err) } d.SetId(aws.StringValue(output.FirewallDomainList.Id)) - d.Set("arn", output.FirewallDomainList.Arn) - return resourceFirewallDomainListUpdate(d, meta) + if v, ok := d.GetOk("domains"); ok && v.(*schema.Set).Len() > 0 { + _, err := conn.UpdateFirewallDomainsWithContext(ctx, &route53resolver.UpdateFirewallDomainsInput{ + FirewallDomainListId: aws.String(d.Id()), + Domains: flex.ExpandStringSet(v.(*schema.Set)), + Operation: aws.String(route53resolver.FirewallDomainUpdateOperationAdd), + }) + + if err != nil { + return diag.Errorf("updating Route53 Resolver Firewall Domain List (%s) domains: %s", d.Id(), err) + } + + if _, err = waitFirewallDomainListUpdated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Domain List (%s) update: %s", d.Id(), err) + } + } + + return resourceFirewallDomainListRead(ctx, d, meta) } -func resourceFirewallDomainListRead(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallDomainListRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - firewallDomainList, err := FindFirewallDomainListByID(conn, d.Id()) + firewallDomainList, err := FindFirewallDomainListByID(ctx, conn, d.Id()) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Route53 Resolver DNS Firewall domain list (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Firewall Domain List (%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 domain list (%s): %w", d.Id(), err) - } - - if firewallDomainList == nil { - log.Printf("[WARN] Route 53 Resolver DNS Firewall domain list (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return diag.Errorf("reading Route53 Resolver Firewall Domain List (%s): %s", d.Id(), err) } arn := aws.StringValue(firewallDomainList.Arn) @@ -110,40 +120,45 @@ func resourceFirewallDomainListRead(d *schema.ResourceData, meta interface{}) er input := &route53resolver.ListFirewallDomainsInput{ FirewallDomainListId: aws.String(d.Id()), } + var output []*string - domains := []*string{} + err = conn.ListFirewallDomainsPagesWithContext(ctx, input, func(page *route53resolver.ListFirewallDomainsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + output = append(output, page.Domains...) - err = conn.ListFirewallDomainsPages(input, func(output *route53resolver.ListFirewallDomainsOutput, lastPage bool) bool { - domains = append(domains, output.Domains...) return !lastPage }) if err != nil { - return fmt.Errorf("error listing Route 53 Resolver DNS Firewall domain list (%s) domains: %w", d.Id(), err) + return diag.Errorf("listing Route53 Resolver Firewall Domain List (%s) domains: %s", d.Id(), err) } - d.Set("domains", flex.FlattenStringSet(domains)) + d.Set("domains", aws.StringValueSlice(output)) + + tags, err := ListTagsWithContext(ctx, conn, arn) - tags, err := ListTags(conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver DNS Firewall domain list (%s): %w", arn, err) + return diag.Errorf("listing tags for Route53 Resolver Firewall Domain List (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceFirewallDomainListUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallDomainListUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChange("domains") { @@ -165,37 +180,37 @@ func resourceFirewallDomainListUpdate(d *schema.ResourceData, meta interface{}) operation = route53resolver.FirewallDomainUpdateOperationRemove } - _, err := conn.UpdateFirewallDomains(&route53resolver.UpdateFirewallDomainsInput{ + _, err := conn.UpdateFirewallDomainsWithContext(ctx, &route53resolver.UpdateFirewallDomainsInput{ FirewallDomainListId: aws.String(d.Id()), Domains: flex.ExpandStringSet(domains), Operation: aws.String(operation), }) if err != nil { - return fmt.Errorf("error updating Route 53 Resolver DNS Firewall domain list (%s) domains: %w", d.Id(), err) + return diag.Errorf("updating Route53 Resolver Firewall Domain List (%s) domains: %s", d.Id(), err) } - _, err = WaitFirewallDomainListUpdated(conn, d.Id()) - - if err != nil { - return fmt.Errorf("error waiting for Route 53 Resolver DNS Firewall domain list (%s) domains to be updated: %w", d.Id(), err) + if _, err = waitFirewallDomainListUpdated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Domain List (%s) update: %s", d.Id(), err) } } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Route53 Resolver DNS Firewall domain list (%s) tags: %w", d.Get("arn").(string), err) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating Route53 Resolver Firewall Domain List (%s) tags: %s", d.Id(), err) } } - return resourceFirewallDomainListRead(d, meta) + return resourceFirewallDomainListRead(ctx, d, meta) } -func resourceFirewallDomainListDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallDomainListDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - _, err := conn.DeleteFirewallDomainList(&route53resolver.DeleteFirewallDomainListInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Firewall Domain List: %s", d.Id()) + _, err := conn.DeleteFirewallDomainListWithContext(ctx, &route53resolver.DeleteFirewallDomainListInput{ FirewallDomainListId: aws.String(d.Id()), }) @@ -204,14 +219,98 @@ func resourceFirewallDomainListDelete(d *schema.ResourceData, meta interface{}) } if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall domain list (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Firewall Domain List (%s): %s", d.Id(), err) + } + + if _, err = waitFirewallDomainListDeleted(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Domain List (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindFirewallDomainListByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallDomainList, error) { + input := &route53resolver.GetFirewallDomainListInput{ + FirewallDomainListId: aws.String(id), } - _, err = WaitFirewallDomainListDeleted(conn, d.Id()) + output, err := conn.GetFirewallDomainListWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } if err != nil { - return fmt.Errorf("error waiting for Route 53 Resolver DNS Firewall domain list (%s) to be deleted: %w", d.Id(), err) + return nil, err } - return nil + if output == nil || output.FirewallDomainList == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.FirewallDomainList, nil +} + +func statusFirewallDomainList(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindFirewallDomainListByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +const ( + firewallDomainListUpdatedTimeout = 5 * time.Minute + firewallDomainListDeletedTimeout = 5 * time.Minute +) + +func waitFirewallDomainListUpdated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallDomainList, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallDomainListStatusUpdating, route53resolver.FirewallDomainListStatusImporting}, + Target: []string{route53resolver.FirewallDomainListStatusComplete, + route53resolver.FirewallDomainListStatusCompleteImportFailed, + }, + Refresh: statusFirewallDomainList(ctx, conn, id), + Timeout: firewallDomainListUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.FirewallDomainList); ok { + if status := aws.StringValue(output.Status); status == route53resolver.FirewallDomainListStatusCompleteImportFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + } + + return output, err + } + + return nil, err +} + +func waitFirewallDomainListDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallDomainList, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallDomainListStatusDeleting}, + Target: []string{}, + Refresh: statusFirewallDomainList(ctx, conn, id), + Timeout: firewallDomainListDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.FirewallDomainList); ok { + return output, err + } + + return nil, err } diff --git a/internal/service/route53resolver/firewall_domain_list_test.go b/internal/service/route53resolver/firewall_domain_list_test.go index fa957254416..2af5ff8e2ea 100644 --- a/internal/service/route53resolver/firewall_domain_list_test.go +++ b/internal/service/route53resolver/firewall_domain_list_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverFirewallDomainList_basic(t *testing.T) { @@ -29,6 +30,8 @@ func TestAccRoute53ResolverFirewallDomainList_basic(t *testing.T) { Config: testAccFirewallDomainListConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFirewallDomainListExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "domains.#", "0"), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), @@ -46,7 +49,6 @@ func TestAccRoute53ResolverFirewallDomainList_domains(t *testing.T) { var v route53resolver.FirewallDomainList rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_route53_resolver_firewall_domain_list.test" - domainName1 := acctest.RandomFQDomainName() domainName2 := acctest.RandomFQDomainName() @@ -170,16 +172,17 @@ func testAccCheckFirewallDomainListDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindFirewallDomainListByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindFirewallDomainListByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver DNS Firewall domain list still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Firewall Domain List still exists: %s", rs.Primary.ID) } return nil @@ -193,16 +196,18 @@ func testAccCheckFirewallDomainListExists(n string, v *route53resolver.FirewallD } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver DNS Firewall domain list ID is set") + return fmt.Errorf("No Route53 Resolver Firewall Domain List ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindFirewallDomainListByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindFirewallDomainListByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *v = *out + *v = *output return nil } @@ -229,6 +234,7 @@ func testAccFirewallDomainListConfig_tags1(rName, tagKey1, tagValue1 string) str return fmt.Sprintf(` resource "aws_route53_resolver_firewall_domain_list" "test" { name = %[1]q + tags = { %[2]q = %[3]q } @@ -240,6 +246,7 @@ func testAccFirewallDomainListConfig_tags2(rName, tagKey1, tagValue1, tagKey2, t return fmt.Sprintf(` resource "aws_route53_resolver_firewall_domain_list" "test" { name = %[1]q + tags = { %[2]q = %[3]q %[4]q = %[5]q diff --git a/internal/service/route53resolver/firewall_rule.go b/internal/service/route53resolver/firewall_rule.go index 019d4c783a5..cf5511cfd66 100644 --- a/internal/service/route53resolver/firewall_rule.go +++ b/internal/service/route53resolver/firewall_rule.go @@ -1,79 +1,76 @@ package route53resolver import ( + "context" "fmt" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceFirewallRule() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallRuleCreate, - Read: resourceFirewallRuleRead, - Update: resourceFirewallRuleUpdate, - Delete: resourceFirewallRuleDelete, + CreateWithoutTimeout: resourceFirewallRuleCreate, + ReadWithoutTimeout: resourceFirewallRuleRead, + UpdateWithoutTimeout: resourceFirewallRuleUpdate, + DeleteWithoutTimeout: resourceFirewallRuleDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validResolverName, - }, - "action": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice(route53resolver.Action_Values(), false), }, - "block_override_dns_type": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice(route53resolver.BlockOverrideDnsType_Values(), false), }, - "block_override_domain": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(1, 255), }, - "block_override_ttl": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(0, 604800), }, - "block_response": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice(route53resolver.BlockResponse_Values(), false), }, - "firewall_domain_list_id": { Type: schema.TypeString, ForceNew: true, Required: true, ValidateFunc: validation.StringLenBetween(1, 64), }, - "firewall_rule_group_id": { Type: schema.TypeString, ForceNew: true, Required: true, ValidateFunc: validation.StringLenBetween(1, 64), }, - + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validResolverName, + }, "priority": { Type: schema.TypeInt, Required: true, @@ -82,17 +79,19 @@ func ResourceFirewallRule() *schema.Resource { } } -func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - firewallRuleGroupId := d.Get("firewall_rule_group_id").(string) - firewallDomainListId := d.Get("firewall_domain_list_id").(string) + firewallDomainListID := d.Get("firewall_domain_list_id").(string) + firewallRuleGroupID := d.Get("firewall_rule_group_id").(string) + id := FirewallRuleCreateResourceID(firewallRuleGroupID, firewallDomainListID) + name := d.Get("name").(string) input := &route53resolver.CreateFirewallRuleInput{ - CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-firewall-rule-")), - Name: aws.String(d.Get("name").(string)), Action: aws.String(d.Get("action").(string)), - FirewallRuleGroupId: aws.String(firewallRuleGroupId), - FirewallDomainListId: aws.String(firewallDomainListId), + CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-firewall-rule-")), + FirewallRuleGroupId: aws.String(firewallRuleGroupID), + FirewallDomainListId: aws.String(firewallDomainListID), + Name: aws.String(name), Priority: aws.Int64(int64(d.Get("priority").(int))), } @@ -112,59 +111,65 @@ func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error input.BlockResponse = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating Route 53 Resolver DNS Firewall rule: %#v", input) - _, err := conn.CreateFirewallRule(input) + _, err := conn.CreateFirewallRuleWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule: %w", err) + return diag.Errorf("creating Route53 Resolver Firewall Rule (%s): %s", name, err) } - d.SetId(FirewallRuleCreateID(firewallRuleGroupId, firewallDomainListId)) + d.SetId(id) - return resourceFirewallRuleRead(d, meta) + return resourceFirewallRuleRead(ctx, d, meta) } -func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - rule, err := FindFirewallRuleByID(conn, d.Id()) - - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Route53 Resolver DNS Firewall rule (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } + firewallRuleGroupID, firewallDomainListID, err := FirewallRuleParseResourceID(d.Id()) if err != nil { - return fmt.Errorf("error getting Route 53 Resolver DNS Firewall rule (%s): %w", d.Id(), err) + return diag.FromErr(err) } - if rule == nil { - log.Printf("[WARN] Route 53 Resolver DNS Firewall rule (%s) not found, removing from state", d.Id()) + firewallRule, err := FindFirewallRuleByTwoPartKey(ctx, conn, firewallRuleGroupID, firewallDomainListID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Firewall Rule (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - d.Set("name", rule.Name) - d.Set("action", rule.Action) - d.Set("block_override_dns_type", rule.BlockOverrideDnsType) - d.Set("block_override_domain", rule.BlockOverrideDomain) - d.Set("block_override_ttl", rule.BlockOverrideTtl) - d.Set("block_response", rule.BlockResponse) - d.Set("firewall_rule_group_id", rule.FirewallRuleGroupId) - d.Set("firewall_domain_list_id", rule.FirewallDomainListId) - d.Set("priority", rule.Priority) + if err != nil { + return diag.Errorf("reading Route53 Resolver Firewall Rule (%s): %s", d.Id(), err) + } + + d.Set("action", firewallRule.Action) + d.Set("block_override_dns_type", firewallRule.BlockOverrideDnsType) + d.Set("block_override_domain", firewallRule.BlockOverrideDomain) + d.Set("block_override_ttl", firewallRule.BlockOverrideTtl) + d.Set("block_response", firewallRule.BlockResponse) + d.Set("firewall_rule_group_id", firewallRule.FirewallRuleGroupId) + d.Set("firewall_domain_list_id", firewallRule.FirewallDomainListId) + d.Set("name", firewallRule.Name) + d.Set("priority", firewallRule.Priority) return nil } -func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn + firewallRuleGroupID, firewallDomainListID, err := FirewallRuleParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + input := &route53resolver.UpdateFirewallRuleInput{ - Name: aws.String(d.Get("name").(string)), Action: aws.String(d.Get("action").(string)), - FirewallRuleGroupId: aws.String(d.Get("firewall_rule_group_id").(string)), - FirewallDomainListId: aws.String(d.Get("firewall_domain_list_id").(string)), + FirewallDomainListId: aws.String(firewallDomainListID), + FirewallRuleGroupId: aws.String(firewallRuleGroupID), + Name: aws.String(d.Get("name").(string)), Priority: aws.Int64(int64(d.Get("priority").(int))), } @@ -184,21 +189,28 @@ func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error input.BlockResponse = aws.String(v.(string)) } - log.Printf("[DEBUG] Updating Route 53 Resolver DNS Firewall rule: %#v", input) - _, err := conn.UpdateFirewallRule(input) + _, err = conn.UpdateFirewallRuleWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error updating Route 53 Resolver DNS Firewall rule (%s): %w", d.Id(), err) + return diag.Errorf("updating Route53 Resolver Firewall Rule (%s): %s", d.Id(), err) } - return resourceFirewallRuleRead(d, meta) + return resourceFirewallRuleRead(ctx, d, meta) } -func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - _, err := conn.DeleteFirewallRule(&route53resolver.DeleteFirewallRuleInput{ - FirewallRuleGroupId: aws.String(d.Get("firewall_rule_group_id").(string)), - FirewallDomainListId: aws.String(d.Get("firewall_domain_list_id").(string)), + firewallRuleGroupID, firewallDomainListID, err := FirewallRuleParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] Deleting Route53 Resolver Firewall Rule: %s", d.Id()) + _, err = conn.DeleteFirewallRuleWithContext(ctx, &route53resolver.DeleteFirewallRuleInput{ + FirewallDomainListId: aws.String(firewallDomainListID), + FirewallRuleGroupId: aws.String(firewallRuleGroupID), }) if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { @@ -206,8 +218,67 @@ func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error } if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall rule (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Firewall Rule (%s): %s", d.Id(), err) } return nil } + +const firewallRuleIDSeparator = ":" + +func FirewallRuleCreateResourceID(firewallRuleGroupID, firewallDomainListID string) string { + parts := []string{firewallRuleGroupID, firewallDomainListID} + id := strings.Join(parts, firewallRuleIDSeparator) + + return id +} + +func FirewallRuleParseResourceID(id string) (string, string, error) { + parts := strings.SplitN(id, firewallRuleIDSeparator, 2) + + if len(parts) < 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected firewall_rule_group_id%[2]sfirewall_domain_list_id", id, firewallRuleIDSeparator) + } + + return parts[0], parts[1], nil +} + +func FindFirewallRuleByTwoPartKey(ctx context.Context, conn *route53resolver.Route53Resolver, firewallRuleGroupID, firewallDomainListID string) (*route53resolver.FirewallRule, error) { + input := &route53resolver.ListFirewallRulesInput{ + FirewallRuleGroupId: aws.String(firewallRuleGroupID), + } + var output *route53resolver.FirewallRule + + err := conn.ListFirewallRulesPagesWithContext(ctx, input, func(page *route53resolver.ListFirewallRulesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.FirewallRules { + if aws.StringValue(v.FirewallDomainListId) == firewallDomainListID { + output = v + + return false + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, &resource.NotFoundError{LastRequest: input} + } + + return output, nil +} diff --git a/internal/service/route53resolver/firewall_rule_group.go b/internal/service/route53resolver/firewall_rule_group.go index 8056e5e4ca4..e5cb7dc68b4 100644 --- a/internal/service/route53resolver/firewall_rule_group.go +++ b/internal/service/route53resolver/firewall_rule_group.go @@ -1,25 +1,28 @@ package route53resolver import ( - "fmt" + "context" "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceFirewallRuleGroup() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallRuleGroupCreate, - Read: resourceFirewallRuleGroupRead, - Update: resourceFirewallRuleGroupUpdate, - Delete: resourceFirewallRuleGroupDelete, + CreateWithoutTimeout: resourceFirewallRuleGroupCreate, + ReadWithoutTimeout: resourceFirewallRuleGroupRead, + UpdateWithoutTimeout: resourceFirewallRuleGroupUpdate, + DeleteWithoutTimeout: resourceFirewallRuleGroupDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -29,24 +32,20 @@ func ResourceFirewallRuleGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "name": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validResolverName, }, - "owner_id": { Type: schema.TypeString, Computed: true, }, - "share_status": { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -55,51 +54,47 @@ func ResourceFirewallRuleGroup() *schema.Resource { } } -func resourceFirewallRuleGroupCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) input := &route53resolver.CreateFirewallRuleGroupInput{ CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-firewall-rule-group-")), - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), } - if v, ok := d.GetOk("tags"); ok && len(v.(map[string]interface{})) > 0 { + + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Route 53 Resolver DNS Firewall rule group: %#v", input) - output, err := conn.CreateFirewallRuleGroup(input) + output, err := conn.CreateFirewallRuleGroupWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule group: %w", err) + return diag.Errorf("creating Route53 Resolver Firewall Rule Group (%s): %s", name, err) } d.SetId(aws.StringValue(output.FirewallRuleGroup.Id)) - return resourceFirewallRuleGroupRead(d, meta) + return resourceFirewallRuleGroupRead(ctx, d, meta) } -func resourceFirewallRuleGroupRead(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - ruleGroup, err := FindFirewallRuleGroupByID(conn, d.Id()) + ruleGroup, err := FindFirewallRuleGroupByID(ctx, conn, d.Id()) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Route53 Resolver DNS Firewall rule group (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Firewall Rule Group (%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 (%s): %w", d.Id(), err) - } - - if ruleGroup == nil { - log.Printf("[WARN] Route 53 Resolver DNS Firewall rule group (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return diag.Errorf("reading Route53 Resolver Firewall Rule Group (%s): %s", d.Id(), err) } arn := aws.StringValue(ruleGroup.Arn) @@ -108,42 +103,45 @@ func resourceFirewallRuleGroupRead(d *schema.ResourceData, meta interface{}) err d.Set("owner_id", ruleGroup.OwnerId) d.Set("share_status", ruleGroup.ShareStatus) - tags, err := ListTags(conn, arn) + tags, err := ListTagsWithContext(ctx, conn, arn) + if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver DNS Firewall rule group (%s): %w", arn, err) + return diag.Errorf("listing tags for Route53 Resolver Firewall Rule Group (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceFirewallRuleGroupUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Route53 Resolver DNS Firewall rule group (%s) tags: %w", d.Get("arn").(string), err) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating Route53 Resolver Firewall Rule Group (%s) tags: %s", d.Id(), err) } } - return resourceFirewallRuleGroupRead(d, meta) + return resourceFirewallRuleGroupRead(ctx, d, meta) } -func resourceFirewallRuleGroupDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - _, err := conn.DeleteFirewallRuleGroup(&route53resolver.DeleteFirewallRuleGroupInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Firewall Rule Group: %s", d.Id()) + _, err := conn.DeleteFirewallRuleGroupWithContext(ctx, &route53resolver.DeleteFirewallRuleGroupInput{ FirewallRuleGroupId: aws.String(d.Id()), }) @@ -152,8 +150,33 @@ func resourceFirewallRuleGroupDelete(d *schema.ResourceData, meta interface{}) e } if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall rule group (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Firewall Rule Group (%s): %s", d.Id(), err) } return nil } + +func FindFirewallRuleGroupByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallRuleGroup, error) { + input := &route53resolver.GetFirewallRuleGroupInput{ + FirewallRuleGroupId: aws.String(id), + } + + output, err := conn.GetFirewallRuleGroupWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.FirewallRuleGroup == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.FirewallRuleGroup, nil +} diff --git a/internal/service/route53resolver/firewall_rule_group_association.go b/internal/service/route53resolver/firewall_rule_group_association.go index 03b24ace1a7..9b35aaa9805 100644 --- a/internal/service/route53resolver/firewall_rule_group_association.go +++ b/internal/service/route53resolver/firewall_rule_group_association.go @@ -1,26 +1,31 @@ package route53resolver import ( - "fmt" + "context" + "errors" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceFirewallRuleGroupAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallRuleGroupAssociationCreate, - Read: resourceFirewallRuleGroupAssociationRead, - Update: resourceFirewallRuleGroupAssociationUpdate, - Delete: resourceFirewallRuleGroupAssociationDelete, + CreateWithoutTimeout: resourceFirewallRuleGroupAssociationCreate, + ReadWithoutTimeout: resourceFirewallRuleGroupAssociationRead, + UpdateWithoutTimeout: resourceFirewallRuleGroupAssociationUpdate, + DeleteWithoutTimeout: resourceFirewallRuleGroupAssociationDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -30,105 +35,91 @@ func ResourceFirewallRuleGroupAssociation() *schema.Resource { Type: schema.TypeString, Computed: true, }, - - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validResolverName, - }, - "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), }, - + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validResolverName, + }, "priority": { Type: schema.TypeInt, Required: true, }, - + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, } } -func resourceFirewallRuleGroupAssociationCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) 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)), + Name: aws.String(name), Priority: aws.Int64(int64(d.Get("priority").(int))), VpcId: aws.String(d.Get("vpc_id").(string)), - Tags: Tags(tags.IgnoreAWS()), } 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 len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + output, err := conn.AssociateFirewallRuleGroupWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule group association: %w", err) + return diag.Errorf("creating Route53 Resolver Firewall Rule Group Association (%s): %s", name, err) } d.SetId(aws.StringValue(output.FirewallRuleGroupAssociation.Id)) - _, err = WaitFirewallRuleGroupAssociationCreated(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) + if _, err := waitFirewallRuleGroupAssociationCreated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Rule Group Association (%s) create: %s", d.Id(), err) } - return resourceFirewallRuleGroupAssociationRead(d, meta) + return resourceFirewallRuleGroupAssociationRead(ctx, d, meta) } -func resourceFirewallRuleGroupAssociationRead(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - ruleGroupAssociation, err := FindFirewallRuleGroupAssociationByID(conn, d.Id()) + ruleGroupAssociation, err := FindFirewallRuleGroupAssociationByID(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Route53 Resolver DNS Firewall rule group association (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver 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 + return diag.Errorf("reading Route53 Resolver Firewall Rule Group Association (%s): %s", d.Id(), err) } arn := aws.StringValue(ruleGroupAssociation.Arn) @@ -139,26 +130,27 @@ func resourceFirewallRuleGroupAssociationRead(d *schema.ResourceData, meta inter d.Set("priority", ruleGroupAssociation.Priority) d.Set("vpc_id", ruleGroupAssociation.VpcId) - tags, err := ListTags(conn, arn) + tags, err := ListTagsWithContext(ctx, conn, arn) + if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver DNS Firewall rule group association (%s): %w", arn, err) + return diag.Errorf("listing tags for Route53 Resolver Firewall Rule Group Association (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceFirewallRuleGroupAssociationUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupAssociationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChanges("name", "mutation_protection", "priority") { @@ -172,33 +164,33 @@ func resourceFirewallRuleGroupAssociationUpdate(d *schema.ResourceData, meta int input.MutationProtection = aws.String(v.(string)) } - log.Printf("[DEBUG] Updating Route 53 Resolver DNS Firewall rule group association: %#v", input) - _, err := conn.UpdateFirewallRuleGroupAssociation(input) + _, err := conn.UpdateFirewallRuleGroupAssociationWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver DNS Firewall rule group association: %w", err) + return diag.Errorf("updating Route53 Resolver Firewall Rule Group Association (%s): %s", d.Id(), err) } - _, err = WaitFirewallRuleGroupAssociationUpdated(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 _, err := waitFirewallRuleGroupAssociationUpdated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Rule Group Association (%s) update: %s", d.Id(), err) } } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(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) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating Route53 Resolver Firewall Rule Group Association (%s) tags: %s", d.Id(), err) } } - return resourceFirewallRuleGroupAssociationRead(d, meta) + return resourceFirewallRuleGroupAssociationRead(ctx, d, meta) } -func resourceFirewallRuleGroupAssociationDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFirewallRuleGroupAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - _, err := conn.DisassociateFirewallRuleGroup(&route53resolver.DisassociateFirewallRuleGroupInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Firewall Rule Group Association: %s", d.Id()) + _, err := conn.DisassociateFirewallRuleGroupWithContext(ctx, &route53resolver.DisassociateFirewallRuleGroupInput{ FirewallRuleGroupAssociationId: aws.String(d.Id()), }) @@ -207,14 +199,116 @@ func resourceFirewallRuleGroupAssociationDelete(d *schema.ResourceData, meta int } if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver DNS Firewall rule group association (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Firewall Rule Group Association (%s): %s", d.Id(), err) + } + + if _, err := waitFirewallRuleGroupAssociationDeleted(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Firewall Rule Group Association (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindFirewallRuleGroupAssociationByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallRuleGroupAssociation, error) { + input := &route53resolver.GetFirewallRuleGroupAssociationInput{ + FirewallRuleGroupAssociationId: aws.String(id), } - _, err = WaitFirewallRuleGroupAssociationDeleted(conn, d.Id()) + output, err := conn.GetFirewallRuleGroupAssociationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } 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, err } - return nil + if output == nil || output.FirewallRuleGroupAssociation == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.FirewallRuleGroupAssociation, nil +} + +func statusFirewallRuleGroupAssociation(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindFirewallRuleGroupAssociationByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +const ( + firewallRuleGroupAssociationCreatedTimeout = 5 * time.Minute + firewallRuleGroupAssociationUpdatedTimeout = 5 * time.Minute + firewallRuleGroupAssociationDeletedTimeout = 5 * time.Minute +) + +func waitFirewallRuleGroupAssociationCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, + Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, + Refresh: statusFirewallRuleGroupAssociation(ctx, conn, id), + Timeout: firewallRuleGroupAssociationCreatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err + } + + return nil, err +} + +func waitFirewallRuleGroupAssociationUpdated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, + Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, + Refresh: statusFirewallRuleGroupAssociation(ctx, conn, id), + Timeout: firewallRuleGroupAssociationUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err + } + + return nil, err +} + +func waitFirewallRuleGroupAssociationDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.FirewallRuleGroupAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusDeleting}, + Target: []string{}, + Refresh: statusFirewallRuleGroupAssociation(ctx, conn, id), + Timeout: firewallRuleGroupAssociationDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err + } + + return nil, err } diff --git a/internal/service/route53resolver/firewall_rule_group_association_test.go b/internal/service/route53resolver/firewall_rule_group_association_test.go index 6bbe2e7f9e4..2667ff32bc0 100644 --- a/internal/service/route53resolver/firewall_rule_group_association_test.go +++ b/internal/service/route53resolver/firewall_rule_group_association_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverFirewallRuleGroupAssociation_basic(t *testing.T) { @@ -225,16 +226,17 @@ func testAccCheckFirewallRuleGroupAssociationDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindFirewallRuleGroupAssociationByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindFirewallRuleGroupAssociationByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver DNS Firewall rule group association still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Firewall Rule Group Association still exists: %s", rs.Primary.ID) } return nil @@ -248,105 +250,93 @@ func testAccCheckFirewallRuleGroupAssociationExists(n string, v *route53resolver } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver DNS Firewall rule group association ID is set") + return fmt.Errorf("No Route53 Resolver Firewall Rule Group Association ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindFirewallRuleGroupAssociationByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindFirewallRuleGroupAssociationByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *v = *out + *v = *output return nil } } func testAccFirewallRuleGroupAssociationConfig_base(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" -} - + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group" "test" { name = %[1]q } -`, rName) +`, rName)) } func testAccFirewallRuleGroupAssociationConfig_basic(rName string) string { - return fmt.Sprintf(` -%[1]s - + return acctest.ConfigCompose(testAccFirewallRuleGroupAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group_association" "test" { - name = %[2]q + name = %[1]q firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id mutation_protection = "DISABLED" priority = 101 vpc_id = aws_vpc.test.id } -`, testAccFirewallRuleGroupAssociationConfig_base(rName), rName) +`, rName)) } func testAccFirewallRuleGroupAssociationConfig_mutationProtection(rName, mutationProtection string) string { - return fmt.Sprintf(` -%[1]s - + return acctest.ConfigCompose(testAccFirewallRuleGroupAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group_association" "test" { - name = %[2]q + name = %[1]q firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id - mutation_protection = %[3]q + mutation_protection = %[2]q priority = 101 vpc_id = aws_vpc.test.id } -`, testAccFirewallRuleGroupAssociationConfig_base(rName), rName, mutationProtection) +`, rName, mutationProtection)) } func testAccFirewallRuleGroupAssociationConfig_priority(rName string, priority int) string { - return fmt.Sprintf(` -%[1]s - + return acctest.ConfigCompose(testAccFirewallRuleGroupAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group_association" "test" { - name = %[2]q + name = %[1]q firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.test.id - priority = %[3]d + priority = %[2]d vpc_id = aws_vpc.test.id } -`, testAccFirewallRuleGroupAssociationConfig_base(rName), rName, priority) +`, rName, priority)) } func testAccFirewallRuleGroupAssociationConfig_tags1(rName, tagKey1, tagValue1 string) string { - return fmt.Sprintf(` -%[1]s - + return acctest.ConfigCompose(testAccFirewallRuleGroupAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group_association" "test" { - name = %[2]q + name = %[1]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 + %[2]q = %[3]q } } -`, testAccFirewallRuleGroupAssociationConfig_base(rName), rName, tagKey1, tagValue1) +`, rName, tagKey1, tagValue1)) } func testAccFirewallRuleGroupAssociationConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return fmt.Sprintf(` -%[1]s - + return acctest.ConfigCompose(testAccFirewallRuleGroupAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group_association" "test" { - name = %[2]q + name = %[1]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 + %[2]q = %[3]q + %[4]q = %[5]q } } -`, testAccFirewallRuleGroupAssociationConfig_base(rName), rName, tagKey1, tagValue1, tagKey2, tagValue2) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } diff --git a/internal/service/route53resolver/firewall_rule_group_test.go b/internal/service/route53resolver/firewall_rule_group_test.go index 195be06b6f7..7c90c9fbebd 100644 --- a/internal/service/route53resolver/firewall_rule_group_test.go +++ b/internal/service/route53resolver/firewall_rule_group_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverFirewallRuleGroup_basic(t *testing.T) { @@ -133,16 +134,17 @@ func testAccCheckFirewallRuleGroupDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindFirewallRuleGroupByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindFirewallRuleGroupByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver DNS Firewall rule group still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Firewall Rule Group still exists: %s", rs.Primary.ID) } return nil @@ -156,16 +158,18 @@ func testAccCheckFirewallRuleGroupExists(n string, v *route53resolver.FirewallRu } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver DNS Firewall rule group ID is set") + return fmt.Errorf("No Route53 Resolver Firewall Rule Group ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindFirewallRuleGroupByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindFirewallRuleGroupByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *v = *out + *v = *output return nil } @@ -183,6 +187,7 @@ func testAccFirewallRuleGroupConfig_tags1(rName, tagKey1, tagValue1 string) stri return fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group" "test" { name = %[1]q + tags = { %[2]q = %[3]q } @@ -194,6 +199,7 @@ func testAccFirewallRuleGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, ta return fmt.Sprintf(` resource "aws_route53_resolver_firewall_rule_group" "test" { name = %[1]q + tags = { %[2]q = %[3]q %[4]q = %[5]q diff --git a/internal/service/route53resolver/firewall_rule_test.go b/internal/service/route53resolver/firewall_rule_test.go index ca6b6d3de56..63990f2751c 100644 --- a/internal/service/route53resolver/firewall_rule_test.go +++ b/internal/service/route53resolver/firewall_rule_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverFirewallRule_basic(t *testing.T) { @@ -137,16 +138,23 @@ func testAccCheckFirewallRuleDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindFirewallRuleByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + firewallRuleGroupID, firewallDomainListID, err := tfroute53resolver.FirewallRuleParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfroute53resolver.FindFirewallRuleByTwoPartKey(context.Background(), conn, firewallRuleGroupID, firewallDomainListID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver DNS Firewall rule still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Firewall Rule still exists: %s", rs.Primary.ID) } return nil @@ -160,16 +168,24 @@ func testAccCheckFirewallRuleExists(n string, v *route53resolver.FirewallRule) r } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver DNS Firewall rule ID is set") + return fmt.Errorf("No Route53 Resolver Firewall Rule ID is set") + } + + firewallRuleGroupID, firewallDomainListID, err := tfroute53resolver.FirewallRuleParseResourceID(rs.Primary.ID) + + if err != nil { + return err } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindFirewallRuleByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindFirewallRuleByTwoPartKey(context.Background(), conn, firewallRuleGroupID, firewallDomainListID) + if err != nil { return err } - *v = *out + *v = *output return nil } diff --git a/internal/service/route53resolver/flex.go b/internal/service/route53resolver/flex.go deleted file mode 100644 index 1627299f88a..00000000000 --- a/internal/service/route53resolver/flex.go +++ /dev/null @@ -1,106 +0,0 @@ -package route53resolver - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func expandEndpointIPAddressUpdate(vIpAddress interface{}) *route53resolver.IpAddressUpdate { - ipAddressUpdate := &route53resolver.IpAddressUpdate{} - - mIpAddress := vIpAddress.(map[string]interface{}) - - if vSubnetId, ok := mIpAddress["subnet_id"].(string); ok && vSubnetId != "" { - ipAddressUpdate.SubnetId = aws.String(vSubnetId) - } - if vIp, ok := mIpAddress["ip"].(string); ok && vIp != "" { - ipAddressUpdate.Ip = aws.String(vIp) - } - if vIpId, ok := mIpAddress["ip_id"].(string); ok && vIpId != "" { - ipAddressUpdate.IpId = aws.String(vIpId) - } - - return ipAddressUpdate -} - -func expandEndpointIPAddresses(vIpAddresses *schema.Set) []*route53resolver.IpAddressRequest { - ipAddressRequests := []*route53resolver.IpAddressRequest{} - - for _, vIpAddress := range vIpAddresses.List() { - ipAddressRequest := &route53resolver.IpAddressRequest{} - - mIpAddress := vIpAddress.(map[string]interface{}) - - if vSubnetId, ok := mIpAddress["subnet_id"].(string); ok && vSubnetId != "" { - ipAddressRequest.SubnetId = aws.String(vSubnetId) - } - if vIp, ok := mIpAddress["ip"].(string); ok && vIp != "" { - ipAddressRequest.Ip = aws.String(vIp) - } - - ipAddressRequests = append(ipAddressRequests, ipAddressRequest) - } - - return ipAddressRequests -} - -func expandRuleTargetIPs(vTargetIps *schema.Set) []*route53resolver.TargetAddress { - targetAddresses := []*route53resolver.TargetAddress{} - - for _, vTargetIp := range vTargetIps.List() { - targetAddress := &route53resolver.TargetAddress{} - - mTargetIp := vTargetIp.(map[string]interface{}) - - if vIp, ok := mTargetIp["ip"].(string); ok && vIp != "" { - targetAddress.Ip = aws.String(vIp) - } - if vPort, ok := mTargetIp["port"].(int); ok { - targetAddress.Port = aws.Int64(int64(vPort)) - } - - targetAddresses = append(targetAddresses, targetAddress) - } - - return targetAddresses -} - -func flattenEndpointIPAddresses(ipAddresses []*route53resolver.IpAddressResponse) []interface{} { - if ipAddresses == nil { - return []interface{}{} - } - - vIpAddresses := []interface{}{} - - for _, ipAddress := range ipAddresses { - mIpAddress := map[string]interface{}{ - "subnet_id": aws.StringValue(ipAddress.SubnetId), - "ip": aws.StringValue(ipAddress.Ip), - "ip_id": aws.StringValue(ipAddress.IpId), - } - - vIpAddresses = append(vIpAddresses, mIpAddress) - } - - return vIpAddresses -} - -func flattenRuleTargetIPs(targetAddresses []*route53resolver.TargetAddress) []interface{} { - if targetAddresses == nil { - return []interface{}{} - } - - vTargetIps := []interface{}{} - - for _, targetAddress := range targetAddresses { - mTargetIp := map[string]interface{}{ - "ip": aws.StringValue(targetAddress.Ip), - "port": int(aws.Int64Value(targetAddress.Port)), - } - - vTargetIps = append(vTargetIps, mTargetIp) - } - - return vTargetIps -} diff --git a/internal/service/route53resolver/id.go b/internal/service/route53resolver/id.go deleted file mode 100644 index 41da738ab64..00000000000 --- a/internal/service/route53resolver/id.go +++ /dev/null @@ -1,25 +0,0 @@ -package route53resolver - -import ( - "fmt" - "strings" -) - -const ruleIdSeparator = ":" - -func FirewallRuleCreateID(firewallRuleGroupId, firewallDomainListId string) string { - parts := []string{firewallRuleGroupId, firewallDomainListId} - id := strings.Join(parts, ruleIdSeparator) - - return id -} - -func FirewallRuleParseID(id string) (string, string, error) { - parts := strings.SplitN(id, ruleIdSeparator, 2) - - if len(parts) < 2 || parts[0] == "" || parts[1] == "" { - return "", "", fmt.Errorf("unexpected format of ID (%s), expected firewall_rule_group_id%sfirewall_domain_list_id", id, ruleIdSeparator) - } - - return parts[0], parts[1], nil -} diff --git a/internal/service/route53resolver/query_log_config.go b/internal/service/route53resolver/query_log_config.go index 0625b61eac6..49bdcbbfe71 100644 --- a/internal/service/route53resolver/query_log_config.go +++ b/internal/service/route53resolver/query_log_config.go @@ -1,25 +1,29 @@ package route53resolver import ( - "fmt" + "context" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceQueryLogConfig() *schema.Resource { return &schema.Resource{ - Create: resourceQueryLogConfigCreate, - Read: resourceQueryLogConfigRead, - Update: resourceQueryLogConfigUpdate, - Delete: resourceQueryLogConfigDelete, + CreateWithoutTimeout: resourceQueryLogConfigCreate, + ReadWithoutTimeout: resourceQueryLogConfigRead, + UpdateWithoutTimeout: resourceQueryLogConfigUpdate, + DeleteWithoutTimeout: resourceQueryLogConfigDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -29,31 +33,26 @@ func ResourceQueryLogConfig() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "destination_arn": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: verify.ValidARN, }, - "name": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validResolverName, }, - "owner_id": { Type: schema.TypeString, Computed: true, }, - "share_status": { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -62,59 +61,52 @@ func ResourceQueryLogConfig() *schema.Resource { } } -func resourceQueryLogConfigCreate(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) input := &route53resolver.CreateResolverQueryLogConfigInput{ CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-query-log-config-")), DestinationArn: aws.String(d.Get("destination_arn").(string)), - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), } - if v, ok := d.GetOk("tags"); ok && len(v.(map[string]interface{})) > 0 { + + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Route53 Resolver Query Log Config: %s", input) - output, err := conn.CreateResolverQueryLogConfig(input) + output, err := conn.CreateResolverQueryLogConfigWithContext(ctx, input) if err != nil { - return fmt.Errorf("error creating Route53 Resolver Query Log Config: %w", err) + return diag.Errorf("creating Route53 Resolver Query Log Config (%s): %s", name, err) } d.SetId(aws.StringValue(output.ResolverQueryLogConfig.Id)) - _, err = WaitQueryLogConfigCreated(conn, d.Id()) - - if err != nil { - return fmt.Errorf("error waiting for Route53 Resolver Query Log Config (%s) to become available: %w", d.Id(), err) + if _, err := waitQueryLogConfigCreated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Query Log Config (%s) create: %s", d.Id(), err) } - return resourceQueryLogConfigRead(d, meta) + return resourceQueryLogConfigRead(ctx, d, meta) } -func resourceQueryLogConfigRead(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - queryLogConfig, err := FindResolverQueryLogConfigByID(conn, d.Id()) + queryLogConfig, err := FindResolverQueryLogConfigByID(ctx, conn, d.Id()) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Route53 Resolver Query Log Config (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading Route53 Resolver Query Log Config (%s): %w", d.Id(), err) - } - - if queryLogConfig == nil { - log.Printf("[WARN] Route53 Resolver Query Log Config (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return diag.Errorf("reading Route53 Resolver Query Log Config (%s): %s", d.Id(), err) } arn := aws.StringValue(queryLogConfig.Arn) @@ -124,43 +116,45 @@ func resourceQueryLogConfigRead(d *schema.ResourceData, meta interface{}) error d.Set("owner_id", queryLogConfig.OwnerId) d.Set("share_status", queryLogConfig.ShareStatus) - tags, err := ListTags(conn, arn) + tags, err := ListTagsWithContext(ctx, conn, arn) + if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver Query Log Config (%s): %w", arn, err) + return diag.Errorf("listing tags for Route53 Resolver Query Log Config (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceQueryLogConfigUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Route53 Resolver Query Log Config (%s) tags: %s", d.Get("arn").(string), err) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Route53 Resolver Query Log Config (%s) tags: %s", d.Id(), err) } } - return resourceQueryLogConfigRead(d, meta) + return resourceQueryLogConfigRead(ctx, d, meta) } -func resourceQueryLogConfigDelete(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route53 Resolver Query Log Config (%s)", d.Id()) - _, err := conn.DeleteResolverQueryLogConfig(&route53resolver.DeleteResolverQueryLogConfigInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Query Log Config: %s", d.Id()) + _, err := conn.DeleteResolverQueryLogConfigWithContext(ctx, &route53resolver.DeleteResolverQueryLogConfigInput{ ResolverQueryLogConfigId: aws.String(d.Id()), }) @@ -169,14 +163,92 @@ func resourceQueryLogConfigDelete(d *schema.ResourceData, meta interface{}) erro } if err != nil { - return fmt.Errorf("error deleting Route53 Resolver Query Log Config (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Query Log Config (%s): %s", d.Id(), err) + } + + if _, err := waitQueryLogConfigDeleted(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Query Log Config (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindResolverQueryLogConfigByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfig, error) { + input := &route53resolver.GetResolverQueryLogConfigInput{ + ResolverQueryLogConfigId: aws.String(id), } - _, err = WaitQueryLogConfigDeleted(conn, d.Id()) + output, err := conn.GetResolverQueryLogConfigWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } if err != nil { - return fmt.Errorf("error waiting for Route53 Resolver Query Log Config (%s) to be deleted: %w", d.Id(), err) + return nil, err } - return nil + if output == nil || output.ResolverQueryLogConfig == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ResolverQueryLogConfig, nil +} + +func statusQueryLogConfig(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindResolverQueryLogConfigByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +const ( + queryLogConfigCreatedTimeout = 5 * time.Minute + queryLogConfigDeletedTimeout = 5 * time.Minute +) + +func waitQueryLogConfigCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverQueryLogConfigStatusCreating}, + Target: []string{route53resolver.ResolverQueryLogConfigStatusCreated}, + Refresh: statusQueryLogConfig(ctx, conn, id), + Timeout: queryLogConfigCreatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverQueryLogConfig); ok { + return output, err + } + + return nil, err +} + +func waitQueryLogConfigDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfig, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverQueryLogConfigStatusDeleting}, + Target: []string{}, + Refresh: statusQueryLogConfig(ctx, conn, id), + Timeout: queryLogConfigDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverQueryLogConfig); ok { + return output, err + } + + return nil, err } diff --git a/internal/service/route53resolver/query_log_config_association.go b/internal/service/route53resolver/query_log_config_association.go index 745aea7b250..3f85cf3f3b9 100644 --- a/internal/service/route53resolver/query_log_config_association.go +++ b/internal/service/route53resolver/query_log_config_association.go @@ -1,21 +1,27 @@ package route53resolver import ( + "context" "fmt" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceQueryLogConfigAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceQueryLogConfigAssociationCreate, - Read: resourceQueryLogConfigAssociationRead, - Delete: resourceQueryLogConfigAssociationDelete, + CreateWithoutTimeout: resourceQueryLogConfigAssociationCreate, + ReadWithoutTimeout: resourceQueryLogConfigAssociationRead, + DeleteWithoutTimeout: resourceQueryLogConfigAssociationDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -26,7 +32,6 @@ func ResourceQueryLogConfigAssociation() *schema.Resource { Required: true, ForceNew: true, }, - "resource_id": { Type: schema.TypeString, Required: true, @@ -36,7 +41,7 @@ func ResourceQueryLogConfigAssociation() *schema.Resource { } } -func resourceQueryLogConfigAssociationCreate(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn input := &route53resolver.AssociateResolverQueryLogConfigInput{ @@ -44,43 +49,34 @@ func resourceQueryLogConfigAssociationCreate(d *schema.ResourceData, meta interf ResourceId: aws.String(d.Get("resource_id").(string)), } - log.Printf("[DEBUG] Creating Route53 Resolver Query Log Config Association: %s", input) - output, err := conn.AssociateResolverQueryLogConfig(input) + output, err := conn.AssociateResolverQueryLogConfigWithContext(ctx, input) if err != nil { - return fmt.Errorf("error creating Route53 Resolver Query Log Config Association: %w", err) + return diag.Errorf("creating Route53 Resolver Query Log Config Association: %s", err) } d.SetId(aws.StringValue(output.ResolverQueryLogConfigAssociation.Id)) - _, err = WaitQueryLogConfigAssociationCreated(conn, d.Id()) - - if err != nil { - return fmt.Errorf("error waiting for Route53 Resolver Query Log Config Association (%s) to become available: %w", d.Id(), err) + if _, err := waitQueryLogConfigAssociationCreated(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Query Log Config Association (%s) create: %s", d.Id(), err) } - return resourceQueryLogConfigAssociationRead(d, meta) + return resourceQueryLogConfigAssociationRead(ctx, d, meta) } -func resourceQueryLogConfigAssociationRead(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - queryLogConfigAssociation, err := FindResolverQueryLogConfigAssociationByID(conn, d.Id()) + queryLogConfigAssociation, err := FindResolverQueryLogConfigAssociationByID(ctx, conn, d.Id()) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Route53 Resolver Query Log Config Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading Route53 Resolver Query Log Config Association (%s): %w", d.Id(), err) - } - - if queryLogConfigAssociation == nil { - log.Printf("[WARN] Route53 Resolver Query Log Config Association (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return diag.Errorf("reading Route53 Resolver Query Log Config Association (%s): %s", d.Id(), err) } d.Set("resolver_query_log_config_id", queryLogConfigAssociation.ResolverQueryLogConfigId) @@ -89,11 +85,11 @@ func resourceQueryLogConfigAssociationRead(d *schema.ResourceData, meta interfac return nil } -func resourceQueryLogConfigAssociationDelete(d *schema.ResourceData, meta interface{}) error { +func resourceQueryLogConfigAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route53 Resolver Query Log Config Association (%s)", d.Id()) - _, err := conn.DisassociateResolverQueryLogConfig(&route53resolver.DisassociateResolverQueryLogConfigInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Query Log Config Association: %s", d.Id()) + _, err := conn.DisassociateResolverQueryLogConfigWithContext(ctx, &route53resolver.DisassociateResolverQueryLogConfigInput{ ResolverQueryLogConfigId: aws.String(d.Get("resolver_query_log_config_id").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), }) @@ -103,14 +99,100 @@ func resourceQueryLogConfigAssociationDelete(d *schema.ResourceData, meta interf } if err != nil { - return fmt.Errorf("error deleting Route53 Resolver Query Log Config Association (%s): %w", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Query Log Config Association (%s): %s", d.Id(), err) + } + + if _, err := waitQueryLogConfigAssociationDeleted(ctx, conn, d.Id()); err != nil { + return diag.Errorf("waiting for Route53 Resolver Query Log Config Association (%s) delete: %s", d.Id(), err) } - _, err = WaitQueryLogConfigAssociationDeleted(conn, d.Id()) + return nil +} + +func FindResolverQueryLogConfigAssociationByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { + input := &route53resolver.GetResolverQueryLogConfigAssociationInput{ + ResolverQueryLogConfigAssociationId: aws.String(id), + } + + output, err := conn.GetResolverQueryLogConfigAssociationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } if err != nil { - return fmt.Errorf("error waiting for Route53 Resolver Query Log Config Association (%s) to be deleted: %w", d.Id(), err) + return nil, err } - return nil + if output == nil || output.ResolverQueryLogConfigAssociation == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ResolverQueryLogConfigAssociation, nil +} + +func statusQueryLogConfigAssociation(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindResolverQueryLogConfigAssociationByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +const ( + queryLogConfigAssociationCreatedTimeout = 5 * time.Minute + queryLogConfigAssociationDeletedTimeout = 5 * time.Minute +) + +func waitQueryLogConfigAssociationCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverQueryLogConfigAssociationStatusCreating}, + Target: []string{route53resolver.ResolverQueryLogConfigAssociationStatusActive}, + Refresh: statusQueryLogConfigAssociation(ctx, conn, id), + Timeout: queryLogConfigAssociationCreatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverQueryLogConfigAssociation); ok { + if status := aws.StringValue(output.Status); status == route53resolver.ResolverQueryLogConfigAssociationStatusFailed { + tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(output.Error), aws.StringValue(output.ErrorMessage))) + } + + return output, err + } + + return nil, err +} + +func waitQueryLogConfigAssociationDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverQueryLogConfigAssociationStatusDeleting}, + Target: []string{}, + Refresh: statusQueryLogConfigAssociation(ctx, conn, id), + Timeout: queryLogConfigAssociationDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverQueryLogConfigAssociation); ok { + if status := aws.StringValue(output.Status); status == route53resolver.ResolverQueryLogConfigAssociationStatusFailed { + tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(output.Error), aws.StringValue(output.ErrorMessage))) + } + + return output, err + } + + return nil, err } diff --git a/internal/service/route53resolver/query_log_config_association_test.go b/internal/service/route53resolver/query_log_config_association_test.go index 812f230f618..1b81d31f0fa 100644 --- a/internal/service/route53resolver/query_log_config_association_test.go +++ b/internal/service/route53resolver/query_log_config_association_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverQueryLogConfigAssociation_basic(t *testing.T) { @@ -75,16 +76,17 @@ func testAccCheckQueryLogConfigAssociationDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindResolverQueryLogConfigAssociationByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindResolverQueryLogConfigAssociationByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver Query Log Config Association still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Query Log Config Association still exists: %s", rs.Primary.ID) } return nil @@ -98,16 +100,18 @@ func testAccCheckQueryLogConfigAssociationExists(n string, v *route53resolver.Re } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver Query Log Config Association ID is set") + return fmt.Errorf("No Route53 Resolver Query Log Config Association ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindResolverQueryLogConfigAssociationByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindResolverQueryLogConfigAssociationByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *v = *out + *v = *output return nil } diff --git a/internal/service/route53resolver/query_log_config_test.go b/internal/service/route53resolver/query_log_config_test.go index 3e8b6a7d538..30656c95589 100644 --- a/internal/service/route53resolver/query_log_config_test.go +++ b/internal/service/route53resolver/query_log_config_test.go @@ -1,17 +1,18 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverQueryLogConfig_basic(t *testing.T) { @@ -135,16 +136,17 @@ func testAccCheckQueryLogConfigDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := tfroute53resolver.FindResolverQueryLogConfigByID(conn, rs.Primary.ID) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindResolverQueryLogConfigByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver Query Log Config still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Query Log Config %s still exists", rs.Primary.ID) } return nil @@ -158,16 +160,18 @@ func testAccCheckQueryLogConfigExists(n string, v *route53resolver.ResolverQuery } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver Query Log Config ID is set") + return fmt.Errorf("No Route53 Resolver Query Log Config ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - out, err := tfroute53resolver.FindResolverQueryLogConfigByID(conn, rs.Primary.ID) + + output, err := tfroute53resolver.FindResolverQueryLogConfigByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *v = *out + *v = *output return nil } diff --git a/internal/service/route53resolver/rule.go b/internal/service/route53resolver/rule.go index 107d81d4d3b..ebaeaaef903 100644 --- a/internal/service/route53resolver/rule.go +++ b/internal/service/route53resolver/rule.go @@ -1,9 +1,8 @@ package route53resolver import ( - "bytes" "context" - "fmt" + "errors" "log" "strings" "time" @@ -11,47 +10,39 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -const ( - RuleStatusDeleted = "DELETED" -) - -const ( - ruleCreatedDefaultTimeout = 10 * time.Minute - ruleUpdatedDefaultTimeout = 10 * time.Minute - ruleDeletedDefaultTimeout = 10 * time.Minute -) - func ResourceRule() *schema.Resource { return &schema.Resource{ - Create: resourceRuleCreate, - Read: resourceRuleRead, - Update: resourceRuleUpdate, - Delete: resourceRuleDelete, - CustomizeDiff: customdiff.Sequence( - resourceRuleCustomizeDiff, - verify.SetTagsDiff, - ), + CreateWithoutTimeout: resourceRuleCreate, + ReadWithoutTimeout: resourceRuleRead, + UpdateWithoutTimeout: resourceRuleUpdate, + DeleteWithoutTimeout: resourceRuleDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(ruleCreatedDefaultTimeout), - Update: schema.DefaultTimeout(ruleUpdatedDefaultTimeout), - Delete: schema.DefaultTimeout(ruleDeletedDefaultTimeout), + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "domain_name": { Type: schema.TypeString, Required: true, @@ -59,29 +50,31 @@ func ResourceRule() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 256), StateFunc: trimTrailingPeriod, }, - - "rule_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - route53resolver.RuleTypeOptionForward, - route53resolver.RuleTypeOptionSystem, - route53resolver.RuleTypeOptionRecursive, - }, false), - }, - "name": { Type: schema.TypeString, Optional: true, ValidateFunc: validResolverName, }, - + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, "resolver_endpoint_id": { Type: schema.TypeString, Optional: true, }, - + "rule_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(route53resolver.RuleTypeOption_Values(), false), + }, + "share_status": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "target_ip": { Type: schema.TypeSet, Optional: true, @@ -100,88 +93,77 @@ func ResourceRule() *schema.Resource { }, }, }, - Set: ruleHashTargetIP, - }, - - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), - - "arn": { - Type: schema.TypeString, - Computed: true, - }, - - "owner_id": { - Type: schema.TypeString, - Computed: true, - }, - - "share_status": { - Type: schema.TypeString, - Computed: true, }, }, + + CustomizeDiff: customdiff.Sequence( + resourceRuleCustomizeDiff, + verify.SetTagsDiff, + ), } } -func resourceRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - req := &route53resolver.CreateResolverRuleInput{ + input := &route53resolver.CreateResolverRuleInput{ CreatorRequestId: aws.String(resource.PrefixedUniqueId("tf-r53-resolver-rule-")), DomainName: aws.String(d.Get("domain_name").(string)), RuleType: aws.String(d.Get("rule_type").(string)), } + if v, ok := d.GetOk("name"); ok { - req.Name = aws.String(v.(string)) + input.Name = aws.String(v.(string)) } + if v, ok := d.GetOk("resolver_endpoint_id"); ok { - req.ResolverEndpointId = aws.String(v.(string)) + input.ResolverEndpointId = aws.String(v.(string)) } + if v, ok := d.GetOk("target_ip"); ok { - req.TargetIps = expandRuleTargetIPs(v.(*schema.Set)) + input.TargetIps = expandRuleTargetIPs(v.(*schema.Set)) } - if v, ok := d.GetOk("tags"); ok && len(v.(map[string]interface{})) > 0 { - req.Tags = Tags(tags.IgnoreAWS()) + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Route 53 Resolver rule: %s", req) - resp, err := conn.CreateResolverRule(req) + output, err := conn.CreateResolverRuleWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver rule: %s", err) + return diag.Errorf("creating Route53 Resolver Rule: %s", err) } - d.SetId(aws.StringValue(resp.ResolverRule.Id)) + d.SetId(aws.StringValue(output.ResolverRule.Id)) - err = RuleWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutCreate), - []string{}, // Should go straight to COMPLETE - []string{route53resolver.ResolverRuleStatusComplete}) - if err != nil { - return err + if _, err := waitRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Rule (%s) create: %s", d.Id(), err) } - return resourceRuleRead(d, meta) + return resourceRuleRead(ctx, d, meta) } -func resourceRuleRead(d *schema.ResourceData, meta interface{}) error { +func resourceRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - ruleRaw, state, err := ruleRefresh(conn, d.Id())() - if err != nil { - return fmt.Errorf("error getting Route53 Resolver rule (%s): %s", d.Id(), err) - } - if state == RuleStatusDeleted { - log.Printf("[WARN] Route53 Resolver rule (%s) not found, removing from state", d.Id()) + rule, err := FindResolverRuleByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Rule (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - rule := ruleRaw.(*route53resolver.ResolverRule) - d.Set("arn", rule.Arn) + if err != nil { + return diag.Errorf("reading Route53 Resolver Rule (%s): %s", d.Id(), err) + } + + arn := aws.StringValue(rule.Arn) + d.Set("arn", arn) // To be consistent with other AWS services that do not accept a trailing period, // we remove the suffix from the Domain Name returned from the API d.Set("domain_name", trimTrailingPeriod(aws.StringValue(rule.DomainName))) @@ -190,89 +172,94 @@ func resourceRuleRead(d *schema.ResourceData, meta interface{}) error { d.Set("resolver_endpoint_id", rule.ResolverEndpointId) d.Set("rule_type", rule.RuleType) d.Set("share_status", rule.ShareStatus) - if err := d.Set("target_ip", schema.NewSet(ruleHashTargetIP, flattenRuleTargetIPs(rule.TargetIps))); err != nil { - return err + if err := d.Set("target_ip", flattenRuleTargetIPs(rule.TargetIps)); err != nil { + return diag.Errorf("setting target_ip: %s", err) } - tags, err := ListTags(conn, d.Get("arn").(string)) + tags, err := ListTagsWithContext(ctx, conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Route53 Resolver rule (%s): %s", d.Get("arn").(string), err) + return diag.Errorf("listing tags for Route53 Resolver Rule (%s): %s", 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) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return diag.Errorf("setting tags_all: %s", err) } return nil } -func resourceRuleUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn if d.HasChanges("name", "resolver_endpoint_id", "target_ip") { - req := &route53resolver.UpdateResolverRuleInput{ - ResolverRuleId: aws.String(d.Id()), + input := &route53resolver.UpdateResolverRuleInput{ Config: &route53resolver.ResolverRuleConfig{}, + ResolverRuleId: aws.String(d.Id()), } + if v, ok := d.GetOk("name"); ok { - req.Config.Name = aws.String(v.(string)) + input.Config.Name = aws.String(v.(string)) } + if v, ok := d.GetOk("resolver_endpoint_id"); ok { - req.Config.ResolverEndpointId = aws.String(v.(string)) + input.Config.ResolverEndpointId = aws.String(v.(string)) } + if v, ok := d.GetOk("target_ip"); ok { - req.Config.TargetIps = expandRuleTargetIPs(v.(*schema.Set)) + input.Config.TargetIps = expandRuleTargetIPs(v.(*schema.Set)) } - log.Printf("[DEBUG] Updating Route53 Resolver rule: %#v", req) - _, err := conn.UpdateResolverRule(req) + _, err := conn.UpdateResolverRuleWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error updating Route 53 Resolver rule (%s): %s", d.Id(), err) + return diag.Errorf("updating Route53 Resolver Rule (%s): %s", d.Id(), err) } - err = RuleWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutUpdate), - []string{route53resolver.ResolverRuleStatusUpdating}, - []string{route53resolver.ResolverRuleStatusComplete}) - if err != nil { - return err + if _, err := waitRuleUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Rule (%s) update: %s", d.Id(), err) } } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Route53 Resolver rule (%s) tags: %s", d.Get("arn").(string), err) + + if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating Route53 Resolver Rule (%s) tags: %s", d.Id(), err) } } - return resourceRuleRead(d, meta) + return resourceRuleRead(ctx, d, meta) } -func resourceRuleDelete(d *schema.ResourceData, meta interface{}) error { +func resourceRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route53 Resolver rule: %s", d.Id()) - _, err := conn.DeleteResolverRule(&route53resolver.DeleteResolverRuleInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Rule: %s", d.Id()) + _, err := conn.DeleteResolverRuleWithContext(ctx, &route53resolver.DeleteResolverRuleInput{ ResolverRuleId: aws.String(d.Id()), }) + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { return nil } + if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver rule (%s): %s", d.Id(), err) + return diag.Errorf("deleting Route53 Resolver Rule (%s): %s", d.Id(), err) + } + + if _, err := waitRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Rule (%s) delete: %s", d.Id(), err) } - return RuleWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutDelete), - []string{route53resolver.ResolverRuleStatusDeleting}, - []string{RuleStatusDeleted}) + return nil } func resourceRuleCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { @@ -289,47 +276,147 @@ func resourceRuleCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v i return nil } -func ruleRefresh(conn *route53resolver.Route53Resolver, ruleId string) resource.StateRefreshFunc { +func FindResolverRuleByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverRule, error) { + input := &route53resolver.GetResolverRuleInput{ + ResolverRuleId: aws.String(id), + } + + output, err := conn.GetResolverRuleWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.ResolverRule == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ResolverRule, nil +} + +func statusRule(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.GetResolverRule(&route53resolver.GetResolverRuleInput{ - ResolverRuleId: aws.String(ruleId), - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return "", RuleStatusDeleted, nil + output, err := FindResolverRuleByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil } + if err != nil { return nil, "", err } - if statusMessage := aws.StringValue(resp.ResolverRule.StatusMessage); statusMessage != "" { - log.Printf("[INFO] Route 53 Resolver rule (%s) status message: %s", ruleId, statusMessage) + return output, aws.StringValue(output.Status), nil + } +} + +func waitRuleCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverRule, error) { + stateConf := &resource.StateChangeConf{ + Target: []string{route53resolver.ResolverRuleStatusComplete}, + Refresh: statusRule(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverRule); ok { + if status := aws.StringValue(output.Status); status == route53resolver.ResolverRuleStatusFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) } - return resp.ResolverRule, aws.StringValue(resp.ResolverRule.Status), nil + return output, err } + + return nil, err } -func RuleWaitUntilTargetState(conn *route53resolver.Route53Resolver, ruleId string, timeout time.Duration, pending, target []string) error { +func waitRuleUpdated(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverRule, error) { stateConf := &resource.StateChangeConf{ - Pending: pending, - Target: target, - Refresh: ruleRefresh(conn, ruleId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, + Pending: []string{route53resolver.ResolverRuleStatusUpdating}, + Target: []string{route53resolver.ResolverRuleStatusComplete}, + Refresh: statusRule(ctx, conn, id), + Timeout: timeout, } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for Route53 Resolver rule (%s) to reach target state: %s", ruleId, err) + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverRule); ok { + if status := aws.StringValue(output.Status); status == route53resolver.ResolverRuleStatusFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + } + + return output, err } - return nil + return nil, err +} + +func waitRuleDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverRuleStatusDeleting}, + Target: []string{}, + Refresh: statusRule(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverRule); ok { + if status := aws.StringValue(output.Status); status == route53resolver.ResolverRuleStatusFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + } + + return output, err + } + + return nil, err } -func ruleHashTargetIP(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-%d-", m["ip"].(string), m["port"].(int))) - return create.StringHashcode(buf.String()) +func expandRuleTargetIPs(vTargetIps *schema.Set) []*route53resolver.TargetAddress { + targetAddresses := []*route53resolver.TargetAddress{} + + for _, vTargetIp := range vTargetIps.List() { + targetAddress := &route53resolver.TargetAddress{} + + mTargetIp := vTargetIp.(map[string]interface{}) + + if vIp, ok := mTargetIp["ip"].(string); ok && vIp != "" { + targetAddress.Ip = aws.String(vIp) + } + if vPort, ok := mTargetIp["port"].(int); ok { + targetAddress.Port = aws.Int64(int64(vPort)) + } + + targetAddresses = append(targetAddresses, targetAddress) + } + + return targetAddresses +} + +func flattenRuleTargetIPs(targetAddresses []*route53resolver.TargetAddress) []interface{} { + if targetAddresses == nil { + return []interface{}{} + } + + vTargetIps := []interface{}{} + + for _, targetAddress := range targetAddresses { + mTargetIp := map[string]interface{}{ + "ip": aws.StringValue(targetAddress.Ip), + "port": int(aws.Int64Value(targetAddress.Port)), + } + + vTargetIps = append(vTargetIps, mTargetIp) + } + + return vTargetIps } // trimTrailingPeriod is used to remove the trailing period diff --git a/internal/service/route53resolver/rule_association.go b/internal/service/route53resolver/rule_association.go index 11f32d798c9..06a0043f45c 100644 --- a/internal/service/route53resolver/rule_association.go +++ b/internal/service/route53resolver/rule_association.go @@ -1,170 +1,212 @@ package route53resolver import ( - "fmt" + "context" + "errors" "log" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/hashicorp/terraform-provider-aws/internal/conns" -) - -const ( - RuleAssociationStatusDeleted = "DELETED" -) - -const ( - ruleAssociationCreatedDefaultTimeout = 10 * time.Minute - ruleAssociationDeletedDefaultTimeout = 10 * time.Minute + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceRuleAssociation() *schema.Resource { return &schema.Resource{ - Create: resourceRuleAssociationCreate, - Read: resourceRuleAssociationRead, - Delete: resourceRuleAssociationDelete, + CreateWithoutTimeout: resourceRuleAssociationCreate, + ReadWithoutTimeout: resourceRuleAssociationRead, + DeleteWithoutTimeout: resourceRuleAssociationDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(ruleAssociationCreatedDefaultTimeout), - Delete: schema.DefaultTimeout(ruleAssociationDeletedDefaultTimeout), + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validResolverName, + }, "resolver_rule_id": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 64), }, - "vpc_id": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 64), }, - - "name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validResolverName, - }, }, } } -func resourceRuleAssociationCreate(d *schema.ResourceData, meta interface{}) error { +func resourceRuleAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - req := &route53resolver.AssociateResolverRuleInput{ + input := &route53resolver.AssociateResolverRuleInput{ ResolverRuleId: aws.String(d.Get("resolver_rule_id").(string)), VPCId: aws.String(d.Get("vpc_id").(string)), } + if v, ok := d.GetOk("name"); ok { - req.Name = aws.String(v.(string)) + input.Name = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating Route 53 Resolver rule association: %s", req) - resp, err := conn.AssociateResolverRule(req) + output, err := conn.AssociateResolverRuleWithContext(ctx, input) + if err != nil { - return fmt.Errorf("error creating Route 53 Resolver rule association: %s", err) + return diag.Errorf("creating Route53 Resolver Rule Association: %s", err) } - d.SetId(aws.StringValue(resp.ResolverRuleAssociation.Id)) + d.SetId(aws.StringValue(output.ResolverRuleAssociation.Id)) - err = RuleAssociationWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutCreate), - []string{route53resolver.ResolverRuleAssociationStatusCreating}, - []string{route53resolver.ResolverRuleAssociationStatusComplete}) - if err != nil { - return err + if _, err := waitRuleAssociationCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Rule Association (%s) create: %s", d.Id(), err) } - return resourceRuleAssociationRead(d, meta) + return resourceRuleAssociationRead(ctx, d, meta) } -func resourceRuleAssociationRead(d *schema.ResourceData, meta interface{}) error { +func resourceRuleAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - assocRaw, state, err := ruleAssociationRefresh(conn, d.Id())() - if err != nil { - return fmt.Errorf("error getting Route53 Resolver rule association (%s): %s", d.Id(), err) - } - if state == RuleAssociationStatusDeleted { - log.Printf("[WARN] Route53 Resolver rule association (%s) not found, removing from state", d.Id()) + ruleAssociation, err := FindResolverRuleAssociationByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Route53 Resolver Rule Association (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - assoc := assocRaw.(*route53resolver.ResolverRuleAssociation) + if err != nil { + return diag.Errorf("reading Route53 Resolver Rule Association (%s): %s", d.Id(), err) + } - d.Set("name", assoc.Name) - d.Set("resolver_rule_id", assoc.ResolverRuleId) - d.Set("vpc_id", assoc.VPCId) + d.Set("name", ruleAssociation.Name) + d.Set("resolver_rule_id", ruleAssociation.ResolverRuleId) + d.Set("vpc_id", ruleAssociation.VPCId) return nil } -func resourceRuleAssociationDelete(d *schema.ResourceData, meta interface{}) error { +func resourceRuleAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - log.Printf("[DEBUG] Deleting Route53 Resolver rule association: %s", d.Id()) - _, err := conn.DisassociateResolverRule(&route53resolver.DisassociateResolverRuleInput{ + log.Printf("[DEBUG] Deleting Route53 Resolver Rule Association: %s", d.Id()) + _, err := conn.DisassociateResolverRuleWithContext(ctx, &route53resolver.DisassociateResolverRuleInput{ ResolverRuleId: aws.String(d.Get("resolver_rule_id").(string)), VPCId: aws.String(d.Get("vpc_id").(string)), }) + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { return nil } + + if err != nil { + return diag.Errorf("deleting Route53 Resolver Rule Association (%s): %s", d.Id(), err) + } + + if _, err := waitRuleAssociationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Route53 Resolver Rule Association (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindResolverRuleAssociationByID(ctx context.Context, conn *route53resolver.Route53Resolver, id string) (*route53resolver.ResolverRuleAssociation, error) { + input := &route53resolver.GetResolverRuleAssociationInput{ + ResolverRuleAssociationId: aws.String(id), + } + + output, err := conn.GetResolverRuleAssociationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { - return fmt.Errorf("error deleting Route 53 Resolver rule association (%s): %s", d.Id(), err) + return nil, err } - return RuleAssociationWaitUntilTargetState(conn, d.Id(), d.Timeout(schema.TimeoutDelete), - []string{route53resolver.ResolverRuleAssociationStatusDeleting}, - []string{RuleAssociationStatusDeleted}) + if output == nil || output.ResolverRuleAssociation == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ResolverRuleAssociation, nil } -func ruleAssociationRefresh(conn *route53resolver.Route53Resolver, assocId string) resource.StateRefreshFunc { +func statusRuleAssociation(ctx context.Context, conn *route53resolver.Route53Resolver, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.GetResolverRuleAssociation(&route53resolver.GetResolverRuleAssociationInput{ - ResolverRuleAssociationId: aws.String(assocId), - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return "", RuleAssociationStatusDeleted, nil + output, err := FindResolverRuleAssociationByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil } + if err != nil { return nil, "", err } - if statusMessage := aws.StringValue(resp.ResolverRuleAssociation.StatusMessage); statusMessage != "" { - log.Printf("[INFO] Route 53 Resolver rule association (%s) status message: %s", assocId, statusMessage) - } + return output, aws.StringValue(output.Status), nil + } +} + +func waitRuleAssociationCreated(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverRuleAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{route53resolver.ResolverRuleAssociationStatusCreating}, + Target: []string{route53resolver.ResolverRuleAssociationStatusComplete}, + Refresh: statusRuleAssociation(ctx, conn, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) - return resp.ResolverRuleAssociation, aws.StringValue(resp.ResolverRuleAssociation.Status), nil + if output, ok := outputRaw.(*route53resolver.ResolverRuleAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err } + + return nil, err } -func RuleAssociationWaitUntilTargetState(conn *route53resolver.Route53Resolver, assocId string, timeout time.Duration, pending, target []string) error { +func waitRuleAssociationDeleted(ctx context.Context, conn *route53resolver.Route53Resolver, id string, timeout time.Duration) (*route53resolver.ResolverRuleAssociation, error) { stateConf := &resource.StateChangeConf{ - Pending: pending, - Target: target, - Refresh: ruleAssociationRefresh(conn, assocId), + Pending: []string{route53resolver.ResolverRuleAssociationStatusDeleting}, + Target: []string{}, + Refresh: statusRuleAssociation(ctx, conn, id), Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 5 * time.Second, } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for Route53 Resolver rule association (%s) to reach target state: %s", assocId, err) + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*route53resolver.ResolverRuleAssociation); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) + + return output, err } - return nil + return nil, err } diff --git a/internal/service/route53resolver/rule_association_test.go b/internal/service/route53resolver/rule_association_test.go index ff5c51ca14e..6538ad3da18 100644 --- a/internal/service/route53resolver/rule_association_test.go +++ b/internal/service/route53resolver/rule_association_test.go @@ -1,25 +1,28 @@ package route53resolver_test import ( + "context" "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverRuleAssociation_basic(t *testing.T) { var assn route53resolver.ResolverRuleAssociation - resourceNameVpc := "aws_vpc.example" - resourceNameRule := "aws_route53_resolver_rule.example" - resourceNameAssoc := "aws_route53_resolver_rule_association.example" - name := fmt.Sprintf("terraform-testacc-r53-resolver-%d", sdkacctest.RandInt()) + resourceName := "aws_route53_resolver_rule_association.test" + vpcResourceName := "aws_vpc.test" + ruleResourceName := "aws_route53_resolver_rule.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domainName := acctest.RandomDomainName() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -28,16 +31,16 @@ func TestAccRoute53ResolverRuleAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckRuleAssociationDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleAssociationConfig_basic(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckRuleAssociationExists(resourceNameAssoc, &assn), - resource.TestCheckResourceAttrPair(resourceNameAssoc, "vpc_id", resourceNameVpc, "id"), - resource.TestCheckResourceAttrPair(resourceNameAssoc, "resolver_rule_id", resourceNameRule, "id"), - resource.TestCheckResourceAttr(resourceNameAssoc, "name", name), + Config: testAccRuleAssociationConfig_basic(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckRuleAssociationExists(resourceName, &assn), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "resolver_rule_id", ruleResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), ), }, { - ResourceName: resourceNameAssoc, + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -45,30 +48,80 @@ func TestAccRoute53ResolverRuleAssociation_basic(t *testing.T) { }) } +func TestAccRoute53ResolverRuleAssociation_disappears(t *testing.T) { + var assn route53resolver.ResolverRuleAssociation + resourceName := "aws_route53_resolver_rule_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domainName := acctest.RandomDomainName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRuleAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRuleAssociationConfig_basic(rName, domainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleAssociationExists(resourceName, &assn), + acctest.CheckResourceDisappears(acctest.Provider, tfroute53resolver.ResourceRuleAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRoute53ResolverRuleAssociation_Disappears_vpc(t *testing.T) { + var assn route53resolver.ResolverRuleAssociation + resourceName := "aws_route53_resolver_rule_association.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domainName := acctest.RandomDomainName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRuleAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRuleAssociationConfig_basic(rName, domainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleAssociationExists(resourceName, &assn), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPC(), vpcResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckRuleAssociationDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_route53_resolver_rule_association" { continue } - // Try to find the resource - _, err := conn.GetResolverRuleAssociation(&route53resolver.GetResolverRuleAssociationInput{ - ResolverRuleAssociationId: aws.String(rs.Primary.ID), - }) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindResolverRuleAssociationByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver rule association still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Rule Association still exists: %s", rs.Primary.ID) } + return nil } -func testAccCheckRuleAssociationExists(n string, assn *route53resolver.ResolverRuleAssociation) resource.TestCheckFunc { +func testAccCheckRuleAssociationExists(n string, v *route53resolver.ResolverRuleAssociation) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -76,26 +129,26 @@ func testAccCheckRuleAssociationExists(n string, assn *route53resolver.ResolverR } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver rule association ID is set") + return fmt.Errorf("No Route53 Resolver Rule Association ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - resp, err := conn.GetResolverRuleAssociation(&route53resolver.GetResolverRuleAssociationInput{ - ResolverRuleAssociationId: aws.String(rs.Primary.ID), - }) + + output, err := tfroute53resolver.FindResolverRuleAssociationByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *assn = *resp.ResolverRuleAssociation + *v = *output return nil } } -func testAccRuleAssociationConfig_basic(name string) string { +func testAccRuleAssociationConfig_basic(rName, domainName string) string { return fmt.Sprintf(` -resource "aws_vpc" "example" { +resource "aws_vpc" "test" { cidr_block = "10.6.0.0/16" enable_dns_hostnames = true enable_dns_support = true @@ -105,16 +158,16 @@ resource "aws_vpc" "example" { } } -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q name = %[1]q rule_type = "SYSTEM" } -resource "aws_route53_resolver_rule_association" "example" { +resource "aws_route53_resolver_rule_association" "test" { name = %[1]q - resolver_rule_id = aws_route53_resolver_rule.example.id - vpc_id = aws_vpc.example.id + resolver_rule_id = aws_route53_resolver_rule.test.id + vpc_id = aws_vpc.test.id } -`, name) +`, rName, domainName) } diff --git a/internal/service/route53resolver/rule_data_source.go b/internal/service/route53resolver/rule_data_source.go index 9cf84cedcab..672488fac6b 100644 --- a/internal/service/route53resolver/rule_data_source.go +++ b/internal/service/route53resolver/rule_data_source.go @@ -1,11 +1,11 @@ package route53resolver import ( - "fmt" - "log" + "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -14,14 +14,13 @@ import ( func DataSourceRule() *schema.Resource { return &schema.Resource{ - Read: dataSourceRuleRead, + ReadWithoutTimeout: dataSourceRuleRead, Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, Computed: true, }, - "domain_name": { Type: schema.TypeString, Optional: true, @@ -29,7 +28,6 @@ func DataSourceRule() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 256), ConflictsWith: []string{"resolver_rule_id"}, }, - "name": { Type: schema.TypeString, Optional: true, @@ -37,66 +35,53 @@ func DataSourceRule() *schema.Resource { ValidateFunc: validResolverName, ConflictsWith: []string{"resolver_rule_id"}, }, - "owner_id": { Type: schema.TypeString, Computed: true, }, - "resolver_endpoint_id": { Type: schema.TypeString, Optional: true, Computed: true, ConflictsWith: []string{"resolver_rule_id"}, }, - "resolver_rule_id": { Type: schema.TypeString, Optional: true, Computed: true, ConflictsWith: []string{"domain_name", "name", "resolver_endpoint_id", "rule_type"}, }, - "rule_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - route53resolver.RuleTypeOptionForward, - route53resolver.RuleTypeOptionSystem, - route53resolver.RuleTypeOptionRecursive, - }, false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(route53resolver.RuleTypeOption_Values(), false), ConflictsWith: []string{"resolver_rule_id"}, }, - "share_status": { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), }, } } -func dataSourceRuleRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + var err error var rule *route53resolver.ResolverRule if v, ok := d.GetOk("resolver_rule_id"); ok { - ruleRaw, state, err := ruleRefresh(conn, v.(string))() - if err != nil { - return fmt.Errorf("error getting Route53 Resolver rule (%s): %w", v, err) - } + id := v.(string) + rule, err = FindResolverRuleByID(ctx, conn, id) - if state == RuleStatusDeleted { - return fmt.Errorf("no Route53 Resolver rules matched found with the id (%q)", v) + if err != nil { + return diag.Errorf("reading Route53 Resolver Rule (%s): %s", id, err) } - - rule = ruleRaw.(*route53resolver.ResolverRule) } else { - req := &route53resolver.ListResolverRulesInput{ + input := &route53resolver.ListResolverRulesInput{ Filters: buildAttributeFilterList(map[string]string{ "DOMAIN_NAME": d.Get("domain_name").(string), "NAME": d.Get("name").(string), @@ -105,26 +90,28 @@ func dataSourceRuleRead(d *schema.ResourceData, meta interface{}) error { }), } - rules := []*route53resolver.ResolverRule{} - log.Printf("[DEBUG] Listing Route53 Resolver rules: %s", req) - err := conn.ListResolverRulesPages(req, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { + var rules []*route53resolver.ResolverRule + err = conn.ListResolverRulesPagesWithContext(ctx, input, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { rules = append(rules, page.ResolverRules...) return !lastPage }) + if err != nil { - return fmt.Errorf("error getting Route53 Resolver rules: %w", err) + return diag.Errorf("listing Route53 Resolver Rules: %s", err) } + if n := len(rules); n == 0 { - return fmt.Errorf("no Route53 Resolver rules matched") + return diag.Errorf("no Route53 Resolver Rules matched") } else if n > 1 { - return fmt.Errorf("%d Route53 Resolver rules matched; use additional constraints to reduce matches to a rule", n) + return diag.Errorf("%d Route53 Resolver Rules matched; use additional constraints to reduce matches to a single Rule", n) } rule = rules[0] } d.SetId(aws.StringValue(rule.Id)) - d.Set("arn", rule.Arn) + arn := aws.StringValue(rule.Arn) + d.Set("arn", arn) // To be consistent with other AWS services that do not accept a trailing period, // we remove the suffix from the Domain Name returned from the API d.Set("domain_name", trimTrailingPeriod(aws.StringValue(rule.DomainName))) @@ -137,15 +124,14 @@ func dataSourceRuleRead(d *schema.ResourceData, meta interface{}) error { d.Set("share_status", shareStatus) // https://github.com/hashicorp/terraform-provider-aws/issues/10211 if shareStatus != route53resolver.ShareStatusSharedWithMe { - arn := aws.StringValue(rule.Arn) - tags, err := ListTags(conn, arn) + tags, err := ListTagsWithContext(ctx, conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Route 53 Resolver rule (%s): %w", arn, err) + return diag.Errorf("listing tags for Route53 Resolver Rule (%s): %s", arn, err) } if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return diag.Errorf("setting tags: %s", err) } } diff --git a/internal/service/route53resolver/rule_data_source_test.go b/internal/service/route53resolver/rule_data_source_test.go index a06bcb22157..dc3a38104ba 100644 --- a/internal/service/route53resolver/rule_data_source_test.go +++ b/internal/service/route53resolver/rule_data_source_test.go @@ -15,8 +15,9 @@ func init() { } func TestAccRoute53ResolverRuleDataSource_basic(t *testing.T) { + domainName := acctest.RandomDomainName() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_route53_resolver_rule.example" + resourceName := "aws_route53_resolver_rule.test" ds1ResourceName := "data.aws_route53_resolver_rule.by_resolver_rule_id" ds2ResourceName := "data.aws_route53_resolver_rule.by_domain_name" ds3ResourceName := "data.aws_route53_resolver_rule.by_name_and_rule_type" @@ -27,8 +28,8 @@ func TestAccRoute53ResolverRuleDataSource_basic(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccRuleDataSourceConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccRuleDataSourceConfig_basic(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(ds1ResourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(ds1ResourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(ds1ResourceName, "domain_name", resourceName, "domain_name"), @@ -68,8 +69,9 @@ func TestAccRoute53ResolverRuleDataSource_basic(t *testing.T) { } func TestAccRoute53ResolverRuleDataSource_resolverEndpointIdWithTags(t *testing.T) { + domainName := acctest.RandomDomainName() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_route53_resolver_rule.example" + resourceName := "aws_route53_resolver_rule.test" ds1ResourceName := "data.aws_route53_resolver_rule.by_resolver_endpoint_id" resource.ParallelTest(t, resource.TestCase{ @@ -78,8 +80,8 @@ func TestAccRoute53ResolverRuleDataSource_resolverEndpointIdWithTags(t *testing. ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccRuleDataSourceConfig_resolverEndpointIDTags(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccRuleDataSourceConfig_resolverEndpointIDTags(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(ds1ResourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(ds1ResourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(ds1ResourceName, "domain_name", resourceName, "domain_name"), @@ -100,8 +102,9 @@ func TestAccRoute53ResolverRuleDataSource_resolverEndpointIdWithTags(t *testing. } func TestAccRoute53ResolverRuleDataSource_sharedByMe(t *testing.T) { + domainName := acctest.RandomDomainName() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_route53_resolver_rule.example" + resourceName := "aws_route53_resolver_rule.test" ds1ResourceName := "data.aws_route53_resolver_rule.by_resolver_endpoint_id" resource.ParallelTest(t, resource.TestCase{ @@ -114,8 +117,8 @@ func TestAccRoute53ResolverRuleDataSource_sharedByMe(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(t), Steps: []resource.TestStep{ { - Config: testAccRuleDataSourceConfig_sharedByMe(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccRuleDataSourceConfig_sharedByMe(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(ds1ResourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(ds1ResourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(ds1ResourceName, "domain_name", resourceName, "domain_name"), @@ -130,15 +133,15 @@ func TestAccRoute53ResolverRuleDataSource_sharedByMe(t *testing.T) { resource.TestCheckResourceAttrPair(ds1ResourceName, "tags.Key1", resourceName, "tags.Key1"), resource.TestCheckResourceAttrPair(ds1ResourceName, "tags.Key2", resourceName, "tags.Key2"), ), - ExpectNonEmptyPlan: true, }, }, }) } func TestAccRoute53ResolverRuleDataSource_sharedWithMe(t *testing.T) { + domainName := acctest.RandomDomainName() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_route53_resolver_rule.example" + resourceName := "aws_route53_resolver_rule.test" ds1ResourceName := "data.aws_route53_resolver_rule.by_resolver_endpoint_id" resource.ParallelTest(t, resource.TestCase{ @@ -151,8 +154,8 @@ func TestAccRoute53ResolverRuleDataSource_sharedWithMe(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(t), Steps: []resource.TestStep{ { - Config: testAccRuleDataSourceConfig_sharedWithMe(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccRuleDataSourceConfig_sharedWithMe(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(ds1ResourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(ds1ResourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(ds1ResourceName, "domain_name", resourceName, "domain_name"), @@ -165,43 +168,42 @@ func TestAccRoute53ResolverRuleDataSource_sharedWithMe(t *testing.T) { // Tags cannot be retrieved for rules shared with us. resource.TestCheckResourceAttr(ds1ResourceName, "tags.%", "0"), ), - ExpectNonEmptyPlan: true, }, }, }) } -func testAccRuleDataSourceConfig_basic(rName string) string { +func testAccRuleDataSourceConfig_basic(rName, domainName string) string { return fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { - domain_name = "%[1]s.example.com" +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "SYSTEM" name = %[1]q } data "aws_route53_resolver_rule" "by_resolver_rule_id" { - resolver_rule_id = aws_route53_resolver_rule.example.id + resolver_rule_id = aws_route53_resolver_rule.test.id } data "aws_route53_resolver_rule" "by_domain_name" { - domain_name = aws_route53_resolver_rule.example.domain_name + domain_name = aws_route53_resolver_rule.test.domain_name } data "aws_route53_resolver_rule" "by_name_and_rule_type" { - name = aws_route53_resolver_rule.example.name - rule_type = aws_route53_resolver_rule.example.rule_type + name = aws_route53_resolver_rule.test.name + rule_type = aws_route53_resolver_rule.test.rule_type } -`, rName) +`, rName, domainName) } -func testAccRuleDataSourceConfig_resolverEndpointIDTags(rName string) string { - return testAccRuleConfig_resolverEndpoint(rName) + fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { - domain_name = "%[1]s.example.com" +func testAccRuleDataSourceConfig_resolverEndpointIDTags(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.bar.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id target_ip { ip = "192.0.2.7" @@ -214,19 +216,19 @@ resource "aws_route53_resolver_rule" "example" { } data "aws_route53_resolver_rule" "by_resolver_endpoint_id" { - resolver_endpoint_id = aws_route53_resolver_rule.example.resolver_endpoint_id + resolver_endpoint_id = aws_route53_resolver_rule.test.resolver_endpoint_id } -`, rName) +`, rName, domainName)) } -func testAccRuleDataSourceConfig_sharedByMe(rName string) string { - return acctest.ConfigAlternateAccountProvider() + testAccRuleConfig_resolverEndpoint(rName) + fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { - domain_name = "%[1]s.example.com" +func testAccRuleDataSourceConfig_sharedByMe(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), acctest.ConfigAlternateAccountProvider(), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.bar.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id target_ip { ip = "192.0.2.7" @@ -244,7 +246,7 @@ resource "aws_ram_resource_share" "test" { } resource "aws_ram_resource_association" "test" { - resource_arn = aws_route53_resolver_rule.example.arn + resource_arn = aws_route53_resolver_rule.test.arn resource_share_arn = aws_ram_resource_share.test.arn } @@ -256,21 +258,21 @@ resource "aws_ram_principal_association" "test" { } data "aws_route53_resolver_rule" "by_resolver_endpoint_id" { - resolver_endpoint_id = aws_route53_resolver_rule.example.resolver_endpoint_id + resolver_endpoint_id = aws_route53_resolver_rule.test.resolver_endpoint_id depends_on = [aws_ram_resource_association.test, aws_ram_principal_association.test] } -`, rName) +`, rName, domainName)) } -func testAccRuleDataSourceConfig_sharedWithMe(rName string) string { - return acctest.ConfigAlternateAccountProvider() + testAccRuleConfig_resolverEndpoint(rName) + fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { - domain_name = "%[1]s.example.com" +func testAccRuleDataSourceConfig_sharedWithMe(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), acctest.ConfigAlternateAccountProvider(), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.bar.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id target_ip { ip = "192.0.2.7" @@ -288,7 +290,7 @@ resource "aws_ram_resource_share" "test" { } resource "aws_ram_resource_association" "test" { - resource_arn = aws_route53_resolver_rule.example.arn + resource_arn = aws_route53_resolver_rule.test.arn resource_share_arn = aws_ram_resource_share.test.arn } @@ -302,11 +304,11 @@ resource "aws_ram_principal_association" "test" { data "aws_route53_resolver_rule" "by_resolver_endpoint_id" { provider = "awsalternate" - resolver_endpoint_id = aws_route53_resolver_rule.example.resolver_endpoint_id + resolver_endpoint_id = aws_route53_resolver_rule.test.resolver_endpoint_id depends_on = [aws_ram_resource_association.test, aws_ram_principal_association.test] } -`, rName) +`, rName, domainName)) } // testAccErrorCheckSkipRoute53 skips Route53 tests that have error messages indicating unsupported features diff --git a/internal/service/route53resolver/rule_test.go b/internal/service/route53resolver/rule_test.go index cf058d1931a..92970c6d707 100644 --- a/internal/service/route53resolver/rule_test.go +++ b/internal/service/route53resolver/rule_test.go @@ -1,22 +1,25 @@ package route53resolver_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfroute53resolver "github.com/hashicorp/terraform-provider-aws/internal/service/route53resolver" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRoute53ResolverRule_basic(t *testing.T) { var rule route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" + domainName := acctest.RandomDomainName() + resourceName := "aws_route53_resolver_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -25,14 +28,18 @@ func TestAccRoute53ResolverRule_basic(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basicNoTags, - Check: resource.ComposeTestCheckFunc( + Config: testAccRuleConfig_basic(domainName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", ""), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "resolver_endpoint_id", ""), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "target_ip.#", "0"), ), }, { @@ -44,9 +51,10 @@ func TestAccRoute53ResolverRule_basic(t *testing.T) { }) } -func TestAccRoute53ResolverRule_justDotDomainName(t *testing.T) { +func TestAccRoute53ResolverRule_disappears(t *testing.T) { var rule route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" + domainName := acctest.RandomDomainName() + resourceName := "aws_route53_resolver_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -55,28 +63,21 @@ func TestAccRoute53ResolverRule_justDotDomainName(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basic("."), + Config: testAccRuleConfig_basic(domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "."), - resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), - resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + acctest.CheckResourceDisappears(acctest.Provider, tfroute53resolver.ResourceRule(), resourceName), ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccRoute53ResolverRule_trailingDotDomainName(t *testing.T) { +func TestAccRoute53ResolverRule_tags(t *testing.T) { var rule route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" + domainName := acctest.RandomDomainName() + resourceName := "aws_route53_resolver_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -85,14 +86,11 @@ func TestAccRoute53ResolverRule_trailingDotDomainName(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basic("example.com."), + Config: testAccRuleConfig_tags1(domainName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), - resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -100,13 +98,30 @@ func TestAccRoute53ResolverRule_trailingDotDomainName(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccRuleConfig_tags2(domainName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleExists(resourceName, &rule), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccRuleConfig_tags1(domainName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleExists(resourceName, &rule), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, }, }) } -func TestAccRoute53ResolverRule_tags(t *testing.T) { +func TestAccRoute53ResolverRule_justDotDomainName(t *testing.T) { var rule route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" + resourceName := "aws_route53_resolver_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -115,14 +130,14 @@ func TestAccRoute53ResolverRule_tags(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basicTags, + Config: testAccRuleConfig_basic("."), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), + resource.TestCheckResourceAttr(resourceName, "domain_name", "."), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.Usage", "original"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -130,26 +145,35 @@ func TestAccRoute53ResolverRule_tags(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + }, + }) +} + +func TestAccRoute53ResolverRule_trailingDotDomainName(t *testing.T) { + var rule route53resolver.ResolverRule + resourceName := "aws_route53_resolver_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, route53resolver.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRuleDestroy, + Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basicTagsChanged, + Config: testAccRuleConfig_basic("example.com."), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule), resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Usage", "changed"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { - Config: testAccRuleConfig_basicNoTags, - Check: resource.ComposeTestCheckFunc( - testAccCheckRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), - resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), - ), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -157,9 +181,10 @@ func TestAccRoute53ResolverRule_tags(t *testing.T) { func TestAccRoute53ResolverRule_updateName(t *testing.T) { var rule1, rule2 route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" - name1 := fmt.Sprintf("terraform-testacc-r53-resolver-%d", sdkacctest.RandInt()) - name2 := fmt.Sprintf("terraform-testacc-r53-resolver-%d", sdkacctest.RandInt()) + resourceName := "aws_route53_resolver_rule.test" + domainName := acctest.RandomDomainName() + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -168,12 +193,10 @@ func TestAccRoute53ResolverRule_updateName(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_basicName(name1), + Config: testAccRuleConfig_name(rName1, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name1), - resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { @@ -182,13 +205,11 @@ func TestAccRoute53ResolverRule_updateName(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccRuleConfig_basicName(name2), + Config: testAccRuleConfig_name(rName2, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule2), testAccCheckRulesSame(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name2), - resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), + resource.TestCheckResourceAttr(resourceName, "name", rName2), ), }, }, @@ -197,10 +218,11 @@ func TestAccRoute53ResolverRule_updateName(t *testing.T) { func TestAccRoute53ResolverRule_forward(t *testing.T) { var rule1, rule2, rule3 route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" - resourceNameEp1 := "aws_route53_resolver_endpoint.foo" - resourceNameEp2 := "aws_route53_resolver_endpoint.bar" - name := fmt.Sprintf("terraform-testacc-r53-resolver-%d", sdkacctest.RandInt()) + resourceName := "aws_route53_resolver_rule.test" + ep1ResourceName := "aws_route53_resolver_endpoint.test.0" + ep2ResourceName := "aws_route53_resolver_endpoint.test.1" + domainName := acctest.RandomDomainName() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -209,13 +231,13 @@ func TestAccRoute53ResolverRule_forward(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_forward(name), + Config: testAccRuleConfig_forward(rName, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), - resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp1, "id"), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", ep1ResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "target_ip.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_ip.*", map[string]string{ "ip": "192.0.2.6", @@ -229,13 +251,13 @@ func TestAccRoute53ResolverRule_forward(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccRuleConfig_forwardTargetIPChanged(name), + Config: testAccRuleConfig_forwardTargetIPChanged(rName, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule2), testAccCheckRulesSame(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp1, "id"), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", ep1ResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), resource.TestCheckResourceAttr(resourceName, "target_ip.#", "2"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_ip.*", map[string]string{ @@ -249,13 +271,13 @@ func TestAccRoute53ResolverRule_forward(t *testing.T) { ), }, { - Config: testAccRuleConfig_forwardEndpointChanged(name), + Config: testAccRuleConfig_forwardEndpointChanged(rName, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule3), testAccCheckRulesSame(&rule3, &rule2), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp2, "id"), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", ep2ResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), resource.TestCheckResourceAttr(resourceName, "target_ip.#", "2"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_ip.*", map[string]string{ @@ -274,9 +296,10 @@ func TestAccRoute53ResolverRule_forward(t *testing.T) { func TestAccRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { var rule1, rule2 route53resolver.ResolverRule - resourceName := "aws_route53_resolver_rule.example" - resourceNameEp := "aws_route53_resolver_endpoint.foo" - name := fmt.Sprintf("terraform-testacc-r53-resolver-%d", sdkacctest.RandInt()) + resourceName := "aws_route53_resolver_rule.test" + epResourceName := "aws_route53_resolver_endpoint.test.0" + domainName := acctest.RandomDomainName() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, @@ -285,13 +308,13 @@ func TestAccRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { CheckDestroy: testAccCheckRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccRuleConfig_forward(name), + Config: testAccRuleConfig_forward(rName, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), - resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp, "id"), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", epResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "target_ip.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_ip.*", map[string]string{ "ip": "192.0.2.6", @@ -300,14 +323,14 @@ func TestAccRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { ), }, { - Config: testAccRuleConfig_forwardEndpointRecreate(name), + Config: testAccRuleConfig_forwardEndpointRecreate(rName, domainName), Check: resource.ComposeTestCheckFunc( testAccCheckRuleExists(resourceName, &rule2), testAccCheckRulesDifferent(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), - resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), - resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp, "id"), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", epResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "target_ip.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_ip.*", map[string]string{ "ip": "192.0.2.6", @@ -321,18 +344,20 @@ func TestAccRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { func testAccCheckRulesSame(before, after *route53resolver.ResolverRule) resource.TestCheckFunc { return func(s *terraform.State) error { - if *before.Arn != *after.Arn { - return fmt.Errorf("Expected Route 53 Resolver rule ARNs to be the same. But they were: %v, %v", *before.Arn, *after.Arn) + if before, after := aws.StringValue(before.Arn), aws.StringValue(after.Arn); before != after { + return fmt.Errorf("Expected Route53 Resolver Rule ARNs to be the same. But they were: %s, %s", before, after) } + return nil } } func testAccCheckRulesDifferent(before, after *route53resolver.ResolverRule) resource.TestCheckFunc { return func(s *terraform.State) error { - if *before.Arn == *after.Arn { - return fmt.Errorf("Expected Route 53 Resolver rule ARNs to be different. But they were both: %v", *before.Arn) + if before, after := aws.StringValue(before.Arn), aws.StringValue(after.Arn); before == after { + return fmt.Errorf("Expected Route53 Resolver rule ARNs to be different. But they were both: %s", before) } + return nil } } @@ -345,23 +370,22 @@ func testAccCheckRuleDestroy(s *terraform.State) error { continue } - // Try to find the resource - _, err := conn.GetResolverRule(&route53resolver.GetResolverRuleInput{ - ResolverRuleId: aws.String(rs.Primary.ID), - }) - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { + _, err := tfroute53resolver.FindResolverRuleByID(context.Background(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } + if err != nil { return err } - return fmt.Errorf("Route 53 Resolver rule still exists: %s", rs.Primary.ID) + + return fmt.Errorf("Route53 Resolver Rule still exists: %s", rs.Primary.ID) } return nil } -func testAccCheckRuleExists(n string, rule *route53resolver.ResolverRule) resource.TestCheckFunc { +func testAccCheckRuleExists(n string, v *route53resolver.ResolverRule) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -369,18 +393,18 @@ func testAccCheckRuleExists(n string, rule *route53resolver.ResolverRule) resour } if rs.Primary.ID == "" { - return fmt.Errorf("No Route 53 Resolver rule ID is set") + return fmt.Errorf("No Route53 Resolver Rule ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).Route53ResolverConn - res, err := conn.GetResolverRule(&route53resolver.GetResolverRuleInput{ - ResolverRuleId: aws.String(rs.Primary.ID), - }) + + output, err := tfroute53resolver.FindResolverRuleByID(context.Background(), conn, rs.Primary.ID) + if err != nil { return err } - *rule = *res.ResolverRule + *v = *output return nil } @@ -388,81 +412,74 @@ func testAccCheckRuleExists(n string, rule *route53resolver.ResolverRule) resour func testAccRuleConfig_basic(domainName string) string { return fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { +resource "aws_route53_resolver_rule" "test" { domain_name = %[1]q rule_type = "SYSTEM" } `, domainName) } -const testAccRuleConfig_basicNoTags = ` -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" - rule_type = "SYSTEM" -} -` - -const testAccRuleConfig_basicTags = ` -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_tags1(domainName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[1]q rule_type = "SYSTEM" tags = { - Environment = "production" - Usage = "original" + %[2]q = %[3]q } } -` +`, domainName, tagKey1, tagValue1) +} -const testAccRuleConfig_basicTagsChanged = ` -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_tags2(domainName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[1]q rule_type = "SYSTEM" tags = { - Usage = "changed" + %[2]q = %[3]q + %[4]q = %[5]q } } -` +`, domainName, tagKey1, tagValue1, tagKey2, tagValue2) +} -func testAccRuleConfig_basicName(name string) string { +func testAccRuleConfig_name(rName, domainName string) string { return fmt.Sprintf(` -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "SYSTEM" - name = %q + name = %[1]q } -`, name) +`, rName, domainName) } -func testAccRuleConfig_forward(name string) string { - return fmt.Sprintf(` -%s - -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_forward(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" - name = %q + name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.foo.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[0].id target_ip { ip = "192.0.2.6" } } -`, testAccRuleConfig_resolverEndpoint(name), name) +`, rName, domainName)) } -func testAccRuleConfig_forwardTargetIPChanged(name string) string { - return fmt.Sprintf(` -%s - -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_forwardTargetIPChanged(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" - name = %q + name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.foo.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[0].id target_ip { ip = "192.0.2.7" @@ -473,19 +490,17 @@ resource "aws_route53_resolver_rule" "example" { port = 54 } } -`, testAccRuleConfig_resolverEndpoint(name), name) +`, rName, domainName)) } -func testAccRuleConfig_forwardEndpointChanged(name string) string { - return fmt.Sprintf(` -%s - -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_forwardEndpointChanged(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" - name = %q + name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.bar.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id target_ip { ip = "192.0.2.7" @@ -496,30 +511,28 @@ resource "aws_route53_resolver_rule" "example" { port = 54 } } -`, testAccRuleConfig_resolverEndpoint(name), name) +`, rName, domainName)) } -func testAccRuleConfig_forwardEndpointRecreate(name string) string { - return fmt.Sprintf(` -%s - -resource "aws_route53_resolver_rule" "example" { - domain_name = "example.com" +func testAccRuleConfig_forwardEndpointRecreate(rName, domainName string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointRecreateBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + domain_name = %[2]q rule_type = "FORWARD" - name = %q + name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.foo.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[0].id target_ip { ip = "192.0.2.6" } } -`, testAccRuleConfig_resolverEndpointRecreate(name), name) +`, rName, domainName)) } -func testAccRuleConfig_resolverVPC(name string) string { - return fmt.Sprintf(` -resource "aws_vpc" "foo" { +func testAccRuleConfig_vpcBase(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true @@ -529,141 +542,69 @@ resource "aws_vpc" "foo" { } } -data "aws_availability_zones" "available" { - state = "available" +resource "aws_subnet" "test" { + count = 3 - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_subnet" "sn1" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 0) - availability_zone = data.aws_availability_zones.available.names[0] + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) tags = { - Name = "%[1]s_1" - } -} - -resource "aws_subnet" "sn2" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 1) - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = "%[1]s_2" - } -} - -resource "aws_subnet" "sn3" { - vpc_id = aws_vpc.foo.id - cidr_block = cidrsubnet(aws_vpc.foo.cidr_block, 2, 2) - availability_zone = data.aws_availability_zones.available.names[2] - - tags = { - Name = "%[1]s_3" + Name = %[1]q } } -resource "aws_security_group" "sg1" { - vpc_id = aws_vpc.foo.id - name = "%[1]s_1" +resource "aws_security_group" "test" { + count = 2 - tags = { - Name = "%[1]s_1" - } -} - -resource "aws_security_group" "sg2" { - vpc_id = aws_vpc.foo.id - name = "%[1]s_2" + vpc_id = aws_vpc.test.id + name = "%[1]s-${count.index}" tags = { - Name = "%[1]s_2" + Name = %[1]q } } -`, name) +`, rName)) } -func testAccRuleConfig_resolverEndpoint(name string) string { - return fmt.Sprintf(` -%[1]s - -resource "aws_route53_resolver_endpoint" "foo" { - direction = "OUTBOUND" - name = "%[2]s_1" - - security_group_ids = [ - aws_security_group.sg1.id, - ] +func testAccRuleConfig_resolverEndpointBase(rName string) string { + return acctest.ConfigCompose(testAccRuleConfig_vpcBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + count = 2 - ip_address { - subnet_id = aws_subnet.sn1.id - } - - ip_address { - subnet_id = aws_subnet.sn2.id - } -} - -resource "aws_route53_resolver_endpoint" "bar" { direction = "OUTBOUND" - name = "%[2]s_2" + name = "%[1]s-${count.index}" - security_group_ids = [ - aws_security_group.sg1.id, - ] + security_group_ids = [aws_security_group.test[0].id] ip_address { - subnet_id = aws_subnet.sn1.id + subnet_id = aws_subnet.test[2].id } ip_address { - subnet_id = aws_subnet.sn3.id + subnet_id = aws_subnet.test[count.index].id } } -`, testAccRuleConfig_resolverVPC(name), name) +`, rName)) } -func testAccRuleConfig_resolverEndpointRecreate(name string) string { - return fmt.Sprintf(` -%[1]s - -resource "aws_route53_resolver_endpoint" "foo" { - direction = "OUTBOUND" - name = "%[2]s_1" - - security_group_ids = [ - aws_security_group.sg2.id, - ] - - ip_address { - subnet_id = aws_subnet.sn1.id - } - - ip_address { - subnet_id = aws_subnet.sn2.id - } -} +func testAccRuleConfig_resolverEndpointRecreateBase(rName string) string { + return acctest.ConfigCompose(testAccRuleConfig_vpcBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_endpoint" "test" { + count = 2 -resource "aws_route53_resolver_endpoint" "bar" { direction = "OUTBOUND" - name = "%[2]s_2" + name = "%[1]s-${count.index}" - security_group_ids = [ - aws_security_group.sg1.id, - ] + security_group_ids = [aws_security_group.test[1].id] ip_address { - subnet_id = aws_subnet.sn1.id + subnet_id = aws_subnet.test[2].id } ip_address { - subnet_id = aws_subnet.sn3.id + subnet_id = aws_subnet.test[count.index].id } } -`, testAccRuleConfig_resolverVPC(name), name) +`, rName)) } diff --git a/internal/service/route53resolver/rules_data_source.go b/internal/service/route53resolver/rules_data_source.go index 01f5402317d..253eb182377 100644 --- a/internal/service/route53resolver/rules_data_source.go +++ b/internal/service/route53resolver/rules_data_source.go @@ -1,22 +1,21 @@ package route53resolver import ( - "fmt" - "log" + "context" "regexp" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func DataSourceRules() *schema.Resource { return &schema.Resource{ - Read: dataSourceRulesRead, + ReadWithoutTimeout: dataSourceRulesRead, Schema: map[string]*schema.Schema{ "name_regex": { @@ -24,7 +23,6 @@ func DataSourceRules() *schema.Resource { Optional: true, ValidateFunc: validation.StringIsValidRegExp, }, - "owner_id": { Type: schema.TypeString, Optional: true, @@ -34,50 +32,36 @@ func DataSourceRules() *schema.Resource { validation.StringInSlice([]string{"Route 53 Resolver"}, false), ), }, - "resolver_endpoint_id": { Type: schema.TypeString, Optional: true, }, - "resolver_rule_ids": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, - "rule_type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - route53resolver.RuleTypeOptionForward, - route53resolver.RuleTypeOptionSystem, - route53resolver.RuleTypeOptionRecursive, - }, false), + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(route53resolver.RuleTypeOption_Values(), false), }, - "share_status": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - route53resolver.ShareStatusNotShared, - route53resolver.ShareStatusSharedWithMe, - route53resolver.ShareStatusSharedByMe, - }, false), + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(route53resolver.ShareStatus_Values(), false), }, }, } } -func dataSourceRulesRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceRulesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).Route53ResolverConn - req := &route53resolver.ListResolverRulesInput{} - resolverRuleIds := []*string{} + input := &route53resolver.ListResolverRulesInput{} + var ruleIDs []*string - log.Printf("[DEBUG] Listing Route53 Resolver rules: %s", req) - err := conn.ListResolverRulesPages(req, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { + err := conn.ListResolverRulesPagesWithContext(ctx, input, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { for _, rule := range page.ResolverRules { if v, ok := d.GetOk("name_regex"); ok && !regexp.MustCompile(v.(string)).MatchString(aws.StringValue(rule.Name)) { continue @@ -95,20 +79,19 @@ func dataSourceRulesRead(d *schema.ResourceData, meta interface{}) error { continue } - resolverRuleIds = append(resolverRuleIds, rule.Id) + ruleIDs = append(ruleIDs, rule.Id) } + return !lastPage }) + if err != nil { - return fmt.Errorf("error getting Route53 Resolver rules: %w", err) + return diag.Errorf("listing Route53 Resolver Rules: %s", err) } d.SetId(meta.(*conns.AWSClient).Region) - err = d.Set("resolver_rule_ids", flex.FlattenStringSet(resolverRuleIds)) - if err != nil { - return fmt.Errorf("error setting resolver_rule_ids: %w", err) - } + d.Set("resolver_rule_ids", aws.StringValueSlice(ruleIDs)) return nil } diff --git a/internal/service/route53resolver/rules_data_source_test.go b/internal/service/route53resolver/rules_data_source_test.go index 9020f016dfa..a1e93432739 100644 --- a/internal/service/route53resolver/rules_data_source_test.go +++ b/internal/service/route53resolver/rules_data_source_test.go @@ -31,8 +31,10 @@ func TestAccRoute53ResolverRulesDataSource_basic(t *testing.T) { } func TestAccRoute53ResolverRulesDataSource_resolverEndpointID(t *testing.T) { - rName1 := fmt.Sprintf("tf-testacc-r53-resolver-%s", sdkacctest.RandString(8)) - rName2 := fmt.Sprintf("tf-testacc-r53-resolver-%s", sdkacctest.RandString(8)) + domainName1 := acctest.RandomDomainName() + domainName2 := acctest.RandomDomainName() + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) ds1ResourceName := "data.aws_route53_resolver_rules.by_resolver_endpoint_id" ds2ResourceName := "data.aws_route53_resolver_rules.by_resolver_endpoint_id_rule_type_share_status" ds3ResourceName := "data.aws_route53_resolver_rules.by_invalid_owner_id" @@ -43,7 +45,7 @@ func TestAccRoute53ResolverRulesDataSource_resolverEndpointID(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccRulesDataSourceConfig_resolverEndpointID(rName1, rName2), + Config: testAccRulesDataSourceConfig_resolverEndpointID(rName1, rName2, domainName1, domainName2), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(ds1ResourceName, "resolver_rule_ids.#", "1"), resource.TestCheckResourceAttr(ds2ResourceName, "resolver_rule_ids.#", "1"), @@ -101,14 +103,14 @@ data "aws_route53_resolver_rules" "test" { } ` -func testAccRulesDataSourceConfig_resolverEndpointID(rName1, rName2 string) string { - return testAccRuleConfig_resolverEndpoint(rName1) + fmt.Sprintf(` +func testAccRulesDataSourceConfig_resolverEndpointID(rName1, rName2, domainName1, domainName2 string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName1), fmt.Sprintf(` resource "aws_route53_resolver_rule" "forward" { - domain_name = "%[1]s.example.com" + domain_name = %[3]q rule_type = "FORWARD" name = %[1]q - resolver_endpoint_id = aws_route53_resolver_endpoint.bar.id + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id target_ip { ip = "192.0.2.7" @@ -116,7 +118,7 @@ resource "aws_route53_resolver_rule" "forward" { } resource "aws_route53_resolver_rule" "recursive" { - domain_name = "%[2]s.example.org" + domain_name = %[4]q rule_type = "RECURSIVE" name = %[2]q } @@ -137,7 +139,7 @@ data "aws_route53_resolver_rules" "by_invalid_owner_id" { owner_id = "000000000000" share_status = "SHARED_WITH_ME" } -`, rName1, rName2) +`, rName1, rName2, domainName1, domainName2)) } func testAccRulesDataSourceConfig_nameRegex(rCount int, rName string) string { diff --git a/internal/service/route53resolver/status.go b/internal/service/route53resolver/status.go deleted file mode 100644 index aca47e390f2..00000000000 --- a/internal/service/route53resolver/status.go +++ /dev/null @@ -1,130 +0,0 @@ -package route53resolver - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -const ( - resolverQueryLogConfigAssociationStatusNotFound = "NotFound" - resolverQueryLogConfigAssociationStatusUnknown = "Unknown" - - resolverQueryLogConfigStatusNotFound = "NotFound" - resolverQueryLogConfigStatusUnknown = "Unknown" - - dnssecConfigStatusNotFound = "NotFound" - dnssecConfigStatusUnknown = "Unknown" - - firewallDomainListStatusNotFound = "NotFound" - firewallDomainListStatusUnknown = "Unknown" - - resolverFirewallRuleGroupAssociationStatusNotFound = "NotFound" - resolverFirewallRuleGroupAssociationStatusUnknown = "Unknown" -) - -// StatusQueryLogConfigAssociation fetches the QueryLogConfigAssociation and its Status -func StatusQueryLogConfigAssociation(conn *route53resolver.Route53Resolver, queryLogConfigAssociationID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - queryLogConfigAssociation, err := FindResolverQueryLogConfigAssociationByID(conn, queryLogConfigAssociationID) - - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil, resolverQueryLogConfigAssociationStatusNotFound, nil - } - - if err != nil { - return nil, resolverQueryLogConfigAssociationStatusUnknown, err - } - - if queryLogConfigAssociation == nil { - return nil, resolverQueryLogConfigAssociationStatusNotFound, nil - } - - return queryLogConfigAssociation, aws.StringValue(queryLogConfigAssociation.Status), nil - } -} - -// StatusQueryLogConfig fetches the QueryLogConfig and its Status -func StatusQueryLogConfig(conn *route53resolver.Route53Resolver, queryLogConfigID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - queryLogConfig, err := FindResolverQueryLogConfigByID(conn, queryLogConfigID) - - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil, resolverQueryLogConfigStatusNotFound, nil - } - - if err != nil { - return nil, resolverQueryLogConfigStatusUnknown, err - } - - if queryLogConfig == nil { - return nil, resolverQueryLogConfigStatusNotFound, nil - } - - return queryLogConfig, aws.StringValue(queryLogConfig.Status), nil - } -} - -// StatusDNSSECConfig fetches the DnssecConfig and its Status -func StatusDNSSECConfig(conn *route53resolver.Route53Resolver, dnssecConfigID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - dnssecConfig, err := FindResolverDNSSECConfigByID(conn, dnssecConfigID) - - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil, dnssecConfigStatusNotFound, nil - } - - if err != nil { - return nil, dnssecConfigStatusUnknown, err - } - - if dnssecConfig == nil { - return nil, dnssecConfigStatusNotFound, nil - } - - return dnssecConfig, aws.StringValue(dnssecConfig.ValidationStatus), nil - } -} - -// StatusFirewallDomainList fetches the FirewallDomainList and its Status -func StatusFirewallDomainList(conn *route53resolver.Route53Resolver, firewallDomainListId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - firewallDomainList, err := FindFirewallDomainListByID(conn, firewallDomainListId) - - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - return nil, firewallDomainListStatusNotFound, nil - } - - if err != nil { - return nil, firewallDomainListStatusUnknown, err - } - - if firewallDomainList == nil { - return nil, firewallDomainListStatusNotFound, nil - } - - return firewallDomainList, aws.StringValue(firewallDomainList.Status), nil - } -} - -// StatusFirewallRuleGroupAssociation fetches the FirewallRuleGroupAssociation and its Status -func StatusFirewallRuleGroupAssociation(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - firewallRuleGroupAssociation, err := FindFirewallRuleGroupAssociationByID(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/internal/service/route53resolver/sweep.go b/internal/service/route53resolver/sweep.go index 82146f27045..7d9726d8b3f 100644 --- a/internal/service/route53resolver/sweep.go +++ b/internal/service/route53resolver/sweep.go @@ -9,8 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/sweep" @@ -32,7 +31,7 @@ func init() { resource.AddTestSweepers("aws_route53_resolver_firewall_config", &resource.Sweeper{ Name: "aws_route53_resolver_firewall_config", - F: sweepFirewallConfig, + F: sweepFirewallConfigs, }) resource.AddTestSweepers("aws_route53_resolver_firewall_domain_list", &resource.Sweeper{ @@ -67,7 +66,7 @@ func init() { resource.AddTestSweepers("aws_route53_resolver_query_log_config_association", &resource.Sweeper{ Name: "aws_route53_resolver_query_log_config_association", - F: sweepQueryLogAssociationsConfig, + F: sweepQueryLogAssociationsConfigs, }) resource.AddTestSweepers("aws_route53_resolver_query_log_config", &resource.Sweeper{ @@ -98,51 +97,42 @@ func sweepDNSSECConfig(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn + input := &route53resolver.ListResolverDnssecConfigsInput{} + sweepResources := make([]sweep.Sweepable, 0) - var sweeperErrs *multierror.Error - err = conn.ListResolverDnssecConfigsPages(&route53resolver.ListResolverDnssecConfigsInput{}, func(page *route53resolver.ListResolverDnssecConfigsOutput, lastPage bool) bool { + err = conn.ListResolverDnssecConfigsPages(input, func(page *route53resolver.ListResolverDnssecConfigsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, resolverDnssecConfig := range page.ResolverDnssecConfigs { - if resolverDnssecConfig == nil { - continue - } - - id := aws.StringValue(resolverDnssecConfig.Id) - resourceId := aws.StringValue(resolverDnssecConfig.ResourceId) - - log.Printf("[INFO] Deleting Route 53 Resolver Dnssec config: %s", id) - + for _, v := range page.ResolverDnssecConfigs { r := ResourceDNSSECConfig() d := r.Data(nil) - d.SetId(aws.StringValue(resolverDnssecConfig.Id)) - d.Set("resource_id", resourceId) + d.SetId(aws.StringValue(v.Id)) + d.Set("resource_id", v.ResourceId) - err := r.Delete(d, client) - - if err != nil { - sweeperErr := fmt.Errorf("error deleting Route 53 Resolver Resolver Dnssec config (%s): %w", id, err) - log.Printf("[ERROR] %s", sweeperErr) - sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route 53 Resolver Resolver Dnssec config sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() + log.Printf("[WARN] Skipping Route53 Resolver DNSSEC Config sweep for %s: %s", region, err) + return nil } if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route 53 Resolver Resolver Dnssec config: %w", err)) + return fmt.Errorf("error listing Route53 Resolver DNSSEC Configs (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver DNSSEC Configs (%s): %w", region, err) + } + + return nil } func sweepEndpoints(region string) error { @@ -151,91 +141,85 @@ func sweepEndpoints(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn + input := &route53resolver.ListResolverEndpointsInput{} + sweepResources := make([]sweep.Sweepable, 0) - var errors error - err = conn.ListResolverEndpointsPages(&route53resolver.ListResolverEndpointsInput{}, func(page *route53resolver.ListResolverEndpointsOutput, lastPage bool) bool { + err = conn.ListResolverEndpointsPages(input, func(page *route53resolver.ListResolverEndpointsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, resolverEndpoint := range page.ResolverEndpoints { - id := aws.StringValue(resolverEndpoint.Id) - - log.Printf("[INFO] Deleting Route53 Resolver endpoint: %s", id) - _, err := conn.DeleteResolverEndpoint(&route53resolver.DeleteResolverEndpointInput{ - ResolverEndpointId: aws.String(id), - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - continue - } - if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error deleting Route53 Resolver endpoint (%s): %w", id, err)) - continue - } + for _, v := range page.ResolverEndpoints { + r := ResourceEndpoint() + d := r.Data(nil) + d.SetId(aws.StringValue(v.Id)) - err = EndpointWaitUntilTargetState(conn, id, endpointDeletedDefaultTimeout, - []string{route53resolver.ResolverEndpointStatusDeleting}, - []string{EndpointStatusDeleted}) - if err != nil { - errors = multierror.Append(errors, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Route53 Resolver Endpoint sweep for %s: %s", region, err) + return nil + } + if err != nil { - if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver endpoint sweep for %s: %s", region, err) - return nil - } - errors = multierror.Append(errors, fmt.Errorf("error retrievingRoute53 Resolver endpoints: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Endpoints (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Endpoints (%s): %w", region, err) } - return errors + return nil } -func sweepFirewallConfig(region string) error { +func sweepFirewallConfigs(region string) error { client, err := sweep.SharedRegionalSweepClient(region) if err != nil { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListFirewallConfigsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListFirewallConfigsPages(&route53resolver.ListFirewallConfigsInput{}, func(page *route53resolver.ListFirewallConfigsOutput, lastPage bool) bool { + err = conn.ListFirewallConfigsPages(input, func(page *route53resolver.ListFirewallConfigsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, firewallConfig := range page.FirewallConfigs { - id := aws.StringValue(firewallConfig.Id) - - log.Printf("[INFO] Deleting Route53 Resolver DNS Firewall config: %s", id) + for _, v := range page.FirewallConfigs { r := ResourceFirewallConfig() d := r.Data(nil) - d.SetId(id) - d.Set("resource_id", firewallConfig.ResourceId) - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) + d.Set("resource_id", v.ResourceId) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall configs sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping Route53 Resolver Firewall Config sweep for %s: %s", region, err) + return nil } + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall configs: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Firewall Configs (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Firewall Configs (%s): %w", region, err) + } + + return nil } func sweepFirewallDomainLists(region string) error { @@ -244,40 +228,41 @@ func sweepFirewallDomainLists(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListFirewallDomainListsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListFirewallDomainListsPages(&route53resolver.ListFirewallDomainListsInput{}, func(page *route53resolver.ListFirewallDomainListsOutput, lastPage bool) bool { + err = conn.ListFirewallDomainListsPages(input, func(page *route53resolver.ListFirewallDomainListsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, queryLogConfig := range page.FirewallDomainLists { - id := aws.StringValue(queryLogConfig.Id) - - log.Printf("[INFO] Deleting Route53 Resolver DNS Firewall domain list: %s", id) + for _, v := range page.FirewallDomainLists { r := ResourceFirewallDomainList() d := r.Data(nil) - d.SetId(id) - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall domain lists sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping Route53 Resolver Firewall Domain List sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Route53 Resolver Firewall Domain Lists (%s): %w", region, err) } + + err = sweep.SweepOrchestrator(sweepResources) + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall domain lists: %w", err)) + return fmt.Errorf("error sweeping Route53 Resolver Firewall Domain Lists (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + return nil } func sweepFirewallRuleGroupAssociations(region string) error { @@ -286,65 +271,41 @@ func sweepFirewallRuleGroupAssociations(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListFirewallRuleGroupAssociationsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListFirewallRuleGroupAssociationsPages(&route53resolver.ListFirewallRuleGroupAssociationsInput{}, func(page *route53resolver.ListFirewallRuleGroupAssociationsOutput, lastPage bool) bool { + err = conn.ListFirewallRuleGroupAssociationsPages(input, 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) + for _, v := range page.FirewallRuleGroupAssociations { r := ResourceFirewallRuleGroupAssociation() d := r.Data(nil) - d.SetId(id) - - if aws.StringValue(firewallRuleGroupAssociation.MutationProtection) == route53resolver.MutationProtectionStatusEnabled { - input := &route53resolver.UpdateFirewallRuleGroupAssociationInput{ - FirewallRuleGroupAssociationId: firewallRuleGroupAssociation.Id, - Name: firewallRuleGroupAssociation.Name, - MutationProtection: aws.String(route53resolver.MutationProtectionStatusDisabled), - } - - _, err := conn.UpdateFirewallRuleGroupAssociation(input) - - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } - - _, err = WaitFirewallRuleGroupAssociationUpdated(conn, d.Id()) - - if err != nil { - log.Printf("[ERROR] error waiting for Route53 Resolver DNS Firewall rule group association (%s) to be updated: %s", d.Id(), err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } - } - - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(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 + log.Printf("[WARN] Skipping Route53 Resolver Firewall Rule Group Association sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Route53 Resolver Firewall Rule Group Associations (%s): %w", region, err) } + + err = sweep.SweepOrchestrator(sweepResources) + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall rule group associations: %w", err)) + return fmt.Errorf("error sweeping Route53 Resolver Firewall Rule Group Associations (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + return nil } func sweepFirewallRuleGroups(region string) error { @@ -353,40 +314,41 @@ func sweepFirewallRuleGroups(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListFirewallRuleGroupsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListFirewallRuleGroupsPages(&route53resolver.ListFirewallRuleGroupsInput{}, func(page *route53resolver.ListFirewallRuleGroupsOutput, lastPage bool) bool { + err = conn.ListFirewallRuleGroupsPages(input, func(page *route53resolver.ListFirewallRuleGroupsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, firewallRuleGroup := range page.FirewallRuleGroups { - id := aws.StringValue(firewallRuleGroup.Id) - - log.Printf("[INFO] Deleting Route53 Resolver DNS Firewall rule group: %s", id) + for _, v := range page.FirewallRuleGroups { r := ResourceFirewallRuleGroup() d := r.Data(nil) - d.SetId(id) - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall rule groups sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping Route53 Resolver Firewall Rule Group sweep for %s: %s", region, err) + return nil } + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall rule groups: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Firewall Rule Groups (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Firewall Rule Groups (%s): %w", region, err) + } + + return nil } func sweepFirewallRules(region string) error { @@ -395,119 +357,109 @@ func sweepFirewallRules(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn + input := &route53resolver.ListFirewallRuleGroupsInput{} var sweeperErrs *multierror.Error + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListFirewallRuleGroupsPages(&route53resolver.ListFirewallRuleGroupsInput{}, func(page *route53resolver.ListFirewallRuleGroupsOutput, lastPage bool) bool { + err = conn.ListFirewallRuleGroupsPages(input, func(page *route53resolver.ListFirewallRuleGroupsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, ruleGroup := range page.FirewallRuleGroups { - if ruleGroup == nil { - continue - } - - ruleGroupId := aws.StringValue(ruleGroup.Id) - + for _, v := range page.FirewallRuleGroups { input := &route53resolver.ListFirewallRulesInput{ - FirewallRuleGroupId: ruleGroup.Id, + FirewallRuleGroupId: v.Id, } - err = conn.ListFirewallRulesPages(input, func(page *route53resolver.ListFirewallRulesOutput, lastPage bool) bool { + err := conn.ListFirewallRulesPages(input, func(page *route53resolver.ListFirewallRulesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, firewallRule := range page.FirewallRules { - id := FirewallRuleCreateID(*firewallRule.FirewallRuleGroupId, *firewallRule.FirewallDomainListId) - - log.Printf("[INFO] Deleting Route53 Resolver DNS Firewall rule: %s", id) + for _, v := range page.FirewallRules { r := ResourceFirewallRule() 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 - } + d.SetId(FirewallRuleCreateResourceID(aws.StringValue(v.FirewallRuleGroupId), aws.StringValue(v.FirewallDomainListId))) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall rules sweep (RuleGroup: %s) for %s: %s", ruleGroupId, region, err) continue } if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall rules for rule group (%s): %w", ruleGroupId, err)) - continue + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Route53 Resolver Firewall Rules (%s): %w", region, err)) } - - return !lastPage } return !lastPage }) if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver DNS Firewall rules sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Print(fmt.Errorf("[WARN] Skipping Route53 Resolver Firewall Rule sweep for %s: %w", region, err)) + return sweeperErrs.ErrorOrNil() } if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver DNS Firewall rule groups: %w", err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Route53 Resolver Firewall Rule Groups (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Route53 Resolver Firewall Rules (%s): %w", region, err)) } return sweeperErrs.ErrorOrNil() } -func sweepQueryLogAssociationsConfig(region string) error { +func sweepQueryLogAssociationsConfigs(region string) error { client, err := sweep.SharedRegionalSweepClient(region) if err != nil { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListResolverQueryLogConfigAssociationsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListResolverQueryLogConfigAssociationsPages(&route53resolver.ListResolverQueryLogConfigAssociationsInput{}, func(page *route53resolver.ListResolverQueryLogConfigAssociationsOutput, lastPage bool) bool { + err = conn.ListResolverQueryLogConfigAssociationsPages(input, func(page *route53resolver.ListResolverQueryLogConfigAssociationsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, queryLogConfigAssociation := range page.ResolverQueryLogConfigAssociations { - id := aws.StringValue(queryLogConfigAssociation.Id) - - log.Printf("[INFO] Deleting Route53 Resolver Query Log Config Association: %s", id) + for _, v := range page.ResolverQueryLogConfigAssociations { r := ResourceQueryLogConfigAssociation() d := r.Data(nil) - d.SetId(id) - // The following additional arguments are required during the resource's Delete operation - d.Set("resolver_query_log_config_id", queryLogConfigAssociation.ResolverQueryLogConfigId) - d.Set("resource_id", queryLogConfigAssociation.ResourceId) - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) + d.Set("resolver_query_log_config_id", v.ResolverQueryLogConfigId) + d.Set("resource_id", v.ResourceId) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver Query Log Config Associations sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping Route53 Resolver Query Log Config Association sweep for %s: %s", region, err) + return nil } + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver Query Log Config Associations: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Query Log Config Associations (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Query Log Config Associations (%s): %w", region, err) + } + + return nil } func sweepQueryLogsConfig(region string) error { @@ -516,40 +468,41 @@ func sweepQueryLogsConfig(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn - var sweeperErrs *multierror.Error + input := &route53resolver.ListResolverQueryLogConfigsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.ListResolverQueryLogConfigsPages(&route53resolver.ListResolverQueryLogConfigsInput{}, func(page *route53resolver.ListResolverQueryLogConfigsOutput, lastPage bool) bool { + err = conn.ListResolverQueryLogConfigsPages(input, func(page *route53resolver.ListResolverQueryLogConfigsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, queryLogConfig := range page.ResolverQueryLogConfigs { - id := aws.StringValue(queryLogConfig.Id) - - log.Printf("[INFO] Deleting Route53 Resolver Query Log Config: %s", id) + for _, v := range page.ResolverQueryLogConfigs { r := ResourceQueryLogConfig() d := r.Data(nil) - d.SetId(id) - err := r.Delete(d, client) + d.SetId(aws.StringValue(v.Id)) - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver Query Log Configs sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping Route53 Resolver Query Log Config sweep for %s: %s", region, err) + return nil } + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Route53 Resolver Query Log Configs: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Query Log Configs (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Query Log Configs (%s): %w", region, err) + } + + return nil } func sweepRuleAssociations(region string) error { @@ -558,53 +511,43 @@ func sweepRuleAssociations(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn + input := &route53resolver.ListResolverRuleAssociationsInput{} + sweepResources := make([]sweep.Sweepable, 0) - var errors error - err = conn.ListResolverRuleAssociationsPages(&route53resolver.ListResolverRuleAssociationsInput{}, func(page *route53resolver.ListResolverRuleAssociationsOutput, lastPage bool) bool { + err = conn.ListResolverRuleAssociationsPages(input, func(page *route53resolver.ListResolverRuleAssociationsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, resolverRuleAssociation := range page.ResolverRuleAssociations { - id := aws.StringValue(resolverRuleAssociation.Id) - - log.Printf("[INFO] Deleting Route53 Resolver rule association %q", id) - _, err := conn.DisassociateResolverRule(&route53resolver.DisassociateResolverRuleInput{ - ResolverRuleId: resolverRuleAssociation.ResolverRuleId, - VPCId: resolverRuleAssociation.VPCId, - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - continue - } - if sweep.SkipSweepError(err) { - log.Printf("[INFO] Skipping Route53 Resolver rule association %q: %s", id, err) - continue - } - if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error deleting Route53 Resolver rule association (%s): %w", id, err)) - continue - } + for _, v := range page.ResolverRuleAssociations { + r := ResourceRuleAssociation() + d := r.Data(nil) + d.SetId(aws.StringValue(v.Id)) + d.Set("resolver_rule_id", v.ResolverRuleId) + d.Set("vpc_id", v.VPCId) - err = RuleAssociationWaitUntilTargetState(conn, id, ruleAssociationDeletedDefaultTimeout, - []string{route53resolver.ResolverRuleAssociationStatusDeleting}, - []string{RuleAssociationStatusDeleted}) - if err != nil { - errors = multierror.Append(errors, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Route53 Resolver Rule Association sweep for %s: %s", region, err) + return nil + } + if err != nil { - if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver rule association sweep for %s: %s", region, err) - return nil - } - errors = multierror.Append(errors, fmt.Errorf("error retrievingRoute53 Resolver rule associations: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Rule Associations (%s): %w", region, err) } - return errors + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Rule Associations (%s): %w", region, err) + } + + return nil } func sweepRules(region string) error { @@ -613,52 +556,43 @@ func sweepRules(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).Route53ResolverConn + input := &route53resolver.ListResolverRulesInput{} + sweepResources := make([]sweep.Sweepable, 0) - var errors error - err = conn.ListResolverRulesPages(&route53resolver.ListResolverRulesInput{}, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { + err = conn.ListResolverRulesPages(input, func(page *route53resolver.ListResolverRulesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, resolverRule := range page.ResolverRules { - id := aws.StringValue(resolverRule.Id) - - ownerID := aws.StringValue(resolverRule.OwnerId) - if ownerID != client.(*conns.AWSClient).AccountID { - log.Printf("[INFO] Skipping Route53 Resolver rule %q, owned by %q", id, ownerID) + for _, v := range page.ResolverRules { + if aws.StringValue(v.OwnerId) != client.(*conns.AWSClient).AccountID { continue } - log.Printf("[INFO] Deleting Route53 Resolver rule %q", id) - _, err := conn.DeleteResolverRule(&route53resolver.DeleteResolverRuleInput{ - ResolverRuleId: aws.String(id), - }) - if tfawserr.ErrCodeEquals(err, route53resolver.ErrCodeResourceNotFoundException) { - continue - } - if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error deleting Route53 Resolver rule (%s): %w", id, err)) - continue - } + r := ResourceRule() + d := r.Data(nil) + d.SetId(aws.StringValue(v.Id)) - err = RuleWaitUntilTargetState(conn, id, ruleDeletedDefaultTimeout, - []string{route53resolver.ResolverRuleStatusDeleting}, - []string{RuleStatusDeleted}) - if err != nil { - errors = multierror.Append(errors, err) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } return !lastPage }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Route53 Resolver Rule sweep for %s: %s", region, err) + return nil + } + if err != nil { - if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping Route53 Resolver rule sweep for %s: %s", region, err) - return nil - } - errors = multierror.Append(errors, fmt.Errorf("error retrievingRoute53 Resolver rules: %w", err)) + return fmt.Errorf("error listing Route53 Resolver Rules (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Route53 Resolver Rules (%s): %w", region, err) } - return errors + return nil } diff --git a/internal/service/route53resolver/validate_test.go b/internal/service/route53resolver/validate_test.go index b2588da7ae7..964ae6a28f8 100644 --- a/internal/service/route53resolver/validate_test.go +++ b/internal/service/route53resolver/validate_test.go @@ -39,7 +39,7 @@ func TestValidResolverName(t *testing.T) { for _, tc := range cases { _, errors := validResolverName(tc.Value, "aws_route53_resolver_endpoint") if len(errors) != tc.ErrCount { - t.Fatalf("Expected the AWS Route 53 Resolver Endpoint Name to not trigger a validation error for %q", tc.Value) + t.Fatalf("Expected the AWS Route53 Resolver Endpoint Name to not trigger a validation error for %q", tc.Value) } } } diff --git a/internal/service/route53resolver/wait.go b/internal/service/route53resolver/wait.go deleted file mode 100644 index 35159dce70e..00000000000 --- a/internal/service/route53resolver/wait.go +++ /dev/null @@ -1,252 +0,0 @@ -package route53resolver - -import ( - "fmt" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -const ( - // Maximum amount of time to wait for a QueryLogConfigAssociation to return ACTIVE - QueryLogConfigAssociationCreatedTimeout = 5 * time.Minute - - // Maximum amount of time to wait for a QueryLogConfigAssociation to be deleted - QueryLogConfigAssociationDeletedTimeout = 5 * time.Minute - - // Maximum amount of time to wait for a QueryLogConfig to return CREATED - QueryLogConfigCreatedTimeout = 5 * time.Minute - - // Maximum amount of time to wait for a QueryLogConfig to be deleted - QueryLogConfigDeletedTimeout = 5 * time.Minute - - // Maximum amount of time to wait for a DnssecConfig to return ENABLED - DNSSECConfigCreatedTimeout = 10 * time.Minute - - // Maximum amount of time to wait for a DnssecConfig to return DISABLED - DNSSECConfigDeletedTimeout = 10 * time.Minute - - // Maximum amount of time to wait for a FirewallDomainList to be updated - FirewallDomainListUpdatedTimeout = 5 * time.Minute - - // 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 -) - -// WaitQueryLogConfigAssociationCreated waits for a QueryLogConfig to return ACTIVE -func WaitQueryLogConfigAssociationCreated(conn *route53resolver.Route53Resolver, queryLogConfigAssociationID string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverQueryLogConfigAssociationStatusCreating}, - Target: []string{route53resolver.ResolverQueryLogConfigAssociationStatusActive}, - Refresh: StatusQueryLogConfigAssociation(conn, queryLogConfigAssociationID), - Timeout: QueryLogConfigAssociationCreatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverQueryLogConfigAssociation); ok { - return v, err - } - - return nil, err -} - -// WaitQueryLogConfigAssociationCreated waits for a QueryLogConfig to be deleted -func WaitQueryLogConfigAssociationDeleted(conn *route53resolver.Route53Resolver, queryLogConfigAssociationID string) (*route53resolver.ResolverQueryLogConfigAssociation, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverQueryLogConfigAssociationStatusDeleting}, - Target: []string{}, - Refresh: StatusQueryLogConfigAssociation(conn, queryLogConfigAssociationID), - Timeout: QueryLogConfigAssociationDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverQueryLogConfigAssociation); ok { - return v, err - } - - return nil, err -} - -// WaitQueryLogConfigCreated waits for a QueryLogConfig to return CREATED -func WaitQueryLogConfigCreated(conn *route53resolver.Route53Resolver, queryLogConfigID string) (*route53resolver.ResolverQueryLogConfig, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverQueryLogConfigStatusCreating}, - Target: []string{route53resolver.ResolverQueryLogConfigStatusCreated}, - Refresh: StatusQueryLogConfig(conn, queryLogConfigID), - Timeout: QueryLogConfigCreatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverQueryLogConfig); ok { - return v, err - } - - return nil, err -} - -// WaitQueryLogConfigCreated waits for a QueryLogConfig to be deleted -func WaitQueryLogConfigDeleted(conn *route53resolver.Route53Resolver, queryLogConfigID string) (*route53resolver.ResolverQueryLogConfig, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverQueryLogConfigStatusDeleting}, - Target: []string{}, - Refresh: StatusQueryLogConfig(conn, queryLogConfigID), - Timeout: QueryLogConfigDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverQueryLogConfig); ok { - return v, err - } - - return nil, err -} - -// WaitDNSSECConfigCreated waits for a DnssecConfig to return ENABLED -func WaitDNSSECConfigCreated(conn *route53resolver.Route53Resolver, dnssecConfigID string) (*route53resolver.ResolverDnssecConfig, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverDNSSECValidationStatusEnabling}, - Target: []string{route53resolver.ResolverDNSSECValidationStatusEnabled}, - Refresh: StatusDNSSECConfig(conn, dnssecConfigID), - Timeout: DNSSECConfigCreatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverDnssecConfig); ok { - return v, err - } - - return nil, err -} - -// WaitDNSSECConfigDisabled waits for a DnssecConfig to return DISABLED -func WaitDNSSECConfigDisabled(conn *route53resolver.Route53Resolver, dnssecConfigID string) (*route53resolver.ResolverDnssecConfig, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.ResolverDNSSECValidationStatusDisabling}, - Target: []string{route53resolver.ResolverDNSSECValidationStatusDisabled}, - Refresh: StatusDNSSECConfig(conn, dnssecConfigID), - Timeout: DNSSECConfigDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.ResolverDnssecConfig); ok { - return v, err - } - - return nil, err -} - -// WaitFirewallDomainListUpdated waits for a FirewallDomainList to be updated -func WaitFirewallDomainListUpdated(conn *route53resolver.Route53Resolver, firewallDomainListId string) (*route53resolver.FirewallDomainList, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ - route53resolver.FirewallDomainListStatusUpdating, - route53resolver.FirewallDomainListStatusImporting, - }, - Target: []string{ - route53resolver.FirewallDomainListStatusComplete, - route53resolver.FirewallDomainListStatusCompleteImportFailed, - }, - Refresh: StatusFirewallDomainList(conn, firewallDomainListId), - Timeout: FirewallDomainListUpdatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.FirewallDomainList); ok { - if aws.StringValue(v.Status) != route53resolver.FirewallDomainListStatusComplete { - err = fmt.Errorf("error updating Route 53 Resolver DNS Firewall domain list (%s): %s", firewallDomainListId, aws.StringValue(v.StatusMessage)) - } - return v, err - } - - return nil, err -} - -// WaitFirewallDomainListDeleted waits for a FirewallDomainList to be deleted -func WaitFirewallDomainListDeleted(conn *route53resolver.Route53Resolver, firewallDomainListId string) (*route53resolver.FirewallDomainList, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.FirewallDomainListStatusDeleting}, - Target: []string{}, - Refresh: StatusFirewallDomainList(conn, firewallDomainListId), - Timeout: FirewallDomainListDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.FirewallDomainList); ok { - return v, err - } - - return nil, err -} - -// WaitFirewallRuleGroupAssociationCreated waits for a FirewallRuleGroupAssociation to return COMPLETE -func WaitFirewallRuleGroupAssociationCreated(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, - Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, - Refresh: StatusFirewallRuleGroupAssociation(conn, firewallRuleGroupAssociationId), - Timeout: FirewallRuleGroupAssociationCreatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { - return v, err - } - - return nil, err -} - -// WaitFirewallRuleGroupAssociationUpdated waits for a FirewallRuleGroupAssociation to return COMPLETE -func WaitFirewallRuleGroupAssociationUpdated(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusUpdating}, - Target: []string{route53resolver.FirewallRuleGroupAssociationStatusComplete}, - Refresh: StatusFirewallRuleGroupAssociation(conn, firewallRuleGroupAssociationId), - Timeout: FirewallRuleGroupAssociationUpdatedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { - return v, err - } - - return nil, err -} - -// WaitFirewallRuleGroupAssociationDeleted waits for a FirewallRuleGroupAssociation to be deleted -func WaitFirewallRuleGroupAssociationDeleted(conn *route53resolver.Route53Resolver, firewallRuleGroupAssociationId string) (*route53resolver.FirewallRuleGroupAssociation, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{route53resolver.FirewallRuleGroupAssociationStatusDeleting}, - Target: []string{}, - Refresh: StatusFirewallRuleGroupAssociation(conn, firewallRuleGroupAssociationId), - Timeout: FirewallRuleGroupAssociationDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForState() - - if v, ok := outputRaw.(*route53resolver.FirewallRuleGroupAssociation); ok { - return v, err - } - - return nil, err -} diff --git a/website/docs/r/route53_resolver_config.html.markdown b/website/docs/r/route53_resolver_config.html.markdown new file mode 100644 index 00000000000..e7ebc3fc9fd --- /dev/null +++ b/website/docs/r/route53_resolver_config.html.markdown @@ -0,0 +1,48 @@ +--- +subcategory: "Route 53 Resolver" +layout: "aws" +page_title: "AWS: aws_route53_resolver_config" +description: |- + Provides a Route 53 Resolver config resource. +--- + +# Resource: aws_route53_resolver_config + +Provides a Route 53 Resolver config resource. + +## Example Usage + +```terraform +resource "aws_vpc" "example" { + cidr_block = "10.0.0.0/16" + enable_dns_support = true + enable_dns_hostnames = true +} + +resource "aws_route53_resolver_config" "example" { + resource_id = aws_vpc.example.id + autodefined_reverse_flag = "DISABLE" +} +``` + +## Argument Reference + +The following argument is supported: + +* `resource_id` - (Required) The ID of the VPC that the configuration is for. +* `autodefined_reverse_flag` - (Required) Indicates whether or not the Resolver will create autodefined rules for reverse DNS lookups. Valid values: `ENABLE`, `DISABLE`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the resolver configuration. +* `owner_id` - The AWS account ID of the owner of the VPC that this resolver configuration applies to. + +## Import + +Route 53 Resolver configs can be imported using the Route 53 Resolver config ID, e.g., + +``` +$ terraform import aws_route53_resolver_config.example rslvr-rc-715aa20c73a23da7 +```